champions-of-luneraria/docs/port/U4-NETWORK.md
Martin Stickelbroeck d590fdebeb U4-Nachtrag: Startbildschirm in der Szene, Rückkehr aus der Partie
Befund aus dem Spieltest des U3-Nachtrags: Nichts legte LuneNetPlay in die
Szene. Das Spiel begann direkt in einer Partie, der Startbildschirm aus U4
war unerreichbar, und der Verlassen-Dialog beendete das Spiel statt zum
Hauptmenü zu führen.

- LuneWorldBuilder legt LuneNetPlay auf das Match-Objekt; Szene neu gebaut.
- Das Menü bekommt ein eigenes UIDocument über dem HUD. Vorher hing es an
  keinem Panel, und das HUD hätte es beim ersten Partiestart geleert.
- Rückkehr über „Menü“ → „Zum Menü“: HUD und Aufstellungsflächen weg,
  Sitzung beendet, leere Karte. Die Kamera ruht hinter dem Menü und beginnt
  jede Partie ungedreht. Die Bund-Wahl ist markiert, die Knopfreihe passt
  in die Tafel.
- Tests und Benchmark ohne Startbildschirm (LuneMatchDriver.DirectStart,
  -luneBenchmark); Bot-Partien und Fingerabdrücke unverändert.
- Neu: LuneSmoke (-luneSmoke <Ordner>), Selbsttest eines Players nach
  lune_smoke.gd. StartScreenTests führt ihn im Editor aus.
