engine cleanup passes 4-7 + multi-instancing ambient rip

Squashes 146 commits from battle-engine-extraction. Net: 2,045 files changed,
+11,896 / -158,687 lines. Ships engine passes 4-7 (dead-code cull, view-layer
stub, receive-path shrink) plus the Phase-5 AsyncLocal ambient deletion that
turns concurrent battles into a type-system property rather than a scope
contract.

## What landed

**Passes 4-7 (chunks 1-34):** Extended the Phase-4 const-false collapse into a
cascading cull across the skill graph, view layer, and receive-path periphery.
Six mode flags (IsWatchBattle/IsReplayBattle/IsAdmin/IsAdminWatch/IsPuzzleQuest/
IsAINetwork) became `const false`, every guarded block deleted. Field*.cs
subclass ctors + BackGroundBase + ObjectChecker culled to no-ops. Mulligan
family reworked to take a mgr param through IMulliganMgr.InitMulligan.
Emotion/Recovery/Resource clusters null-stubbed. Prediction/OperationSimulator/
skill filters converted from static ambient reads to per-mgr reads via
SkillPrm.ownerCard.SelfBattlePlayer.BattleMgr / ins.BattleMgr / this.BattleMgr.

**Phase-5 ambient rip (chunks 35-47):** Deleted BattleAmbient / BattleAmbient-
Context / TestBattleScope in full. Every per-battle mutable slot now lives on
the mgr instance itself:
  mgr.InstanceIsForecast / InstanceIsRandomDraw / InstanceRecoveryInfo /
  InstanceViewerId / InstanceNetworkAgent / GameMgr
BattleManagerBase.GetIns() returns null unconditionally; the residual static
flags + 3 façades (Certification.ViewerId, Data.BattleRecoveryInfo,
ToolboxGame.RealTimeNetworkAgent) are null-tolerant defaults kept for the
handful of engine-internal readers that still reference their types. Zero
BattleAmbient references anywhere in engine + node + tests.

Added pre-seeded GameMgr ctor overload threaded through the mgr chain
(BattleManagerBase → SingleBattleMgr / NetworkBattleManagerBase → NetworkStandard-
BattleMgr → HeadlessBattleMgr / HeadlessNetworkBattleMgr). Fixtures build a
GameMgr, seed it via HeadlessEngineEnv.SeedCharaIds/SeedNetUser, and pass it
to the mgr's ctor — no ambient reach.

Node side (SVSim.BattleNode/SessionBattleEngine): _ctx replaced with a plain
GameMgr field; 34 `using var _ambient = BattleAmbient.Enter(_ctx)` scope wraps
ripped from every accessor and mutator; EngineGlobalInit.WirePerSessionGameMgr
takes GameMgr as a param and runs from SessionBattleEngine.SetupInternal
BEFORE mgr construction.

Test side: TestBattleScope deleted; 18 fixture [SetUp]s migrated to
`HeadlessEngineEnv.EnsureProcessGlobals()`; MultiInstanceEngineTests rewritten
around per-mgr construction (GetIns() → null is the pinned invariant).

## Regression fixes

- **chunk-48** (MulliganCtrl): chunk-35's `= null` stubs on card lookups broke
  the live receive-driven Deal path (BattlePlayerBase.DrawCard NRE'd downstream
  of NetworkPlayerMulliganCtrl.StartMulliganVfx). Restored the three lookups
  via `_battlePlayer.BattleMgr.GetBattleCardIdx`. Engine tests were satisfied
  by the WireMulliganPhase seam; unit tests exposed the live-path gap.

## Ship state

- SVSim.BattleEngine.Tests: 56/56 pass, 2 skip
- SVSim.UnitTests: 1554/1554 pass (was 1523/31-fail before chunk 48)
- Solution build: 0 source warnings (40 pre-existing NU1902 MessagePack CVEs
  in SVSim.EmulatedEntrypoint, unrelated)
- Sequential PVP smoke: verified live (two back-to-back battles, no regression
  on cleanup/spinup)
- Concurrent PVP smoke: verified live

Adds tools/engine-port/ClosureAnalyzer/ — the Roslyn transitive-type-closure
analyzer needed to make future cascade cleanup safe (per feedback memory
"Engine cleanup needs closure tool" from the 2026-06-28 pass-3 failure).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-07-03 19:18:54 -04:00
parent 5c1db83967
commit 2d9a6eea4b
2045 changed files with 11704 additions and 158495 deletions

View File

