using NUnit.Framework; namespace SVSim.BattleEngine.Tests { // Shared base for every network-emit test fixture (M13 EmitPathReadOracleTests, the // construction-probe's OnEmit seam test, and any M14+ network fixture to come). // // WHY this exists — the global-state leak it closes: // HeadlessEngineEnv.NewNetworkEmitBattle() mutates two PROCESS globals that no per-mgr // teardown reverts on its own: // - Wizard.ToolboxGame.RealTimeNetworkAgent: a non-null agent is injected so the engine's // NetworkBattleSender ToolboxGame.RealTimeNetworkAgent.* calls resolve (the capture seam). // - BattleManagerBase.IsForecast: the emit path needs this FALSE, so the harness flips it from // the EnsureInitialized default of TRUE. // HeadlessEngineEnv.EnsureInitialized() is _done-guarded — it sets IsForecast=true exactly once // and then NO-OPs forever after. So once a network-emit fixture leaves IsForecast=false behind, // nothing restores it: a later SOLO fixture (e.g. RandomDrawOracleTests) that assumes // IsForecast=true would silently run with IsForecast=false (un-suppressed VFX) and a stale // injected agent. Currently latent-benign, but a real hygiene gap M14's added network fixtures // could turn flaky. // // Deriving from this base means NUnit runs the base-class [TearDown] after EVERY test in the // derived fixture automatically, so the reset can never be forgotten when a new network-emit // fixture is added — inherit this, don't re-roll a local teardown. public abstract class NetworkEmitFixtureBase { [TearDown] public void ResetNetworkEmitGlobals() { // Clear the injected agent (the solo oracles don't read it, but clearing it is defensive, // mirroring RandomDrawOracleTests.ResetRandomDrawGate). Wizard.ToolboxGame.SetRealTimeNetworkBattle(null); // Restore the EnsureInitialized default. This is the load-bearing restore: every solo // oracle and EnsureInitialized assume IsForecast=TRUE, and EnsureInitialized only sets it // once (guarded), so without restoring it here a solo fixture running after a network-emit // fixture would see IsForecast=false and un-suppressed VFX. BattleManagerBase.IsForecast = true; } } }