test(battle-engine M13): NewNetworkEmitBattle harness + OnEmit capture seam
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -343,6 +343,46 @@ namespace SVSim.BattleEngine.Tests
|
|||||||
return mgr;
|
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)
|
private static void SetField(object obj, string name, object value)
|
||||||
{
|
{
|
||||||
var f = obj.GetType().GetField(name,
|
var f = obj.GetType().GetField(name,
|
||||||
|
|||||||
@@ -21,5 +21,15 @@ namespace SVSim.BattleEngine.Tests
|
|||||||
Assert.That(mgr, Is.Not.Null);
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user