- Tests: EditMode 29/29, PlayMode 14/14 (StartScreenTests 26/26), Verify
  718/0, Fingerabdrücke DCE95C96556F3A1A und 7B8E43AEFC0D60C5 unverändert.
  macOS-Player: Selbsttest 26/26, Bilder in
  docs/port/screenshots/u4/startbildschirm/. Ein zusammenhängender
  PlayMode-Lauf auf dem Endstand und ein Player-Benchmark stehen aus, weil
  der Mac zugeklappt schlief (siehe U4-NETWORK.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 05:37:51 +02:00

123 lines
12 KiB
Markdown
Raw 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.

# U4 · Netzwerk
Stand: 2. Oktober 2026 · **fertig** (`luneraria/u4` = `769e622`). Nachtrag vom 7. Oktober 2026: Startbildschirm in der Szene ([unten](#nachtrag-startbildschirm-in-der-szene-7-oktober-2026)).
Es wurde keine neue Architektur erfunden. Übertragen ist der M4-Umfang (`lune_session.gd`, Protokoll 4), serverautoritativ:
- Clients senden Absichten.
- Der Host berechnet Gold, Käufe, Kampf, Schaden, Besitz, Boss und Sieg.
## Umsetzung
| Baustein | Ort | Inhalt |
| --- | --- | --- |
| `LuneSession` | `Assets/Luneraria/Core/Net/LuneSession.cs` | autoritative Sitzung (Port von `lune_session.gd`): Rollen, Sitze, Lobby, Hallo/Beitritt/Wiedereinstieg, Befehle, Bereit, Kampfbeginn, Momentaufnahmen, Abrechnung, Revanche, Disconnect |
| `NetModel` | `Assets/Luneraria/Core/Net/NetModel.cs` | Sitzungsmodelle (Lobby, öffentlicher Zustand, Einheitenätzeze, Rahmen) und `ILuneNetLink` |
| `LuneLoopback` | `Assets/Luneraria/Core/Net/LuneLoopback.cs` | In-Prozess-Transport für die Protokoll- und Mehr-Runden-Tests |
| `LuneWire` | `Assets/Luneraria/Net/LuneWire.cs` | Codierung der Nachrichten (Art + Payload, Cursor-Codec) und der Kampfrahmen |
| `LuneTcp` | `Assets/Luneraria/Net/LuneTcp.cs` | TCP-Transport (Ersatz für ENet): ein Socket je Gast, zuverlässige Nachrichten und Rahmen, Pump auf dem Spielthread |
| `LuneMainMenu` | `Assets/Luneraria/UI/Game/LuneMainMenu.cs` | Startbildschirm: Allein spielen, Hosten, Beitreten, Bund, Lane, Name, Adresse, Port |
| `LuneNetPlay` | `Assets/Luneraria/UI/Game/LuneNetPlay.cs` | Schale um Sitzung und Transport (Port von `lune_main.gd`): Menü, Partie, Rückkehr, Revanche. Seit dem Nachtrag legt `LuneWorldBuilder` sie in die Szene |
| `LuneSmoke` | `Assets/Luneraria/UI/Game/LuneSmoke.cs` | Selbsttest eines Players über den Startbildschirm (nach `lune_smoke.gd`), `-luneSmoke <Ordner>`; Nachtrag |
| `LuneGame` | `Assets/Luneraria/UI/Game/LuneGame.cs` | Befehle, Rückgängig und Bereit gehen im Netzspiel über die Sitzung; Gast zeigt nur Host-Rahmen |
Offline ist ein lokaler Host mit Bot-Plätzen (`LuneSession.HostOffline`). Hosten und Beitreten nutzen `LuneTcp` mit `LuneWire`. Peer 1 ist der Host; freie Plätze bleiben Bots.
## Erfasster M4-Umfang
| Funktion | Verhalten in der Referenz | im Port |
| --- | --- | --- |
| Rollen | offline (lokaler Host mit Bot-Plätzen), Host (ENet-Server, bis 5 Gäste), Gast | erfüllt (offline, Host, Gast; TCP statt ENet) |
| Sechs Plätze | ein Peer genau ein Platz; freie Plätze spielen Bots; Lobbyeinträge mit Name, Bereit, Bot | erfüllt |
| Beitritt | Hallo mit Protokollversion und Name; falsche Version wird abgelehnt; Namen gekürzt (24 Zeichen); volle Partie abgelehnt | erfüllt |
| Private Planung | ein Gast sendet Befehle für seinen Platz; fremde Plätze werden abgelehnt (Spoof-Schutz); der Zustand enthält nur den eigenen Entwurf | erfüllt |
| Bereit | nach Bereit ist die Planung verbindlich; alle bereit → Kampf; Bots planen einmal je Runde | erfüllt |
| Kampfsynchronisation | Kampfbeginn mit Einheitenätzen und Gebäude-HP; alle 0,05 s neue Einheiten und ein Frame (13 Werte je Einheit, ungeordnet-zuverlässig); Gast interpoliert, verspätete Frames werden verworfen | erfüllt (Rahmen mit Serial; späte Frames verworfen) |
| Abrechnung | Host sendet den öffentlichen Zustand nach dem Kampf | erfüllt |
| Disconnect | Platz geht an einen Bot, der in der Planung sofort plant; niemals Kernverlust | erfüllt |
| Wiedereinstieg | gleicher Name, solange der Platz bot-gehalten ist → Platz zurück, volle Synchronisation | erfüllt |
| Übernahme | ein neuer Mensch verwirft den unbestätigten Bot-Entwurf und muss selbst bereit melden | erfüllt |
| Revanche | Host startet neu, Plätze bleiben | erfüllt |
| Hostverlust | Gast kehrt mit Meldung ins Menü zurück | erfüllt |
| Tests | `test_lune_net` 43 Prüfungen, `net_rounds.py` Loopback mit zwei Prozessen 12 Prüfungen | erfüllt: `NetSuite` 43/43, `NetRoundsSuite` 12/12 (In-Prozess-Loopback statt zwei Prozesse) |
## Prüfung
| Test | Ergebnis |
| --- | --- |
| `NetSuite` (Port von `test_lune_net.gd`) | **43/43**: Sitzplatzbesitz und Spoof, veraltete Befehle, Doppelkauf, fremde UID, Disconnect ohne Kernschaden, sechs Plätze und Phasen, Hallo und Wiedereinstieg, Bot-Übernahme und einmaliges Bot-Planen, Bereit verbindlich und Privatsphäre der Entwürfe |
| `NetRoundsSuite` (Port von `net_rounds.py`) | **12/12**: Host und Gast spielen 5 Runden, Prüfsummen nach jeder Abrechnung gleich, Gast rechnet 0 Kampfschritte und erhält Rahmen, Gast A trennt im Kampf von Runde 2, Bot übernimmt, Gast B kehrt mit demselben Namen auf denselben Sitz zurück |
| `LuneWireTests` | Round-trip von Befehlen, öffentlichem Zustand (inkl. Entwurf, Hero, Buchungen), Einheitenätzen, Rahmen, Lobby und Optionen |
| EditMode gesamt | 29/29 (Kern 9 inkl. `NetProtocol` und `NetRounds`, Wire 4, Integration 2, UI-Modell 12, Bildraten 4) |
| PlayMode gesamt | 13/13 (Szene, Vertical Function Test, Capture, Verify A–I, UI-Toolkit-Probe); Bildwerkzeuge und Spieltest `Explicit` |
| Kerntests .NET | 457/457 (75 + 91 + 73 + 157 + 6 + 43 + 12) |
| Fingerabdrücke der Bot-Partien | unverändert `DCE95C96556F3A1A`, `7B8E43AEFC0D60C5` |
Das Loopback der Mehr-Runden-Partie läuft im Prozess mit zwei `LuneSession`-Instanzen und `LuneLoopback` (Nachrichten synchron in Sende-Reihenfolge). Damit sind Protokoll, Prüfsummen, Übernahme und Wiedereinstieg abgedeckt; eine echte Zwei-Prozess-Partie über Sockets bleibt eine optionale Erweiterung.
## Abnahme U4
| Vorgabe | Stand |
| --- | --- |
| Rollen offline / Host / Gast | erfüllt |
| Sechs Plätze, ein Peer ein Platz, freie Plätze Bots | erfüllt |
| Beitritt mit Protokollprüfung, Namenskürzung, volle Partie abgelehnt | erfüllt |
| Private Planung, Spoof-Schutz, nur eigener Entwurf im Zustand | erfüllt |
| Bereit verbindlich, alle bereit → Kampf, Bots einmal je Runde | erfüllt |
| Kampfsynchronisation (Beginn, neue Einheiten, Rahmen 20/s, späte Frames verworfen) | erfüllt |
| Abrechnung mit öffentlichem Zustand | erfüllt |
| Disconnect → Bot, kein Kernverlust | erfüllt |
| Wiedereinstieg über den Namen | erfüllt |
| Übernahme verwirft den Bot-Entwurf | erfüllt |
| Revanche durch den Host | erfüllt |
| Hostverlust → Meldung und Rückkehr ins Menü | erfüllt |
| Mindestumfang: Ready, private Planung, Bot-Übernahme, Wiedereinstieg, Snapshots, Revanche, Disconnect, sechs Plätze | erfüllt |
| Tests 43 + 12 | erfüllt |
## Nachtrag: Startbildschirm in der Szene (7. Oktober 2026)
Zweig `luneraria/u4-nachtrag`, aufgesetzt auf `luneraria/u5.2` (`1b31f9b`, mit dem U3-Nachtrag).
**Befund** aus dem Spieltest des U3-Nachtrags: Nichts legte `LuneNetPlay` in die Szene. `G1_Map.unity` enthielt die Komponente nicht, `LuneWorldBuilder` fügte sie nicht hinzu, und es gab keinen Bootstrap. Das Spiel begann deshalb direkt in einer Partie (`LuneGame.Start` startet sie, wenn `Net` fehlt), und der Startbildschirm war unerreichbar. Der Verlassen-Dialog versprach das Hauptmenü, beendete aber das Spiel.
Beim Einbinden zeigten sich zwei weitere Fehler. Sie hätten den Startbildschirm auch mit der Komponente in der Szene verborgen:
- `LuneNetPlay.Start` legte ein `UIDocument` ohne `PanelSettings` an und hängte das Menü an kein Panel.
- Das HUD übernimmt das `UIDocument` des Match-Objekts und leert es bei jedem Partiestart (`LuneHud.Init`). Ein Menü dort wäre mit der ersten Partie verschwunden.
**Behebung:**
| Teil | Änderung |
| --- | --- |
| Szene | `LuneWorldBuilder` legt `LuneNetPlay` auf das Match-Objekt neben `LuneGame`, `LuneMatchDriver`, `LuneMatchView` und `LuneBenchmark`, mit Verweisen auf Spielcontroller, Treiber, Ansicht, Kamera und UI-Assets. Szene neu gebaut |
| Menüfläche | eigenes `UIDocument` (Objekt „Menu“) mit eigenen `PanelSettings`, über dem HUD (Sortierung 20 über 10). Die Wurzel nimmt keine Klicks, ein verborgenes Menü lässt das HUD frei |
| Start | Spielcontroller und Treiber warten. Die Kamera steht in der Übersicht still, denn die Felder Name und Adresse nehmen W, A, S, D, Q und E |
| Partie | „Allein spielen“, Hosten und Beitreten wie in U4. Jede Partie beginnt mit ungedrehter Kamera; Godot legt dafür je Partie ein neues Spielobjekt an |
| Rückkehr | „Menü“ → „Zum Menü“: HUD und Aufstellungsflächen verschwinden, der Spielcontroller ruht, die Sitzung endet. Der Treiber hält eine neue Partie, die nie läuft; die Karte ist leer wie beim Start |
| Bund-Wahl | Glutpakt und Wurzelbund zeigen die Wahl wie die Lane-Knöpfe; vorher fehlte die Markierung |
| Menübreite | Die vier Aktionsknöpfe brauchen etwa 650 px und ragten bei höchstens 640 px über den Rahmen (erstes Player-Bild); die Tafel darf jetzt 760 px breit werden |
| Verlassen-Dialog | mit Startbildschirm „ZUM MENÜ“; ohne ihn (Tests, Benchmark) sagt der Dialog, dass das Spiel endet, und der Knopf heißt „BEENDEN“ |
**Tests und Benchmark ohne Startbildschirm:** `LuneMatchDriver.DirectStart` (die beiden Szenenlader der Tests setzen ihn) und `-luneBenchmark` (`LuneBenchmark.Running`) halten `LuneNetPlay` heraus. Dann startet `LuneGame` die Partie der Szene wie vor U4, und die Fingerabdrücke der Bot-Partien bleiben gleich.
**Selbsttest im Player** (nach `lune_smoke.gd`, ergänzt um die Rückkehr): `<Player> -luneSmoke <Ordner>` drückt die echten Knöpfe (`LuneButton.Click`; Godots Selbsttest löst `pressed` aus) und prüft im Player auch, dass jeder Knopf in seiner Mitte zuoberst liegt. Jeder Schritt wird vom Bildschirm fotografiert. Am Ende schreibt der Test `report.json` und beendet sich mit 0 (bestanden) oder 1. Ablauf: Startbildschirm → „Allein spielen“ → Planung → „Startaufstellung“ und „Bereit“ → Kampf → „Menü“ → „Zum Menü“ → Startbildschirm → zweite Partie.
**Prüfung:**
| Prüfung | Ergebnis |
| --- | --- |
| `StartScreenTests` (PlayMode, neu) | 26/26 Prüfungen im Editor (Selbsttest ohne Bilder) |
| macOS-Player, `-luneSmoke` | 26/26 in 10,8 s (Release, Metal, 1920×1080). Bilder `screenshots/u4/startbildschirm/`, Bericht `logs/u4/smoke-macos.json`, Protokoll `logs/u4/smoke-macos-log.txt` |
| EditMode | 29/29 |
| PlayMode | 14/14 (13 + `StartScreenTests`), Verify A–I 718/0. Zu den Läufen siehe unten |
| Fingerabdrücke der Bot-Partien | Seed 2 `DCE95C96556F3A1A` (12.690 Schritte), Seed 9 ab Runde 21 `7B8E43AEFC0D60C5` (4.195 Schritte): gleich dem Kern und unverändert, in der Szene auch bei 30, 60, 120 fps und unbegrenzt |
| Benchmark `-luneBenchmark` | `LuneNetPlay` bleibt aus, die Bot-Partie bleibt (Code; die Szenentests nehmen denselben Weg ohne Startbildschirm). Ein Player-Lauf steht aus |
Zu den PlayMode-Läufen: Der erste vollständige Lauf (7. Oktober, 23:28) ergab 14/14. Ihm fehlte nur die Menübreite, eine Layoutzeile, die kein Test liest. Danach wurde der Mac zugeklappt. Er ging in den Ruhezustand („Thermal Emergency Sleep“ um 23:35) und wachte nur noch alle 15 Minuten kurz für Wartung auf. Drei weitere Läufe auf dem Endstand liefen nur in diesen Wachphasen; jeder Test bestand darin mindestens einmal. Der Lauf vom 8. Oktober (05:14) ergab 12/14, mit Bildraten und Vertical Function Test; `F_Input` und `StartScreenTests` bestanden in den Läufen davor und danach. Fehlschläge gab es nur an Echtzeitgrenzen, wenn der Mac dazwischen schlief: Bildratenläufe über 600 s, der 120-s-Wächter des Selbsttests und eine Kameraprüfung 0,6 s nach einem Lane-Klick. Ein zusammenhängender Lauf auf dem Endstand bei geöffnetem Mac steht aus.
## Bekannte Grenzen
- **Transport:** TCP statt ENet. Kampfrahmen laufen auf demselben Socket und werden über die Serial verworfen (wie `unreliable_ordered`). Unter Paketverlust kann TCP Head-of-Line-Blocking erzeugen; für eine Internetpartie wäre ein unzuverlässiger Kanal wie bei ENet der nächste Schritt.
- **Zwei-Prozess-Loopback** (`net_rounds.py`) ist als In-Prozess-Test umgesetzt (`NetRoundsSuite`). Ein Sockets-Lauf über zwei Player ist optional.
- **Windows auf x64-Hardware** und Netz über mehrere Rechner sind nicht gemessen (Build-Module fehlen, siehe [U1.5-UNITY.md](U1.5-UNITY.md)).
- **Hauptmenü** ist der Startbildschirm mit Offline/Hosten/Beitreten; das erweiterte Menü der Referenz (Emblem, Version, erweiterte Optionen, 3D-Bühne) ist vereinfacht. Bis zum Nachtrag vom 7. Oktober 2026 war er nicht in der Szene. F1, M und F11 wirken erst in der Partie; in der Referenz auch im Menü.
- **„Menü“ im HUD** fragt vor der Rückkehr (Dialog aus U3); Godot kehrt sofort zurück.
- **Kein Audio-Netzwerk** und keine Lobby-Chat-Nachrichten (waren nicht Teil des M4-Mindestumfangs).