diff --git a/SVSim.BattleEngine.Tests/HeadlessFixture.cs b/SVSim.BattleEngine.Tests/HeadlessFixture.cs index 92db08f..9952e61 100644 --- a/SVSim.BattleEngine.Tests/HeadlessFixture.cs +++ b/SVSim.BattleEngine.Tests/HeadlessFixture.cs @@ -202,6 +202,19 @@ namespace SVSim.BattleEngine.Tests var dm = GameMgr.GetIns().GetDataMgr(); SetField(dm, "_playerCharaId", HeadlessMasterData.PlayerCharaId); SetField(dm, "_enemyCharaId", HeadlessMasterData.EnemyCharaId); + // NetworkBattleManagerBase.CreateBackgroundId() (M13) reads + // GameMgr.GetIns().GetNetworkUserInfoData().GetFieldId() when the RecoveryManager yields no + // bg id (NullRecoveryManager.BackGroundId == -1). In production RealTimeNetworkAgent seeds + // this NetworkUserInfoData at match start; the bare construction path leaves GameMgr's + // _netUser null (no lazy init, unlike the other GodObject getters). Seed a no-op instance + // whose _selfInfo carries just "fieldId" (GetFieldId reads _selfInfo["fieldId"]); field id 1 + // == ForestField, a valid background. Nothing here drives game state — it only satisfies the + // network mgr's background lookup so a single-battle-only SingleBattleMgr never needs. + var netUser = new NetworkUserInfoData(); + netUser.SetSelfInfo( + new System.Collections.Generic.Dictionary { ["fieldId"] = 1 }, + isWatchReplayRecovery: false); + GameMgr.GetIns().SetNetworkUserInfoData(netUser); _done = true; } diff --git a/SVSim.BattleEngine.Tests/NetworkMgrConstructionProbeTests.cs b/SVSim.BattleEngine.Tests/NetworkMgrConstructionProbeTests.cs new file mode 100644 index 0000000..18a5f2a --- /dev/null +++ b/SVSim.BattleEngine.Tests/NetworkMgrConstructionProbeTests.cs @@ -0,0 +1,25 @@ +using NUnit.Framework; +using SVSim.BattleEngine.Rng; +using Wizard.BattleMgr; + +namespace SVSim.BattleEngine.Tests +{ + // M13 step 1 (the M2 ConstructionProbe pattern): can a NetworkBattleManagerBase-derived mgr be + // built headless at all? NetworkBattleManagerSetup constructs NetworkTouchControl(this, + // _battleCamera, _backGround) + RegisterActionManager + OperateReceive — the largest new shim + // surface since M5's prefab path. Isolate "ctor runs" before any play is driven. + [TestFixture] + public class NetworkMgrConstructionProbeTests + { + [Test] + public void HeadlessNetworkBattleMgr_constructs_headless() + { + HeadlessEngineEnv.EnsureInitialized(); + Assert.DoesNotThrow(() => + { + var mgr = new HeadlessNetworkBattleMgr(new HeadlessContentsCreator()); + Assert.That(mgr, Is.Not.Null); + }); + } + } +} diff --git a/SVSim.BattleEngine/Rng/HeadlessNetworkBattleMgr.cs b/SVSim.BattleEngine/Rng/HeadlessNetworkBattleMgr.cs new file mode 100644 index 0000000..e5f5956 --- /dev/null +++ b/SVSim.BattleEngine/Rng/HeadlessNetworkBattleMgr.cs @@ -0,0 +1,37 @@ +using Wizard; +using Wizard.BattleMgr; + +namespace SVSim.BattleEngine.Rng +{ + // The headless authoritative NETWORK battle mgr — the emitting twin of HeadlessBattleMgr. Emission + // lives on NetworkBattleManagerBase (NetworkBattleSender's ctor demands one), so the M13 emit read + // needs this subclass; HeadlessBattleMgr : SingleBattleMgr cannot reach the sender. RNG overrides are + // identical to HeadlessBattleMgr (the same BattleManagerBase virtuals + RandomSourceBridge), so the + // M14 rand-list emit reuses this mgr unchanged. M13's deterministic card never exercises a roll. + public sealed class HeadlessNetworkBattleMgr : NetworkBattleManagerBase + { + private readonly IRandomSource _rng; + + public HeadlessNetworkBattleMgr(IBattleMgrContentsCreator contentsCreator, IRandomSource rng = null) + : base(contentsCreator) + { + _rng = rng ?? new SeededRandomSource(contentsCreator.RandomSeed); + } + + public override int StableRandom(int val) + { + double unit = _rng.NextUnit(); + randomResult = unit; + return RandomSourceBridge.Range(val, unit); + } + + public override double StableRandomDouble() + { + double unit = _rng.NextUnit(); + randomResult = unit; + return unit; + } + + public override int StableRandomOnlySelf(int val) => _rng.NextSelf(val); + } +}