test(engine-ambient): tighten MultiInstanceEngineTests post-setup assertions
Replace trivially-true Pp>=0 with concrete post-Setup pins (LeaderLife=20, Pp=0, HandCount=3). Drop the unused seed parameter from SampleDeck - every call already returned the same vanilla deck, and the StressN test name 'Random Decks' overpromised. The cross-contamination property the test pins (parallel LeaderLife[] equals sequential LeaderLife[]) holds with identical decks + distinct masterSeeds, which is what's actually being verified. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -221,14 +221,13 @@ namespace SVSim.BattleEngine.Tests
|
|||||||
.SetValue(null, "headless-udid");
|
.SetValue(null, "headless-udid");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple deterministic 40-card deck seed for multi-instance tests. The seed is unused for now
|
// Simple deterministic 40-card deck for multi-instance tests: every slot is the same vanilla
|
||||||
// (every slot is the same vanilla FollowerId), but takes a seed parameter so callers can vary
|
// FollowerId. Card 100011010 is loaded as part of EnsureProcessGlobals' HeadlessCardMaster.Load
|
||||||
// it without API churn if/when we want a heterogeneous deck. Card 100011010 is loaded as part
|
// batch so SessionBattleEngine.Setup resolves each entry without re-loading. Kept a single
|
||||||
// of EnsureProcessGlobals' HeadlessCardMaster.Load batch so SessionBattleEngine.Setup resolves
|
// shape — the multi-instance property being verified (per-session ambient isolation across
|
||||||
// each entry without re-loading.
|
// parallel battles) is driven by distinct masterSeeds on the engines, not by deck variation.
|
||||||
public static long[] SampleDeck(int seed)
|
public static long[] SampleDeck()
|
||||||
{
|
{
|
||||||
var rng = new System.Random(seed);
|
|
||||||
var deck = new long[40];
|
var deck = new long[40];
|
||||||
for (int i = 0; i < 40; i++) deck[i] = FollowerId;
|
for (int i = 0; i < 40; i++) deck[i] = FollowerId;
|
||||||
return deck;
|
return deck;
|
||||||
|
|||||||
@@ -23,34 +23,38 @@ public class MultiInstanceEngineTests
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task TwoBattles_ResolveIndependently_OnDifferentTasks()
|
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 engineA = new SessionBattleEngine();
|
||||||
var engineB = new SessionBattleEngine();
|
var engineB = new SessionBattleEngine();
|
||||||
engineA.Setup(masterSeed: 111, deckA1, deckA2, seatAClass: 1, seatBClass: 2);
|
engineA.Setup(masterSeed: 111, HeadlessEngineEnv.SampleDeck(), HeadlessEngineEnv.SampleDeck(),
|
||||||
engineB.Setup(masterSeed: 222, deckB1, deckB2, seatAClass: 5, seatBClass: 7);
|
seatAClass: 1, seatBClass: 2);
|
||||||
|
engineB.Setup(masterSeed: 222, HeadlessEngineEnv.SampleDeck(), HeadlessEngineEnv.SampleDeck(),
|
||||||
|
seatAClass: 5, seatBClass: 7);
|
||||||
|
|
||||||
var taskA = Task.Run(() => DriveBasicTurns(engineA));
|
var taskA = Task.Run(() => DriveBasicTurns(engineA));
|
||||||
var taskB = Task.Run(() => DriveBasicTurns(engineB));
|
var taskB = Task.Run(() => DriveBasicTurns(engineB));
|
||||||
await Task.WhenAll(taskA, taskB);
|
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(engineA.LeaderLife(true), Is.EqualTo(20));
|
||||||
Assert.That(engineB.LeaderLife(true), Is.EqualTo(20));
|
Assert.That(engineB.LeaderLife(true), Is.EqualTo(20));
|
||||||
Assert.That(engineA.Pp(true), Is.GreaterThanOrEqualTo(0));
|
Assert.That(engineA.Pp(true), Is.EqualTo(0));
|
||||||
Assert.That(engineB.Pp(true), Is.GreaterThanOrEqualTo(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]
|
[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];
|
var inputs = new (int seed, long[] deckA, long[] deckB)[n];
|
||||||
for (int i = 0; i < n; i++)
|
for (int i = 0; i < n; i++)
|
||||||
inputs[i] = (1000 + i,
|
inputs[i] = (1000 + i, HeadlessEngineEnv.SampleDeck(), HeadlessEngineEnv.SampleDeck());
|
||||||
HeadlessEngineEnv.SampleDeck(seed: 100 + i * 2),
|
|
||||||
HeadlessEngineEnv.SampleDeck(seed: 101 + i * 2));
|
|
||||||
|
|
||||||
// Setup is process-globally serialized: a small set of decomp-origin static accumulators
|
// Setup is process-globally serialized: a small set of decomp-origin static accumulators
|
||||||
// (Wizard.LocalLog._lastTraceLogStringBuilder, etc.) is touched during BattleManagerBase ctor.
|
// (Wizard.LocalLog._lastTraceLogStringBuilder, etc.) is touched during BattleManagerBase ctor.
|
||||||
|
|||||||
Reference in New Issue
Block a user