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>
89 lines
3.2 KiB
C#
89 lines
3.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace Luneraria.Core
|
|
{
|
|
/// <summary>
|
|
/// In-process transport for two (or more) LuneSession instances (the loopback of
|
|
/// tools/luneraria/net_pair.gd without processes). Messages are delivered synchronously in
|
|
/// send order; frames go through the same path with SendFrame. Peer 1 is the host. A node
|
|
/// implements ILuneNetLink for exactly one peer and routes through the hub.
|
|
/// </summary>
|
|
public sealed class LuneLoopback
|
|
{
|
|
sealed class Node : ILuneNetLink
|
|
{
|
|
readonly LuneLoopback _hub;
|
|
readonly int _peer;
|
|
public LuneSession Session;
|
|
|
|
public Node(LuneLoopback hub, int peer)
|
|
{
|
|
_hub = hub;
|
|
_peer = peer;
|
|
}
|
|
|
|
public void Send(int peer, string kind, object payload)
|
|
{
|
|
_hub.Deliver(_peer, peer, kind, payload);
|
|
}
|
|
|
|
public void SendFrame(int peer, int serial, double elapsed, float[] units, float[] buildingHp)
|
|
{
|
|
_hub.DeliverFrame(_peer, peer, serial, elapsed, units, buildingHp);
|
|
}
|
|
}
|
|
|
|
readonly Dictionary<int, Node> _nodes = new Dictionary<int, Node>();
|
|
/// <summary>Every message that left a peer (tests, diagnostics).</summary>
|
|
public readonly List<string> Log = new List<string>();
|
|
|
|
/// <summary>Attaches a session under a peer id and returns the session for chaining.</summary>
|
|
public LuneSession Attach(int peer, LuneSession session)
|
|
{
|
|
var node = new Node(this, peer) { Session = session };
|
|
_nodes[peer] = node;
|
|
session.Link = node;
|
|
return session;
|
|
}
|
|
|
|
/// <summary>Drops a peer and tells the authority (bot takeover, never a core loss).</summary>
|
|
public void Detach(int peer)
|
|
{
|
|
Node node;
|
|
if (!_nodes.TryGetValue(peer, out node))
|
|
return;
|
|
_nodes.Remove(peer);
|
|
if (node.Session != null)
|
|
{
|
|
node.Session.Link = null;
|
|
node.Session.Connected = false;
|
|
}
|
|
foreach (Node other in _nodes.Values)
|
|
{
|
|
if (other.Session != null && other.Session.IsAuthority())
|
|
other.Session.OnPeerDisconnected(peer);
|
|
}
|
|
}
|
|
|
|
/// <summary>True while the hub still knows the peer.</summary>
|
|
public bool Has(int peer) { return _nodes.ContainsKey(peer); }
|
|
|
|
void Deliver(int from, int to, string kind, object payload)
|
|
{
|
|
Log.Add(from + "→" + to + " " + kind);
|
|
Node node;
|
|
if (!_nodes.TryGetValue(to, out node) || node.Session == null)
|
|
return;
|
|
node.Session.OnMessage(from, kind, payload);
|
|
}
|
|
|
|
void DeliverFrame(int from, int to, int serial, double elapsed, float[] units, float[] buildingHp)
|
|
{
|
|
Node node;
|
|
if (!_nodes.TryGetValue(to, out node) || node.Session == null)
|
|
return;
|
|
node.Session.OnFrame(from, serial, elapsed, units, buildingHp);
|
|
}
|
|
}
|
|
}
|