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:
@@ -0,0 +1,77 @@
|
||||
Multi-instancing migration (Step 8 — forcing function): delete the static per-battle fallbacks
|
||||
the earlier ambient-* patches added as a coexistence shim. With the fallbacks gone, any unwrapped
|
||||
engine call into IsForecast/IsRandomDraw fails fast (BattleAmbient.Require() throws
|
||||
InvalidOperationException); GetIns soft-returns null when no scope is active so engine code
|
||||
that patterns `GetIns()?.Foo ?? default` keeps composing (per design — strict for setters /
|
||||
load-bearing reads, soft for the GetIns null-tolerant chain). Supersedes the intermediate
|
||||
.ambient-isforecast-israndomdraw + .ambient-main-getins patches (kept for the audit trail).
|
||||
|
||||
Semantics chosen per accessor:
|
||||
- IsForecast : get/set both BattleAmbient.Require() — strict (must be in a scope)
|
||||
- IsRandomDraw : get/set both BattleAmbient.Require() — strict
|
||||
- GetIns() : Current?.Mgr — soft (null when no scope; preserves engine `?.` patterns)
|
||||
- ctor : `_mainFallback = this` line deleted entirely
|
||||
- DisposeManager: `_mainFallback = null` line deleted entirely
|
||||
|
||||
In-file edits:
|
||||
- line ~413 declaration `private static BattleManagerBase _mainFallback;` DELETED
|
||||
- lines ~415-432 the four fallback statics + their if-else fallback branches DELETED,
|
||||
replaced with strict get/set via BattleAmbient.Require()
|
||||
- line ~727 GetIns() body -> Current?.Mgr (no fallback)
|
||||
- line ~732 ctor `_mainFallback = this` DELETED (no longer needed; ambient.Mgr is published
|
||||
explicitly from SessionBattleEngine.SetupInternal after construction)
|
||||
- line ~1491 dispose `_mainFallback = null` DELETED (the matching scope drop replaces it)
|
||||
|
||||
--- Engine/BattleManagerBase.cs (~lines 413-433)
|
||||
- private static BattleManagerBase _mainFallback;
|
||||
-
|
||||
- private static bool _isRandomDrawFallback = false;
|
||||
- public static bool IsRandomDraw {
|
||||
- get => SVSim.BattleEngine.Ambient.BattleAmbient.Current?.IsRandomDraw ?? _isRandomDrawFallback;
|
||||
- set {
|
||||
- var c = SVSim.BattleEngine.Ambient.BattleAmbient.Current;
|
||||
- if (c != null) c.IsRandomDraw = value;
|
||||
- else _isRandomDrawFallback = value;
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- private static bool _isForecastFallback = false;
|
||||
- public static bool IsForecast {
|
||||
- get => SVSim.BattleEngine.Ambient.BattleAmbient.Current?.IsForecast ?? _isForecastFallback;
|
||||
- set {
|
||||
- var c = SVSim.BattleEngine.Ambient.BattleAmbient.Current;
|
||||
- if (c != null) c.IsForecast = value;
|
||||
- else _isForecastFallback = value;
|
||||
- }
|
||||
- }
|
||||
+ public static bool IsRandomDraw {
|
||||
+ get => SVSim.BattleEngine.Ambient.BattleAmbient.Require().IsRandomDraw;
|
||||
+ set => SVSim.BattleEngine.Ambient.BattleAmbient.Require().IsRandomDraw = value;
|
||||
+ }
|
||||
+
|
||||
+ public static bool IsForecast {
|
||||
+ get => SVSim.BattleEngine.Ambient.BattleAmbient.Require().IsForecast;
|
||||
+ set => SVSim.BattleEngine.Ambient.BattleAmbient.Require().IsForecast = value;
|
||||
+ }
|
||||
|
||||
--- Engine/BattleManagerBase.cs (~line 727)
|
||||
public static BattleManagerBase GetIns()
|
||||
{
|
||||
- return SVSim.BattleEngine.Ambient.BattleAmbient.Current?.Mgr ?? _mainFallback;
|
||||
+ // Soft read: returns null when no scope is active, preserving engine call sites that
|
||||
+ // pattern `GetIns()?.Foo ?? default`. Inside a scope, returns the scope's Mgr (which may
|
||||
+ // itself be null mid-Setup — see SessionBattleEngine.SetupInternal, which publishes Mgr
|
||||
+ // before WireMulliganPhase).
|
||||
+ return SVSim.BattleEngine.Ambient.BattleAmbient.Current?.Mgr;
|
||||
}
|
||||
|
||||
--- Engine/BattleManagerBase.cs (~line 732)
|
||||
protected BattleManagerBase(IBattleMgrContentsCreator contentsCreator)
|
||||
{
|
||||
- _mainFallback = this;
|
||||
BattleLifeTimeSharedObject = new BattleLifeTimeSharedObject();
|
||||
|
||||
--- Engine/BattleManagerBase.cs (~line 1491)
|
||||
GameMgr.GetIns().GetPrefabMgr().AllUnLoad();
|
||||
- _mainFallback = null;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
Multi-instancing migration (Step 8 — forcing function): delete the static viewer-id fallback the
|
||||
earlier .ambient-viewerid patch added as a coexistence shim. ViewerId is now strictly per-session
|
||||
ambient (BattleAmbient.Require().ViewerId). Supersedes the intermediate .ambient-viewerid patch
|
||||
(kept for the audit trail).
|
||||
|
||||
Semantics:
|
||||
- get : BattleAmbient.Require().ViewerId — strict (throws when no scope is active)
|
||||
- set : NO-OP. The historical caller (SavedataManager.SetInt + Save) is the client process's
|
||||
persistent-storage path; in the headless multi-instance world the viewer id flows from the
|
||||
session ctx (BattleAmbientContext.ViewerId, init-only at scope entry — see
|
||||
SessionBattleEngine's field initializer, which seeds it from EngineGlobalInit.ThisViewerId).
|
||||
Making the setter strict (Require()) would also work but degrades to a write that can't land
|
||||
(init-only) — a comment-documented no-op is clearer about why the setter went dead.
|
||||
|
||||
Related (SVSim.BattleNode/Sessions/Engine/EngineGlobalInit.cs): the reflection write that seeded
|
||||
`Certification._viewerIdFallback` is now dead — the static is deleted. Replaced with a comment-
|
||||
only marker so the design intent (ThisViewerId defines the engine's "player" perspective for the
|
||||
IsRecovery target parse) stays discoverable from the global init path.
|
||||
|
||||
InitializeFileds(): the now-dead `_viewerIdFallback = 0;` line is also deleted (the line above and
|
||||
below — SavedataManager.SetInt("VIEWER_ID", 0) — stay).
|
||||
|
||||
In-file edits:
|
||||
- line ~19 declaration `private static int _viewerIdFallback;` DELETED
|
||||
- lines ~41-65 the ambient+fallback get/set body REPLACED with strict get + no-op set
|
||||
- line ~155 `_viewerIdFallback = 0;` inside InitializeFileds DELETED
|
||||
|
||||
--- Engine/Cute/Certification.cs (~line 19)
|
||||
private static string udid;
|
||||
-
|
||||
- private static int _viewerIdFallback;
|
||||
-
|
||||
private static int short_udid;
|
||||
|
||||
--- Engine/Cute/Certification.cs (~lines 41-65)
|
||||
public static int ViewerId
|
||||
{
|
||||
- get
|
||||
- {
|
||||
- var c = SVSim.BattleEngine.Ambient.BattleAmbient.Current;
|
||||
- if (c != null) return c.ViewerId;
|
||||
- if (_viewerIdFallback == 0)
|
||||
- {
|
||||
- _viewerIdFallback = Toolbox.SavedataManager.GetInt("VIEWER_ID");
|
||||
- }
|
||||
- return _viewerIdFallback;
|
||||
- }
|
||||
- set
|
||||
- {
|
||||
- var c = SVSim.BattleEngine.Ambient.BattleAmbient.Current;
|
||||
- if (c != null)
|
||||
- {
|
||||
- // Inside a scope, ViewerId is fixed at scope entry — swallow the write.
|
||||
- return;
|
||||
- }
|
||||
- Toolbox.SavedataManager.SetInt("VIEWER_ID", value);
|
||||
- Toolbox.SavedataManager.Save();
|
||||
- _viewerIdFallback = value;
|
||||
- }
|
||||
+ // Post-Task-8: strictly ambient. The historical SavedataManager-backed lazy decode was the
|
||||
+ // client process's single-viewer-id source; in the headless multi-instance world the viewer
|
||||
+ // id MUST come from the per-session ambient context. Setter is a no-op (BattleAmbientContext
|
||||
+ // .ViewerId is `init`-only — fixed at scope entry per design — and the historical caller
|
||||
+ // (SavedataManager.SetInt + Save) is dead in the server world).
|
||||
+ get => SVSim.BattleEngine.Ambient.BattleAmbient.Require().ViewerId;
|
||||
+ set { /* ambient ViewerId is init-only; SavedataManager path is dead headless */ }
|
||||
}
|
||||
|
||||
--- Engine/Cute/Certification.cs (~line 155)
|
||||
sessionId = null;
|
||||
udid = null;
|
||||
- _viewerIdFallback = 0;
|
||||
short_udid = 0;
|
||||
@@ -0,0 +1,43 @@
|
||||
Multi-instancing migration (Step 8 — forcing function): delete the static BattleRecoveryInfo
|
||||
fallback the earlier .ambient-recoveryinfo patch added as a coexistence shim. BattleRecoveryInfo
|
||||
is now strictly per-session ambient. Supersedes the intermediate .ambient-recoveryinfo patch
|
||||
(kept for the audit trail).
|
||||
|
||||
Semantics:
|
||||
- BattleRecoveryInfo (getter) : Current?.RecoveryInfo — soft (null when no scope). The
|
||||
MulliganMgrBase.StartDeal call site reads this with a null-tolerant pattern, so a null
|
||||
degrade is the historical fallback. Inside a scope, returns the per-session RecoveryInfo
|
||||
(SessionBattleEngine pre-seeds an uninitialized BattleRecoveryInfo on its ctx field
|
||||
initializer).
|
||||
- BattleRecoveryInfo (setter) : BattleAmbient.Require().RecoveryInfo = value — strict
|
||||
(writes must land on the per-session ctx; forcing function).
|
||||
|
||||
Related (SVSim.BattleNode/Sessions/Engine/EngineGlobalInit.cs): the dead `Data.BattleRecoveryInfo
|
||||
= ...` write inside the `_done` lock block is also deleted (flagged by Task 7 reviewer). The
|
||||
per-session ctx pre-seeds RecoveryInfo, so the process-global write was dead the moment the
|
||||
ambient seam landed.
|
||||
|
||||
In-file edits:
|
||||
- line ~179 declaration `private static BattleRecoveryInfo _battleRecoveryInfoFallback;` DELETED
|
||||
- lines ~180-189 the ambient+fallback get/set body REPLACED with strict semantics
|
||||
|
||||
--- Engine/Wizard/Data.cs (~lines 179-189)
|
||||
- private static BattleRecoveryInfo _battleRecoveryInfoFallback;
|
||||
public static BattleRecoveryInfo BattleRecoveryInfo
|
||||
{
|
||||
- get => SVSim.BattleEngine.Ambient.BattleAmbient.Current?.RecoveryInfo ?? _battleRecoveryInfoFallback;
|
||||
- set
|
||||
- {
|
||||
- var c = SVSim.BattleEngine.Ambient.BattleAmbient.Current;
|
||||
- if (c != null) c.RecoveryInfo = value;
|
||||
- else _battleRecoveryInfoFallback = value;
|
||||
- }
|
||||
+ // Soft read: returns null when no scope is active. The MulliganMgrBase.StartDeal call site
|
||||
+ // reads this with a null-tolerant ??=-style pattern, so a null degrade is the historical
|
||||
+ // fallback. Inside a scope, returns the per-session RecoveryInfo (SessionBattleEngine
|
||||
+ // pre-seeds an uninitialized BattleRecoveryInfo on its ctx field initializer).
|
||||
+ get => SVSim.BattleEngine.Ambient.BattleAmbient.Current?.RecoveryInfo;
|
||||
+ // Strict setter: writes must land on the per-session ctx. No historical production caller
|
||||
+ // writes this outside a scope; an unwrapped write now fails fast (forcing function).
|
||||
+ set => SVSim.BattleEngine.Ambient.BattleAmbient.Require().RecoveryInfo = value;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
Multi-instancing migration (Step 8 — forcing function): delete the static RealTimeNetworkAgent
|
||||
fallback the earlier .ambient-rtna patch added as a coexistence shim. RealTimeNetworkAgent is now
|
||||
strictly per-session ambient. Supersedes the intermediate .ambient-rtna patch (kept for the audit
|
||||
trail).
|
||||
|
||||
Semantics:
|
||||
- RealTimeNetworkAgent (getter) : Current?.NetworkAgent — soft (null when no scope).
|
||||
Engine code reads this via `ToolboxGame.RealTimeNetworkAgent?.Foo`-style patterns (network
|
||||
send paths gated on a non-null agent), so a null on the unwrapped path is the correct
|
||||
degrade — not a throw.
|
||||
- SetRealTimeNetworkBattle : BattleAmbient.Require().NetworkAgent = agent — strict (writes must
|
||||
land on the per-session ctx; forcing function).
|
||||
- DestroyNetworkAgent : reads Current, destroys, nulls the ambient NetworkAgent in-place.
|
||||
Outside a scope is a no-op (nothing to destroy in the ambient world).
|
||||
|
||||
In-file edits:
|
||||
- line ~28 declaration `private static RealTimeNetworkAgent _realTimeNetworkAgentFallback;` DELETED
|
||||
- lines ~29-32 getter body no fallback chain
|
||||
- lines ~61-66 Set body Require() (strict)
|
||||
- lines ~68-78 Destroy body Current-based; no fallback branch
|
||||
|
||||
--- Engine/Wizard/ToolboxGame.cs (~lines 28-32)
|
||||
- private static RealTimeNetworkAgent _realTimeNetworkAgentFallback;
|
||||
public static RealTimeNetworkAgent RealTimeNetworkAgent
|
||||
{
|
||||
- get => SVSim.BattleEngine.Ambient.BattleAmbient.Current?.NetworkAgent ?? _realTimeNetworkAgentFallback;
|
||||
+ // Soft read: returns null when no scope is active, mirroring GetIns. Engine code reads
|
||||
+ // this via `ToolboxGame.RealTimeNetworkAgent?.Foo`-style patterns (network-send paths
|
||||
+ // gated on a non-null agent), so a null on the unwrapped path is the correct degrade —
|
||||
+ // not a throw.
|
||||
+ get => SVSim.BattleEngine.Ambient.BattleAmbient.Current?.NetworkAgent;
|
||||
}
|
||||
|
||||
--- Engine/Wizard/ToolboxGame.cs (~lines 61-78)
|
||||
public static void SetRealTimeNetworkBattle(RealTimeNetworkAgent agent)
|
||||
{
|
||||
- var c = SVSim.BattleEngine.Ambient.BattleAmbient.Current;
|
||||
- if (c != null) c.NetworkAgent = agent;
|
||||
- else _realTimeNetworkAgentFallback = agent;
|
||||
+ // Strict: must be inside a scope to set the per-session agent. Forcing-function — any
|
||||
+ // historical unwrapped caller (no production callsite remains) now fails fast.
|
||||
+ SVSim.BattleEngine.Ambient.BattleAmbient.Require().NetworkAgent = agent;
|
||||
}
|
||||
|
||||
public static void DestroyNetworkAgent()
|
||||
{
|
||||
var c = SVSim.BattleEngine.Ambient.BattleAmbient.Current;
|
||||
- var current = c?.NetworkAgent ?? _realTimeNetworkAgentFallback;
|
||||
+ var current = c?.NetworkAgent;
|
||||
if (current != null)
|
||||
{
|
||||
Object.DestroyImmediate(current.gameObject);
|
||||
- if (c != null) c.NetworkAgent = null;
|
||||
- else _realTimeNetworkAgentFallback = null;
|
||||
+ c.NetworkAgent = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user