champions-of-luneraria/Assets/Luneraria/View/LuneMatchDriver.cs
Martin Stickelbroeck 769e622952 U4 Netzwerk: LuneSession, TCP, Host/Join, 43+12 Prüfungen
Der M4-Umfang ist als Protokoll 4 portiert, serverautoritativ. Der Host
besitzt LuneMatch und rechnet jeden Kampf; Clients senden nur Absichten
und zeigen den Kampf aus Host-Momentaufnahmen.

- LuneSession (Core/Net): Rollen offline/Host/Gast, sechs Sitze, Lobby,
  Hallo mit Protokollprüfung und Namenskürzung, Beitritt und Wieder-
  einstieg über den Anzeigenamen, private Planung mit Spoof-Schutz,
  Bereit ist verbindlich, Bots planen einmal je Runde, Disconnect gibt
  den Platz an einen Bot (nie Kernverlust), Übernahme verwirft den
  unbestätigten Bot-Entwurf, Revanche hält die Plätze, Hostverlust
  meldet und führt ins Menü zurück.
- Kampfsynchronisation: Beginn mit Einheitenätzen und Gebäude-HP, alle
  0,05 s neue Einheiten und ein Frame (13 Werte je Einheit), späte
  Frames über die Serial verworfen, Gast interpoliert (frame_alpha).
- Transport: LuneLoopback (In-Prozess) für die Tests, LuneTcp + LuneWire
  (Cursor-Codec) für Echtverbindungen statt ENet; ein Socket je Gast.
- Oberfläche: LuneMainMenu (Offline/Hosten/Beitreten, Bund, Lane, Name,
  Adresse, Port) als Startbildschirm, LuneNetPlay als Schale wie
  lune_main.gd. LuneGame leitet Befehle, Rückgängig und Bereit im
  Netzspiel über die Sitzung; Tempo und Pause bestimmt der Host.
- Tests: NetSuite 43/43 (Port von test_lune_net.gd), NetRoundsSuite
  12/12 (Port von net_rounds.py: 5 Runden, Prüfsummen gleich, Gast
  rechnet 0 Schritte, Drop und Wiedereinstieg auf denselben Sitz),
  LuneWireTests Round-trip. EditMode 29/29, PlayMode 13/13, Kerntests
  457/457. Fingerabdrücke der Bot-Partien unverändert.
- Dokumentation: U4-NETWORK (Abnahme, Grenzen), PORT-QA (U4), PORT-STATUS
  (U4 fertig, U5 nächster Schritt), KNOWN-BEHAVIOR-DIFFERENCES.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:14:42 +02:00

110 lines
4.6 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Collections.Generic;
using Luneraria.Core;
using UnityEngine;
namespace Luneraria.View
{
/// <summary>
/// Hosts a match in the scene (U2C/U2D; human seat and tempo since U3). Update only hands frame
/// time (times the speed) to the runner's fixed clock; it never decides anything in combat.
/// Seed, start round and extra gold are the public fixtures of the golden scenarios. With
/// HumanSlot &gt;= 0 the HUD plans that seat and planning waits for its Ready; tempo steps
/// (1×, 2×, 4×) and pause act only in combat, as in Godot (lune_game.gd).
/// </summary>
public sealed class LuneMatchDriver : MonoBehaviour
{
/// <summary>Tempo steps of the HUD's tempo button.</summary>
public static readonly int[] SpeedSteps = { 1, 2, 4 };
public int Seed = 2;
public int StartRound = 1;
public int ExtraGold;
public string Profile = "";
/// <summary>Optional Bünde of the six seats (fixtures like Godot's start option "bunds"); empty seats follow the seed.</summary>
[System.NonSerialized] public string[] Bunds;
/// <summary>The seat a human plays through the HUD; -1 for a bot match (U2 tests, benchmark).</summary>
public int HumanSlot = -1;
/// <summary>Match speed: simulated seconds per real second (tests run faster than 1).</summary>
public float Speed = 1f;
/// <summary>Index into SpeedSteps (the player's tempo, on top of Speed).</summary>
public int SpeedIndex;
public bool Paused;
public bool RecordTrace = true;
public LuneMatchRunner Runner { get; private set; }
/// <summary>Main-thread cost of the last frame's simulation (milliseconds).</summary>
public double LastAdvanceMs { get; private set; }
/// <summary>Bound network session (U4); null offline.</summary>
public LuneSession Session;
/// <summary>True on a guest: the host simulates, this side only shows snapshots.</summary>
public bool IsClient;
readonly System.Diagnostics.Stopwatch _clock = new System.Diagnostics.Stopwatch();
public int Tempo { get { return SpeedSteps[Mathf.Clamp(SpeedIndex, 0, SpeedSteps.Length - 1)]; } }
public void Restart()
{
var options = new MatchOptions { Seed = Seed, HumanSlot = HumanSlot, Bunds = Bunds != null ? new List<string>(Bunds) : new List<string>() };
Runner = new LuneMatchRunner(options, StartRound, ExtraGold, Profile);
if (RecordTrace)
Runner.Trace = new LuneTrace();
SpeedIndex = 0;
Paused = false;
Session = null;
IsClient = false;
}
/// <summary>
/// Adopts the session's match (U4). The host still advances combat and ticks snapshots;
/// a guest never advances — its match is a display mirror of the host's snapshots.
/// </summary>
public void Adopt(LuneMatch match, int humanSlot, bool isClient, LuneSession session)
{
Session = session;
IsClient = isClient;
Runner = new LuneMatchRunner(match, humanSlot, session);
SpeedIndex = 0;
Paused = false;
}
/// <summary>Next tempo step (1× → 2× → 4× → 1×).</summary>
public void CycleSpeed()
{
SpeedIndex = (SpeedIndex + 1) % SpeedSteps.Length;
}
/// <summary>Pause exists only in combat; planning waits for the player anyway.</summary>
public bool TogglePause()
{
if (Runner == null || Runner.Current != LuneMatchRunner.Stage.Combat)
return false;
Paused = !Paused;
return true;
}
void Awake()
{
if (Runner == null)
Restart();
}
void Update()
{
if (Runner == null)
return;
_clock.Restart();
// A guest shows the host's snapshots and never advances the match.
if (IsClient)
{
LastAdvanceMs = 0.0;
return;
}
// Pause holds only the combat steps (Godot: a paused game still plans and settles).
if (!(Paused && Runner.Current == LuneMatchRunner.Stage.Combat))
Runner.Advance(Time.deltaTime * Speed * Tempo);
if (Session != null && Session.IsNetworked() && Session.IsAuthority())
Session.HostTick(Time.deltaTime * Speed * Tempo);
LastAdvanceMs = _clock.Elapsed.TotalMilliseconds;
}
}
}