From 25e9ae9573352326548c2f7e57c60775b3348a9d Mon Sep 17 00:00:00 2001 From: gamer147 Date: Sat, 6 Jun 2026 12:00:04 -0400 Subject: [PATCH] test(battle-engine M13): NewNetworkEmitBattle harness + OnEmit capture seam Co-Authored-By: Claude Opus 4.8 --- SVSim.BattleEngine.Tests/HeadlessFixture.cs | 40 +++++++++++++++++++ .../NetworkMgrConstructionProbeTests.cs | 10 +++++ 2 files changed, 50 insertions(+) diff --git a/SVSim.BattleEngine.Tests/HeadlessFixture.cs b/SVSim.BattleEngine.Tests/HeadlessFixture.cs index 19837fb..0d49ab0 100644 --- a/SVSim.BattleEngine.Tests/HeadlessFixture.cs +++ b/SVSim.BattleEngine.Tests/HeadlessFixture.cs @@ -343,6 +343,46 @@ namespace SVSim.BattleEngine.Tests return mgr; } + // M13 emit-path read. Builds a HeadlessNetworkBattleMgr (the emitting twin of the + // HeadlessBattleMgr NewAuthoritativeBattle returns) and stands up the OnEmit capture seam: the + // engine's own RealTimeNetworkAgent.OnEmit event (RealTimeNetworkAgent.cs:1268) fires the played + // URI before both emit guards, so capturing it needs no Engine/shim edit — just an injected agent. + // Returns (mgr, emitted-URI list). The caller seeds the hand and drives mgr.OperateMgr.PlayCard. + public static (HeadlessNetworkBattleMgr mgr, System.Collections.Generic.List emitted) + NewNetworkEmitBattle(IRandomSource rng = null) + { + EnsureInitialized(); // sets IsForecast = true among other globals + var mgr = new HeadlessNetworkBattleMgr(new HeadlessContentsCreator(), rng); + mgr.IsRecovery = true; // collapse wait delays to 0 (F1) + + var player = mgr.BattlePlayer; + var enemy = mgr.BattleEnemy; + SetField(player, "_opponentBattlePlayer", enemy); + SetField(enemy, "_opponentBattlePlayer", player); + player.IsSelfTurn = true; + enemy.IsSelfTurn = false; + + InitLeaderLife(mgr); // a 0-life leader reads as game-over and blocks plays + InitCardTemplates(mgr); // play/draw VFX touches the card view layer + + // Inject a headless RealTimeNetworkAgent so NetworkBattleSender's ToolboxGame.RealTimeNetworkAgent + // .* calls resolve, and subscribe OnEmit. GetUninitializedObject skips the MonoBehaviour Awake. + var agent = (RealTimeNetworkAgent)System.Runtime.Serialization.FormatterServices + .GetUninitializedObject(typeof(RealTimeNetworkAgent)); + // CurrentMatchingStatus has a protected setter; seed it non-Disconnected so EmitMsgPack does not + // early-return at RealTimeNetworkAgent.cs:1272 (needed only for the best-effort payload read, Task 4; + // OnEmit fires regardless). The default on the uninitialized object is OffLine (0), which clears the + // SetCurrentMatchingStatus guards; the only side effect is a static-StringBuilder trace log, so the + // public setter runs cleanly headless. Prepared (50) is the real enum member (RealTimeNetworkAgent.cs:35). + agent.SetCurrentMatchingStatus(RealTimeNetworkAgent.MatchingStatus.Prepared); + + var emitted = new System.Collections.Generic.List(); + agent.OnEmit += uri => emitted.Add(uri); + Wizard.ToolboxGame.SetRealTimeNetworkBattle(agent); + + return (mgr, emitted); + } + private static void SetField(object obj, string name, object value) { var f = obj.GetType().GetField(name, diff --git a/SVSim.BattleEngine.Tests/NetworkMgrConstructionProbeTests.cs b/SVSim.BattleEngine.Tests/NetworkMgrConstructionProbeTests.cs index 18a5f2a..28873e8 100644 --- a/SVSim.BattleEngine.Tests/NetworkMgrConstructionProbeTests.cs +++ b/SVSim.BattleEngine.Tests/NetworkMgrConstructionProbeTests.cs @@ -21,5 +21,15 @@ namespace SVSim.BattleEngine.Tests Assert.That(mgr, Is.Not.Null); }); } + + [Test] + public void OnEmit_capture_seam_is_wired_via_injected_agent() + { + var (mgr, emitted) = HeadlessEngineEnv.NewNetworkEmitBattle(); + Assert.That(mgr, Is.Not.Null); + Assert.That(Wizard.ToolboxGame.RealTimeNetworkAgent, Is.Not.Null, + "agent must be injected so NetworkBattleSender's ToolboxGame.RealTimeNetworkAgent.* calls resolve"); + Assert.That(emitted, Is.Empty, "no emit yet — only the seam is wired"); + } } }