feat(engine-ambient): delete static fallbacks; add MultiInstanceEngineTests
Step 8 (final) of multi-instancing migration. All per-battle statics now require a BattleAmbient scope — unwrapped writes throw InvalidOperationException (fail-fast forcing function). MultiInstanceEngineTests proves correctness: two parallel battles resolve independently, N=4/8/16 stress matches sequential baseline, GameMgr.GetIns throws without scope. Migration complete. EngineSessionGate gone. Suite fully green. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -89,14 +89,11 @@ internal static class EngineGlobalInit
|
||||
// Suppress VFX / take the virtual-battle resolution path (no live view layer).
|
||||
BattleManagerBase.IsForecast = true;
|
||||
|
||||
// The receive-conductor deal path runs under IsRecovery (SessionBattleEngine sets it after
|
||||
// construction) and reads Data.BattleRecoveryInfo.IsMulliganEnd in MulliganMgrBase.StartDeal
|
||||
// (line 43) — null by default -> NRE. Seed a no-op instance with IsMulliganEnd=false (the
|
||||
// default) so the deal returns its real parallel VFX rather than the mulligan-end short
|
||||
// circuit. GetUninitializedObject skips the JsonData ctor. Only when absent (coexistence).
|
||||
if (Data.BattleRecoveryInfo == null)
|
||||
Data.BattleRecoveryInfo =
|
||||
(BattleRecoveryInfo)FormatterServices.GetUninitializedObject(typeof(BattleRecoveryInfo));
|
||||
// Post-Task-8: BattleRecoveryInfo lives on the per-session BattleAmbientContext (RecoveryInfo,
|
||||
// pre-seeded with an uninitialized no-op instance in SessionBattleEngine's field initializer).
|
||||
// Each session owns its own (IsMulliganEnd=false default), so the MulliganMgrBase.StartDeal
|
||||
// read resolves per-session — the historical process-global Data.BattleRecoveryInfo write that
|
||||
// lived here was dead the moment the ambient seam landed (reviewer-flagged in Task 7).
|
||||
|
||||
// --- static CardMaster (full cards.json) ----------------------------------------------
|
||||
// ALWAYS rebuild + re-inject the FULL master. We must not defer to a possibly-thin
|
||||
@@ -124,20 +121,14 @@ internal static class EngineGlobalInit
|
||||
udidField.SetValue(null, "headless-udid");
|
||||
|
||||
// --- Cute.Certification.viewer_id ------------------------------------------------------
|
||||
// The IsRecovery target parse (NetworkBattleReceiver.CreateTargetList, isWatch branch) derives
|
||||
// a target's owner from `vid != PlayerStaticData.UserViewerID`, where
|
||||
// UserViewerID => Certification.ViewerId, whose getter LAZILY reads
|
||||
// Toolbox.SavedataManager.GetInt("VIEWER_ID") when its backing field is 0 — and that savedata
|
||||
// read throws headless. The exception is SWALLOWED by ConvertReceiveDataToMakeData's blanket
|
||||
// catch, which (with the node's checkBreakData:false ingest) silently drops the parsed
|
||||
// targetList, leaving an attack/evolve with an EMPTY target list -> the action throws on
|
||||
// targetList[0]. Seed the backing field with a stable nonzero id so the getter short-circuits.
|
||||
// It defines the engine's "player" perspective: a target vid == ThisViewerId resolves on
|
||||
// BattlePlayer (engine seat A), vid != it on BattleEnemy (seat B). Only set when 0 (coexistence).
|
||||
var viewerIdField = typeof(Certification).GetField("_viewerIdFallback",
|
||||
BindingFlags.Static | BindingFlags.NonPublic)!;
|
||||
if ((int)(viewerIdField.GetValue(null) ?? 0) == 0)
|
||||
viewerIdField.SetValue(null, ThisViewerId);
|
||||
// Post-Task-8: viewer id lives on the per-session BattleAmbientContext (ViewerId, init-only).
|
||||
// SessionBattleEngine seeds it from ThisViewerId in its field initializer and enters the
|
||||
// ambient on every Setup/Receive — so the engine's Certification.ViewerId getter
|
||||
// (BattleAmbient.Require().ViewerId) always resolves cleanly when the engine code is
|
||||
// reached. The reflection write that used to seed `Certification._viewerIdFallback` is now
|
||||
// dead (the static is deleted). Left as a comment-only marker so the design intent (this
|
||||
// viewer id defines the engine's "player" perspective for the IsRecovery target parse)
|
||||
// stays discoverable from the global init path.
|
||||
|
||||
_done = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user