champions-of-luneraria/docs/port/U4-NETWORK.md
Martin Stickelbroeck 07e536518f Doku: U4-Nachtrag in U5.3 zusammengeführt (c62a1f4)
PORT-QA, U4-NETWORK und PORT-STATUS nennen den Merge, die neu gebaute Szene
und die Prüfung auf dem zusammengeführten Stand; Bericht des Player-Selbsttests
unter docs/port/logs/u4/smoke-macos-u5.3.json.

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

132 lines
14 KiB
Markdown
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.

# 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` (`d590fde`), aufgesetzt auf `luneraria/u5.2` (`1b31f9b`, mit dem U3-Nachtrag). Am 8. Oktober in `luneraria/u5.3` zusammengeführt (`c62a1f4`). Einziger Konflikt war die Szene; sie ist mit dem zusammengeführten Builder neu gebaut (U5.3-Welt plus `LuneNetPlay`, sonst gleiche Verweise). Prüfung dort: EditMode 29/29, PlayMode 14/14, Fingerabdrücke unverändert, Player-Selbsttest 26/26 (`logs/u4/smoke-macos-u5.3.json`).
**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`. Im Bestätigungslauf mit neu gebautem Player wieder 26/26 |
| EditMode | 29/29 |
| PlayMode | 14/14 (13 + `StartScreenTests`), Verify A–I 718/0. Bestätigungslauf auf dem Endstand (`acf2572`) am 8. Oktober in einem Zug, 307 s |
| 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 (Seed 9 ab Runde 21, bis 116 Einheiten). A/B gegen U5.2 unter gleichen Bedingungen, siehe unten |
**Leistung (A/B):** Je drei Benchmark-Läufe abwechselnd mit einem U5.2-Player (`1b31f9b`, ohne Nachtrag) und dem Nachtrag-Player (`acf2572`); zuvor ein verworfener Aufwärmlauf, weil macOS neue Programme beim ersten Start prüft. macOS-Player, Release, Metal, 1920×1080, VSync aus, Mac M5 bei normaler Desktop-Nutzung. Rohdaten `logs/u4/benchmark-ab/`.
| Player | Übersicht (Zoom 64) | Mid nah (Zoom 22) | HUD-Skripte je Bild | Einheiten |
| --- | --- | --- | --- | --- |
| U5.2 `1b31f9b` | 112,5 / 114,5 / 112,9 fps | 146,7 / 150,0 / 147,7 fps | 0,088–0,103 ms | 116 / 63 |
| U4-Nachtrag `acf2572` | 111,0 / 113,9 / 113,9 fps | 145,2 / 148,9 / 145,4 fps | 0,090–0,109 ms | 116 / 63 |
Die Mittelwerte liegen 0,3 % (Übersicht) und 1,1 % (Mid nah) auseinander. Das ist weniger als die Streuung zwischen den Läufen eines Players (bis 2,6 %). Im Benchmark baut `LuneNetPlay` kein Menü und hat keine Bildarbeit. Beide Player liegen unter den U5.2-Zahlen vom 2. Oktober (133 / 180 fps). Der Abstand trifft beide gleich und hängt an der Umgebung. Heute brauchen beide Player in der Übersicht länger als damals: Hauptthread 3,6–3,7 statt 3,1 ms, Render-Thread 4,2–4,5 statt 3,7 ms, Present-Wartezeit 5,3 statt 4,4 ms.
Die nächtlichen Wiederholungsläufe vom 7. auf den 8. Oktober zählen nicht. Der Mac war zugeklappt („Thermal Emergency Sleep“ um 23:35) und wachte nur alle 15 Minuten kurz für Wartung auf. Dabei rissen Echtzeitgrenzen: Bildratenläufe über 600 s, der 120-s-Wächter des Selbsttests und eine Kameraprüfung 0,6 s nach einem Lane-Klick. Der Bestätigungslauf bei geöffnetem Mac ersetzt sie.
## 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).