Follow-up to the multi-instancing migration. Wraps the process-shared engine
statics that aren't ambient-fronted but race between concurrent battles:
- UnityEngine.Resources._loaded: Dictionary -> ConcurrentDictionary.GetOrAdd
(the shared prefab cache keyed by path; concurrent first-misses produced
duplicate GameObjects + Dictionary corruption)
- UnityEngine.GameObject._components: Dictionary -> ConcurrentDictionary with
Interlocked.CompareExchange init (Resources.Load returns SHARED prefab
GameObjects, so two engines' Setup() can race on the same _components map
— surfaced as "Operations that change non-concurrent collections" crashes
during BattleManagerBase ctor's GetComponent<T>() chain)
- Wizard.LocalLog: single static lock around all mutating entry points
(StringBuilder _lastTraceLogStringBuilder + ~12 mutable string/bool/int
scratch fields; serializing the trace-log surface is cheap since logging
is not the hot path)
Flips SVSim.BattleEngine.Tests assembly Parallelizable scope from Self to
Fixtures and restructures MultiInstanceEngineTests.StressN_BaselineMatches so
Setup runs INSIDE Task.Run (was previously serialized as a workaround for the
LocalLog races). The fixture is also lifted to ParallelScope.All so the
two-engines and stress tests can run alongside each other.
Suite fully green under fixture parallelism (59/0/2 across 3 consecutive runs);
SVSim.UnitTests still 1054/0/0 — true multi-instance correctness is now proved
end-to-end in tests rather than gated behind a serial workaround.
Manifest sha refresh + new patch artifact for the LocalLog edit (decomp-origin);
the two shim files are authored, so no metadata update is needed for them.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Step 4 of multi-instancing migration. Three additional per-battle statics
front-fronted by BattleAmbient.Current, each with a static fallback for
unwrapped callers. ViewerId's SavedataManager-persisting setter is preserved
on the fallback path; inside a scope, the setter is a no-op (the per-battle
perspective is fixed at scope entry).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Step 3 of multi-instancing migration. The dominant per-battle singleton now
resolves through BattleAmbient.Current.Mgr when a scope is active. The legacy
'main' field is renamed _mainFallback and retained for unwrapped callers
(tests, anything not yet scope-wrapped). GetIns() still returns null when
neither is set, preserving the '?.Foo ?? default' patterns in engine code.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hygiene fixup for the IsForecast/IsRandomDraw ambient conversion in 3b5f2e1.
The manifest sha was stale (pointed at the pre-ambient RNG-virtual-patched
contents) and the change had no companion .patch artifact alongside
BattleManagerBase.rng-virtual.patch. Follow established convention.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Final three clusters:
- RoomParamKey: copy Wizard.RoomMatch/RoomParamKey.cs verbatim (UriNames/WatchUriNames
static dicts keyed by PlayerController.ROOM_URI + PlayerControllerForWatching.
SEND_PARAMETER — both now real enums).
- CardChooseTask: copy the TwoPick/CardChooseTask.cs (TaskManager `using`s .Arena.TwoPick,
not .Competition — copy_loop had only landed the Competition twin).
- SetCardNumLabel CS1739: decompiler param-name artifact — the local fn's 3rd param was
recovered as `flag` but call sites pass it named `isRed:`. First DP5 tracked patch:
Engine/UICardList.cs edited (flag->isRed, zero logic change), recorded in
Patches/ + manifest patched=1 (drift-clean).
M1 exit criteria met: `dotnet build SVSim.BattleEngine` = 0 errors, no Unity ref in csproj,
check_drift clean. Session 7: 198 -> 0 across waves 7a-7k.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>