# 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 `; 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): ` -luneSmoke ` 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).