@@ -28,16 +28,6 @@ public class AreaSelectUI : UIBase
Loaded
}
private const float CLEAR_EFFECT_WAIT_SEC = 1.1f;
private const float CLEAR_LABEL_DELAY_SEC = 0.4f;
private const float NEWCHAPTER_FOCUS_SEC = 1f;
private const float NEWCHAPTER_FOCUS_MAPICON_ENABLE_THRESHOLD = 0.55f;
private const float MAPICON_EFFECTWAIT_SEC = 0.05f;
private CHAPTERUNLOCK_STATE StateChapterUnlock;
private float NewAreaTimer;
@@ -60,32 +50,6 @@ public class AreaSelectUI : UIBase
private List<string> _loadEffectResources = new List<string>();
public static readonly string CUESHEETNAME_MAP = "se_st_map";
public const int CHAPTERLIST_CAPACITY_DEFAULT = 8;
private const int SORTINGORDER_EFFECT_OVER = 1;
private const float PLAYERICON_Y = -320f;
private const float TITLEPANEL_X_IN = 0f;
private const float TITLEPANEL_X_OUT = 949f;
private const float CHAPTERLIST_X_IN = -200f;
private const float CHAPTERLIST_X_OUT = 400f;
private const int CHAPTERLISTICON_MAX = 5;
public const float CHAPTERLISTICON_OFFSET_Y = 80f;
public const int CHAPTERLISTICON_MARGIN = 50;
public const float CHAPTERLIST_SCROLL_RADIUS = 200f;
public const float CHAPTERLIST_SWITCH_SEC = 0.5f;
private static readonly Vector2 CHAPTERLIST_PANEL_CLIPOFFSET = new Vector2(0f, -420f);
private static readonly Vector2 CHAPTERLIST_PANEL_CLIPPOS = new Vector2(0f, 0f);
@@ -189,8 +153,6 @@ public class AreaSelectUI : UIBase
private bool _isBackTransitionStarted;
private IProcessing _firstSelectionProcessing;
private StoryChapterData _anotherEndingAppearanceAnimationChapterData;
private GameObject _anotherEndingAppearanceEffect;
@@ -203,20 +165,6 @@ public class AreaSelectUI : UIBase
public static readonly Vector3 BG_SCALE_ZOOMIN = new Vector3(0.6f, 0.6f, 0.6f);
public const float BG_POS_ZOOMIN_OFFSET_Y = -400f;
private const float CHAPTERLISTROOT_X_OUT = 800f;
private const float CHAPTERLISTROOT_X_IN = -25f;
private const float MAPMOVE_TIMER_IDLE = 1f;
private const float MAPMOVE_TIMER_ZOOMIN = 1f;
private const float MAPMOVE_TIMER_TOEND = 1f;
private const float ANOTHER_ENDING_APPEARANCE_ANIMATION_TIME = 1.5f;
[SerializeField]
private iTween.EaseType BG_ZOOMIN_EASETYPE = iTween.EaseType.easeInOutCubic;
@@ -461,7 +409,7 @@ public class AreaSelectUI : UIBase
{
_chapterSelectButtonList[_selectChapterIndex].OnStartClearEffect();
IsClearLabelDisplayed = false;
GameMgr.GetIns().GetSoundMgr().PlaySe(Se.TYPE.SYS_MAP_CLEAR);
GameObject obj = ((_selectChapterData.ClearStatus == StoryChapterData.ChapterClearStatus.Cleared) ? _clearEffect : _alreadyReadEffect);
obj.transform.parent = _clearEffectPlayingChapter.ClearLabelTransform;
obj.transform.localPosition = Vector3.zero;
@@ -489,8 +437,8 @@ public class AreaSelectUI : UIBase
{
_mapIcon.SetActiveMapIcon(_selectChapterIndex, isActive: true);
Vector3 mapIconPos = _mapIcon.GetMapIconPos(_selectChapterIndex, isLocal: false);
GameMgr.GetIns().GetEffectMgr().Start(EffectMgr.EffectType.CMN_MAP_CHAPTER_1, mapIconPos.x, mapIconPos.y);
GameMgr.GetIns().GetSoundMgr().PlaySe(Se.TYPE.SYS_MAP_NEW_AREA_RELEASE);
// Pre-Phase-5b: fired CMN_MAP_CHAPTER_1 UI VFX. Dropped headless.
}
IsNewMapIconOpened = true;
}
@@ -504,7 +452,7 @@ public class AreaSelectUI : UIBase
private void StateUnlockChapter_FocusNewChapter_Enter()
{
SelectChapter(nowAreaAllNum - 1, isplayse: false, iscalcscroll: true, ismapiconeffectenable: false, isFirstClear: true);
GameMgr.GetIns().GetSoundMgr().PlaySe(Se.TYPE.SYS_MAP_CAMERA_MOVE);
NewAreaTimer = ((_BG.TransitionChapterExtraData == null) ? 1f : (1f + _BG.TransitionChapterExtraData.FirstClearMoveOutDelayTime));
IsNewMapIconOpened = false;
}
@@ -599,19 +547,19 @@ public class AreaSelectUI : UIBase
protected override void onClose()
{
GameMgr.GetIns().GetEffectMgr().StopBuff(EffectMgr.EffectType.CMN_MAP_PLAYERICON, PlayerIcon);
// Pre-Phase-5b: StopBuff CMN_MAP_PLAYERICON. Dropped headless.
_mapIcon.Term();
_chapterEffect.UnLoadEffect();
_chapterEffect.Term();
StopEffectTap();
if (LoadStatusTutorialEffect.Loaded == _loadStatusTutorialEffect)
{
GameMgr.GetIns().GetEffectMgr().DisposeLatestMadeEffects();
// Pre-Phase-5b: DisposeLatestMadeEffects. Dropped headless.
_loadStatusTutorialEffect = LoadStatusTutorialEffect.NotLoad;
}
Toolbox.ResourcesManager.RemoveAssetGroup(_resourcePathList);
_resourcePathList.Clear();
GameMgr.GetIns().GetSoundMgr().UnloadSe(CUESHEETNAME_MAP);
UnloadUnlockChapterResources();
_BG.UnLoadBG();
_BG.Term();
@@ -631,7 +579,7 @@ public class AreaSelectUI : UIBase
{
if (chapterindex == _selectChapterIndex)
{
GameMgr.GetIns().GetSoundMgr().PlaySe(Se.TYPE.SYS_BTN_DECIDE);
StopEffectTap();
ExecuteSelectionProcessings();
}
@@ -666,7 +614,7 @@ public class AreaSelectUI : UIBase
{
if (chapterindex == _selectChapterIndex)
{
GameMgr.GetIns().GetSoundMgr().PlaySe(Se.TYPE.SYS_BTN_DECIDE);
ExecuteSelectionProcessings();
}
else
@@ -676,20 +624,12 @@ public class AreaSelectUI : UIBase
}
}
private void OnCancelScenarioSummary()
{
if (IsUseChapterListTapEffect)
{
StartEffectTap();
}
}
private void StartEffectTap()
{
if (IsUseChapterListTapEffect && !(null != ChapterListTapEffect))
{
ChapterListTapEffectLocator = _chapterSelectButtonList[_chapterSelectButtonList.Count - 1].TapEffectLocator;
ChapterListTapEffect = GameMgr.GetIns().GetEffectMgr().StartBuff(EffectMgr.EffectType.CMN_TUTORIAL_TAP_1, ChapterListTapEffectLocator);
ChapterListTapEffect = null; // Pre-Phase-5b: StartBuff CMN_TUTORIAL_TAP_1. Dropped headless.
}
}
@@ -697,7 +637,7 @@ public class AreaSelectUI : UIBase
{
if (!(null == ChapterListTapEffectLocator))
{
GameMgr.GetIns().GetEffectMgr().StopBuff(EffectMgr.EffectType.CMN_TUTORIAL_TAP_1, ChapterListTapEffectLocator);
// Pre-Phase-5b: StopBuff CMN_TUTORIAL_TAP_1. Dropped headless.
ChapterListTapEffectLocator = null;
ChapterListTapEffect = null;
}
@@ -818,7 +758,7 @@ public class AreaSelectUI : UIBase
_BG.SetBGDragEnable(!enable);
if (isplayse)
{
GameMgr.GetIns().GetSoundMgr().PlaySe(enable ? Se.TYPE.SYS_TOGGLE_ON : Se.TYPE.SYS_TOGGLE_OFF);
}
}
@@ -856,7 +796,6 @@ public class AreaSelectUI : UIBase
_chapterEffect.Init(this, base.transform);
SetPlayerIconVisible(isvisible: false);
PlayerIcon.transform.localPosition = new Vector3(0f, -320f, 0f);
_firstSelectionProcessing = CreateSelectionProcessings(SectionData.IsTutorialCategory);
StartCoroutine(Setup());
}
@@ -917,12 +856,11 @@ public class AreaSelectUI : UIBase
if (IsUseChapterListTapEffect)
{
_loadStatusTutorialEffect = LoadStatusTutorialEffect.Loading;
GameMgr.GetIns().GetEffectMgr().InitCommonEffect("Json/EffectTutorialData", isBattle: false, isField: false, isBattleEffect: false, delegate
{
_loadStatusTutorialEffect = LoadStatusTutorialEffect.Loaded;
});
// Pre-Phase-5b: async-loaded tutorial effect JSON via EffectMgr; callback flipped the
// load-status flag. Headless has no EffectMgr; mark loaded synchronously so the
// LoadStatusTutorialEffect state machine stays coherent.
_loadStatusTutorialEffect = LoadStatusTutorialEffect.Loaded;
}
GameMgr.GetIns().GetSoundMgr().LoadSe(CUESHEETNAME_MAP);
_scenarioSummary = new ScenarioSummary(SectionId, SectionClassId);
yield return StartCoroutine(Toolbox.ResourcesManager.LoadAssetGroupAsync(_resourcePathList, null));
bool isLoadedClearEffect = false;
@@ -1039,7 +977,7 @@ public class AreaSelectUI : UIBase
_chapterRewardPanel.SetClearPresent(_selectChapterData);
if (isplayse)
{
GameMgr.GetIns().GetSoundMgr().PlaySe(Se.TYPE.SYS_SCROLL);
}
if (iscalcscroll)
{
@@ -1125,30 +1063,6 @@ public class AreaSelectUI : UIBase
{
}
private static void ShowBuildDeckBuyConfirmDialog(int currentCount, int maxCount)
{
int productId = PlayerPrefsWrapper.GetValue(PlayerPrefsWrapper.LAST_BATTLE_DECK_ID);
if (currentCount < maxCount)
{
SystemText systemText = Data.SystemText;
string message = systemText.Get("Story_0057");
if (currentCount > 0)
{
message = systemText.Get("Story_0058");
}
DialogBase dialogBase = UIManager.GetInstance().CreateConfirmationDialog(message);
dialogBase.SetButtonLayout(DialogBase.ButtonLayout.BlueBtn_CancelBtn);
dialogBase.SetTitleLabel(systemText.Get("Story_0056"));
dialogBase.SetButtonText(systemText.Get("Shop_0095"), systemText.Get("Common_0005"));
dialogBase.onPushButton1 = delegate
{
BuildDeckPurchasePage.BuildDeckConfirmDialogShow(productId);
UIManager.GetInstance().ChangeViewScene(UIManager.ViewScene.BuildDeckPurchasePage);
UIManager.GetInstance()._Footer.UpdateCurrentIndex(5);
};
}
}
private void SetupChapterDataListTutorial()
{
Data.StoryInfo.ChapterDataList = Data.Master.TutorialAreaSelectList.Select((TutorialAreaSelect x) => new StoryChapterData(x)).ToList();
@@ -1211,11 +1125,11 @@ public class AreaSelectUI : UIBase
{
if (isvisible)
{
GameMgr.GetIns().GetEffectMgr().StartBuff(EffectMgr.EffectType.CMN_MAP_PLAYERICON, PlayerIcon);
// Pre-Phase-5b: StartBuff CMN_MAP_PLAYERICON. Dropped headless.
}
else
{
GameMgr.GetIns().GetEffectMgr().StopBuff(EffectMgr.EffectType.CMN_MAP_PLAYERICON, PlayerIcon);
// Pre-Phase-5b: StopBuff CMN_MAP_PLAYERICON. Dropped headless.
}
}
@@ -1260,43 +1174,8 @@ public class AreaSelectUI : UIBase
SelectedStoryInfo.ClearInfoForChapterSelectionScene();
}
private static IProcessing CreateSelectionProcessings(bool isTutorialCategory)
private void ExecuteSelectionProcessings()
{
IProcessing[] array = ((!isTutorialCategory) ? new IProcessing[8]
{
new SubChapterSelectionDialogDisplay(),
new SummaryDialogDisplay(),
new DeckSelectionDialogDisplay(),
new ChapterCharaDecider(),
new DownloadInfoGetter(),
new DownloadConfirmDialogDisplay(),
new DeckSelectionConfirmDialogDisplay(),
new StoryStarter()
} : new IProcessing[3]
{
new SummaryDialogDisplay(),
new ChapterCharaDecider(),
new TutorialStoryStarter()
});
for (int i = 0; i < array.Length - 1; i++)
{
array[i].NextProcessing = array[i + 1];
}
return array.FirstOrDefault();
}
private void ExecuteSelectionProcessings()
{
if (_firstSelectionProcessing != null)
{
Parameter param = CreateSelectionProcessingParameter();
_firstSelectionProcessing.Execute(param);
}
}
private Parameter CreateSelectionProcessingParameter()
{
return new Parameter(this, _commonPrefabContainer, _scenarioSummary, SelectedStoryInfo, _selectChapterData, nowAreaAllNum, null, _selectChapterData.IsPlayedChapter ? new int?(_selectChapterIndex) : ((int?)null));
}
private void InitBGStartMove()