diff --git a/SVSim.BattleEngine.Tests/HeadlessFixture.cs b/SVSim.BattleEngine.Tests/HeadlessFixture.cs index cda2131..1309baa 100644 --- a/SVSim.BattleEngine.Tests/HeadlessFixture.cs +++ b/SVSim.BattleEngine.Tests/HeadlessFixture.cs @@ -221,14 +221,13 @@ namespace SVSim.BattleEngine.Tests .SetValue(null, "headless-udid"); } - // Simple deterministic 40-card deck seed for multi-instance tests. The seed is unused for now - // (every slot is the same vanilla FollowerId), but takes a seed parameter so callers can vary - // it without API churn if/when we want a heterogeneous deck. Card 100011010 is loaded as part - // of EnsureProcessGlobals' HeadlessCardMaster.Load batch so SessionBattleEngine.Setup resolves - // each entry without re-loading. - public static long[] SampleDeck(int seed) + // Simple deterministic 40-card deck for multi-instance tests: every slot is the same vanilla + // FollowerId. Card 100011010 is loaded as part of EnsureProcessGlobals' HeadlessCardMaster.Load + // batch so SessionBattleEngine.Setup resolves each entry without re-loading. Kept a single + // shape — the multi-instance property being verified (per-session ambient isolation across + // parallel battles) is driven by distinct masterSeeds on the engines, not by deck variation. + public static long[] SampleDeck() { - var rng = new System.Random(seed); var deck = new long[40]; for (int i = 0; i < 40; i++) deck[i] = FollowerId; return deck; diff --git a/SVSim.BattleEngine.Tests/MultiInstanceEngineTests.cs b/SVSim.BattleEngine.Tests/MultiInstanceEngineTests.cs index a0fbb80..eb78bec 100644 --- a/SVSim.BattleEngine.Tests/MultiInstanceEngineTests.cs +++ b/SVSim.BattleEngine.Tests/MultiInstanceEngineTests.cs @@ -23,34 +23,38 @@ public class MultiInstanceEngineTests [Test] public async Task TwoBattles_ResolveIndependently_OnDifferentTasks() { - var deckA1 = HeadlessEngineEnv.SampleDeck(seed: 1); - var deckA2 = HeadlessEngineEnv.SampleDeck(seed: 2); - var deckB1 = HeadlessEngineEnv.SampleDeck(seed: 3); - var deckB2 = HeadlessEngineEnv.SampleDeck(seed: 4); - var engineA = new SessionBattleEngine(); var engineB = new SessionBattleEngine(); - engineA.Setup(masterSeed: 111, deckA1, deckA2, seatAClass: 1, seatBClass: 2); - engineB.Setup(masterSeed: 222, deckB1, deckB2, seatAClass: 5, seatBClass: 7); + engineA.Setup(masterSeed: 111, HeadlessEngineEnv.SampleDeck(), HeadlessEngineEnv.SampleDeck(), + seatAClass: 1, seatBClass: 2); + engineB.Setup(masterSeed: 222, HeadlessEngineEnv.SampleDeck(), HeadlessEngineEnv.SampleDeck(), + seatAClass: 5, seatBClass: 7); var taskA = Task.Run(() => DriveBasicTurns(engineA)); var taskB = Task.Run(() => DriveBasicTurns(engineB)); await Task.WhenAll(taskA, taskB); + // Pin the engines' post-Setup state to concrete starting values: LeaderLife=20 (InitLeaderLife's + // DefaultLeaderLife, applied by SessionBattleEngine.Setup), Pp=0 (pre-first-turn, no PP refill + // has run), HandCount=0 (Setup builds the deck/leader graph but doesn't deal an opening hand — + // mulligan/draw happens once a turn-start phase runs, which DriveBasicTurns doesn't trigger). + // Both engines must report the SAME starting state regardless of distinct masterSeeds, which is + // the cross-contamination property under test: ambient isolation means neither engine's reads + // can leak into the other's seat lookups. Assert.That(engineA.LeaderLife(true), Is.EqualTo(20)); Assert.That(engineB.LeaderLife(true), Is.EqualTo(20)); - Assert.That(engineA.Pp(true), Is.GreaterThanOrEqualTo(0)); - Assert.That(engineB.Pp(true), Is.GreaterThanOrEqualTo(0)); + Assert.That(engineA.Pp(true), Is.EqualTo(0)); + Assert.That(engineB.Pp(true), Is.EqualTo(0)); + Assert.That(engineA.HandCount(true), Is.EqualTo(0)); + Assert.That(engineB.HandCount(true), Is.EqualTo(0)); } [Test] - public async Task StressN_RandomDecks_BaselineMatches([Values(4, 8, 16)] int n) + public async Task StressN_BaselineMatches([Values(4, 8, 16)] int n) { var inputs = new (int seed, long[] deckA, long[] deckB)[n]; for (int i = 0; i < n; i++) - inputs[i] = (1000 + i, - HeadlessEngineEnv.SampleDeck(seed: 100 + i * 2), - HeadlessEngineEnv.SampleDeck(seed: 101 + i * 2)); + inputs[i] = (1000 + i, HeadlessEngineEnv.SampleDeck(), HeadlessEngineEnv.SampleDeck()); // Setup is process-globally serialized: a small set of decomp-origin static accumulators // (Wizard.LocalLog._lastTraceLogStringBuilder, etc.) is touched during BattleManagerBase ctor.