test(battle-engine M13): NewNetworkEmitBattle harness + OnEmit capture seam

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-06 12:00:04 -04:00
parent 6b2c825eb8
commit 25e9ae9573
2 changed files with 50 additions and 0 deletions

View File

@@ -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<NetworkBattleDefine.NetworkBattleURI> 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<NetworkBattleDefine.NetworkBattleURI>();
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,

View File

@@ -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");
}
}
}