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

@@ -174,8 +174,6 @@ public class DeckCardEditUI : CardSelectListUIBase
}
}
private const int DEPTH_RESULT_DIALOG = 100;
public static string CurrentDeckName = null;
private static Format EditDeckFormat = Format.Max;
@@ -204,20 +202,6 @@ public class DeckCardEditUI : CardSelectListUIBase
private DeckGroupListData _deckGroupListData;
private const string CRAFT_BTN_LEFT_ON = "pilltab_02_left_on";
private const string CRAFT_BTN_LEFT_OFF = "pilltab_02_left_off";
private const string CRAFT_BTN_RIGHT_ON = "pilltab_02_right_on";
private const string CRAFT_BTN_RIGHT_OFF = "pilltab_02_right_off";
private const string MY_ROTATION_ALL_PACK_SPRITE = "btn_check_";
private const string BUTTON_SPRITE_FOOTER_OFF = "off";
private const string BUTTON_SPRITE_FOOTER_ON = "on";
private readonly Vector3 MY_ROTATION_FORMAT_ICON_POSITION = new Vector3(45f, -47f, 0f);
private readonly Vector3 MY_ROTATION_CARD_NUMBER_ICON_POSITION = new Vector3(68f, -62f, -1f);
@@ -226,12 +210,6 @@ public class DeckCardEditUI : CardSelectListUIBase
private readonly Vector3 MY_ROTATION_CARD_NUMBER_MAX_POSITION = new Vector3(58f, 0f, 0f);
private const int MY_ROTATION_CLASS_NAME_WIDTH = 107;
private const int MY_ROTATION_CLASS_UNDER_LINE_WIDTH = 184;
private const float BACK_BUTTON_POS_Y = -40.7f;
[SerializeField]
private DeckBuildShortageCardView _prefabShortageCardView;
@@ -429,12 +407,8 @@ public class DeckCardEditUI : CardSelectListUIBase
}
}
public bool IsCraftMode => _deckCardBundle.IsCraftMode;
public bool IsBattleRetry { get; set; }
public bool IsEditState => m_state == _stateEdit;
public event Action<int> OnChangeDeckCardNum;
private DeckData GetDeck(Format format, int deckId)
@@ -464,81 +438,6 @@ public class DeckCardEditUI : CardSelectListUIBase
UIManager.GetInstance().OverrideSceneParam(UIManager.ViewScene.DeckList, new DeckListUIParam(deck.Format, conventionDeckList?.Conventioninfo));
}
public static void SetDeckCopyParameter(DeckData deck, bool isCreatedByBuilder, bool isCopySubClass, ConventionDeckList conventionDeckList, MyRotationInfo myRotationInfo = null)
{
CopySrcDeckData = deck;
ClassSet = new ClassSet(deck.GetDeckClassID(), deck.GetDeckSubClassID());
IsCopySkinAndSleeve = PlayerPrefsWrapper.GetBool(PlayerPrefsWrapper.IS_COPY_SLEEVE_AND_SKIN);
IsCopySubClass = isCopySubClass;
IsCreatedByBuilder = isCreatedByBuilder;
_conventionDeckList = conventionDeckList;
CanUseNonPossessionCard = conventionDeckList == null;
if (deck.DeckAttributeType == DeckAttributeType.TrialDeck || deck.DeckAttributeType == DeckAttributeType.SampleDeck)
{
IsCreatedByBuilder = true;
}
if (myRotationInfo == null)
{
MyRotationInfo = Data.MyRotationAllInfo.Get(deck.MyRotationId);
}
else
{
MyRotationInfo = myRotationInfo;
}
}
public static void SetCreateAutoParameter(Format format, bool canUseNonPossessionCard)
{
IsCreateAuto = true;
CanUseNonPossessionCard = canUseNonPossessionCard;
}
public static void SetDeckCopyParameterForDeckIntroduction(DeckData emptyDeck, DeckData srcDeck, MyRotationInfo myRotationInfo = null)
{
CurrentDeckName = null;
CurrentDeckId = emptyDeck.GetDeckID();
CurrentDeckData = emptyDeck;
EditDeckFormat = emptyDeck.Format;
ClassSet = new ClassSet(srcDeck.GetDeckClassID(), srcDeck.GetDeckSubClassID());
CopySrcDeckData = srcDeck;
IsCopySkinAndSleeve = false;
IsCopySubClass = FormatBehaviorManager.GetDefaultBehaviour(srcDeck.Format).UseSubClass && PlayerPrefsWrapper.GetBool(PlayerPrefsWrapper.IS_COPY_SUBCLASS_CARDS);
IsCreatedByBuilder = true;
IsCreateAuto = false;
_conventionDeckList = null;
CanUseNonPossessionCard = true;
if (EditDeckFormat == Format.MyRotation)
{
if (myRotationInfo == null)
{
MyRotationInfo = Data.MyRotationAllInfo.Get(srcDeck.MyRotationId);
}
else
{
MyRotationInfo = myRotationInfo;
}
}
UIManager.GetInstance().OverrideSceneParam(UIManager.ViewScene.DeckList, new DeckListUIParam(emptyDeck.Format, null));
}
public static void SetBuildDeckEditParameter(DeckData buildDeck, string deckName, DeckData emptyDeck)
{
CurrentDeckName = deckName;
CurrentDeckId = emptyDeck.GetDeckID();
CurrentDeckData = emptyDeck;
ClassSet = new ClassSet(buildDeck.GetDeckClassID(), buildDeck.GetDeckSubClassID());
EditDeckFormat = emptyDeck.Format;
CopySrcDeckData = buildDeck;
IsCopySkinAndSleeve = false;
IsCopySubClass = FormatBehaviorManager.GetDefaultBehaviour(buildDeck.Format).UseSubClass && PlayerPrefsWrapper.GetBool(PlayerPrefsWrapper.IS_COPY_SUBCLASS_CARDS);
IsCreatedByBuilder = true;
IsCreateAuto = false;
_conventionDeckList = null;
CanUseNonPossessionCard = true;
MyRotationInfo = null;
UIManager.GetInstance().OverrideSceneParam(UIManager.ViewScene.DeckList, new DeckListUIParam(emptyDeck.Format, null));
}
public override bool IsEnableSwipeAutoSameBasicCardAdd()
{
return true;
@@ -789,7 +688,7 @@ public class DeckCardEditUI : CardSelectListUIBase
private void SetupDeckViewerAndText()
{
m_labelDeckName.text = _deckCardBundle.DeckName;
m_labelClassName.text = GameMgr.GetIns().GetDataMgr().GetClanNameByKey((int)ClassSet.MainClass);
m_labelClassName.text = ((int)ClassSet.MainClass).ToString(); // Pre-Phase-5b: no clan-name lookup
_deckViewer.RemoveData();
_deckViewer.SetDeckName(_deckCardBundle.DeckName);
_deckViewer.SetClassSet(ClassSet);
@@ -913,18 +812,6 @@ public class DeckCardEditUI : CardSelectListUIBase
InitUseSubClassDisplay();
}
public void OnBtnPushSelectCardFilter()
{
GameMgr.GetIns().GetSoundMgr().PlaySe(Se.TYPE.SYS_COMMON_BUTTON);
ShowFilterMenu(_selectCardFilter, Data.SystemText.Get("Card_0126"));
}
public void OnBtnPushOwnFilter()
{
GameMgr.GetIns().GetSoundMgr().PlaySe(Se.TYPE.SYS_COMMON_BUTTON);
ShowFilterMenu(_pagingFilter, Data.SystemText.Get("Card_0021"));
}
protected override void OnFinishFadeIn()
{
_fadeInFinish = true;
@@ -953,13 +840,6 @@ public class DeckCardEditUI : CardSelectListUIBase
return false;
}
public bool LoadDeckCard(List<int> idList)
{
base.IsLoading = true;
HideDetail();
return _deckCardBundle.LoadDeckCard(idList);
}
public void ChangeCraftMode(bool isCraft)
{
if (!base.IsLoading && base.CurrentState == _stateEdit)
@@ -967,7 +847,7 @@ public class DeckCardEditUI : CardSelectListUIBase
base.IsLoading = true;
m_craftOnBtn.normalSprite = (isCraft ? "pilltab_02_right_on" : "pilltab_02_right_off");
m_craftOffBtn.normalSprite = (isCraft ? "pilltab_02_left_off" : "pilltab_02_left_on");
GameMgr.GetIns().GetSoundMgr().PlaySe(Se.TYPE.SYS_TOGGLE_ON);
_deckCardBundle.ChangeCraftMode(isCraft);
}
}
@@ -1035,7 +915,7 @@ public class DeckCardEditUI : CardSelectListUIBase
bool flag = false;
if (ConventionInfo == null)
{
deckUpdateTask = GameMgr.GetIns().GetDeckUpdateTask();
deckUpdateTask = null; // Pre-Phase-5b: no DeckUpdateTask headless
flag = deckUpdateTask.AchievedInfo._rewards.Count > 0;
}
if (flag)
@@ -1090,7 +970,7 @@ public class DeckCardEditUI : CardSelectListUIBase
private void ChangeSceneToBattle()
{
DataMgr dataManager = GameMgr.GetIns().GetDataMgr();
DataMgr dataManager = null; // Pre-Phase-5b: headless has no DataMgr
DeckInfoTask task = new DeckInfoTask();
task.SetParameter(Format.All);
UIManager.GetInstance().StartCoroutine(Toolbox.NetworkManager.Connect(task, delegate
@@ -1111,7 +991,7 @@ public class DeckCardEditUI : CardSelectListUIBase
private string GetSaveAndRetryDialogText()
{
DataMgr dataMgr = GameMgr.GetIns().GetDataMgr();
DataMgr dataMgr = null; // Pre-Phase-5b: DataMgr not reachable in DeckCardEdit UI headless
switch (dataMgr.m_BattleType)
{
case DataMgr.BattleType.FreeBattle:
@@ -1141,7 +1021,7 @@ public class DeckCardEditUI : CardSelectListUIBase
private string GetRetryDialogText()
{
DataMgr dataMgr = GameMgr.GetIns().GetDataMgr();
DataMgr dataMgr = null; // Pre-Phase-5b: DataMgr not reachable in DeckCardEdit UI headless
switch (dataMgr.m_BattleType)
{
case DataMgr.BattleType.FreeBattle:
@@ -1300,34 +1180,9 @@ public class DeckCardEditUI : CardSelectListUIBase
UpdateCraftShortageButton();
}
private void OnChangeManyDeckCards()
{
_hasChanged = true;
m_costCurve.Refresh(_deckCardBundle.SelectionAreaList.IdList.ToArray());
DeckCardNumAnim();
UpdateCraftShortageButton();
}
public void SetAllDeckCards(List<int> cardIdList, Action onFirstAnimationFinish = null, float cardRotateDelayTimeMax = float.MaxValue)
{
_deckCardBundle.SetSelectionArea(cardIdList, onFirstAnimationFinish, cardRotateDelayTimeMax);
OnChangeManyDeckCards();
}
public void RemoveAllDeckCards()
{
_deckCardBundle.ClearSelectionArea();
OnChangeManyDeckCards();
}
public CardBundle GetDeckCardBundle()
{
return _deckCardBundle.SelectionAreaList;
}
private void OnClickAutoDeckBtn(GameObject obj)
{
GameMgr.GetIns().GetSoundMgr().PlaySe(Se.TYPE.SYS_COMMON_BUTTON);
if (Data.MaintenanceCodeList.Contains(NetworkDefine.MAINTENANCE_TYPE.AUTO_DECK_CREATE))
{
DialogBase dialogBase = UIManager.GetInstance().CreateConfirmationDialog(Data.SystemText.Get("Card_0266"));
@@ -1379,7 +1234,7 @@ public class DeckCardEditUI : CardSelectListUIBase
private void OnClickSaveBtn(GameObject obj)
{
SaveDeck();
GameMgr.GetIns().GetSoundMgr().PlaySe(Se.TYPE.SYS_COMMON_BUTTON);
}
private DeckData CreateDeckData()
@@ -1408,15 +1263,11 @@ public class DeckCardEditUI : CardSelectListUIBase
HideDetail();
_deckViewer.gameObject.SetActive(value: true);
_deckViewer.SetDeck(CreateDeckData(), _conventionDeckList);
if (QRCodeUtility.IsShowQRCode(_deckViewer, _formatBehavior, base.Format))
{
_deckViewer.SetQRSmallTexture();
}
else if (_formatBehavior.CanShowQRCode)
if (_formatBehavior.CanShowQRCode)
{
_deckViewer.SetQRCodeButtonToGray();
}
GameMgr.GetIns().GetSoundMgr().PlaySe(Se.TYPE.SYS_COMMON_BUTTON);
}
private void HideDeckViewer()
@@ -1427,7 +1278,7 @@ public class DeckCardEditUI : CardSelectListUIBase
private void OnClickCraftShortageCardButton(GameObject obj)
{
GameMgr.GetIns().GetSoundMgr().PlaySe(Se.TYPE.SYS_COMMON_BUTTON);
if (!CanUseNonPossessionCard || base.IsLoading)
{
return;
@@ -1475,7 +1326,7 @@ public class DeckCardEditUI : CardSelectListUIBase
dialogBase.SetSize(DialogBase.Size.XL);
dialogBase.SetTitleLabel(Data.SystemText.Get("Dia_DeckEdit_014_Title"));
dialogBase.SetPanelDepth(100);
dialogBase.OpenSe = Se.TYPE.NONE;
dialogBase.OpenSe = 0;
DeckBuildShortageCardView _shortageCardView = UnityEngine.Object.Instantiate(_prefabShortageCardView);
dialogBase.SetObj(_shortageCardView.gameObject);
dialogBase.OnCloseStart = CloseShortageCardViewer;
@@ -1519,7 +1370,7 @@ public class DeckCardEditUI : CardSelectListUIBase
dialogBase.SetButtonLayout(DialogBase.ButtonLayout.OkBtn);
List<int> addIdList = _shortageIdList.FindAll(delegate(int id)
{
if (GameMgr.GetIns().GetDataMgr().IsMaintenanceCard(id))
if (false /* Pre-Phase-5b: IsMaintenanceCard headless-false */)
{
return false;
}
@@ -1623,7 +1474,7 @@ public class DeckCardEditUI : CardSelectListUIBase
if (_deckViewer.isActiveAndEnabled)
{
HideDeckViewer();
GameMgr.GetIns().GetSoundMgr().PlaySe(Se.TYPE.SYS_BTN_CANCEL);
return false;
}
if (_hasChanged && DeckCardNum > 0)
@@ -1709,7 +1560,7 @@ public class DeckCardEditUI : CardSelectListUIBase
List<int> list = new List<int>();
List<int> source = deckCardList.ConvertAll((int id) => CardMaster.GetInstance(formatBehavior.CardMasterId).GetCardParameterFromId(id).BaseCardId);
List<int> needBaseIdList = source.Distinct().ToList();
Dictionary<int, int> possessionBaseCardDictionary = GameMgr.GetIns().GetDataMgr().GetPossessionBaseCardDictionary(isIncludingSpotCard: true, formatBehavior.CardMasterId);
Dictionary<int, int> possessionBaseCardDictionary = new(); // Pre-Phase-5b: possession lookup not reachable headless
int i;
for (i = 0; i < needBaseIdList.Count; i++)
{
@@ -1799,7 +1650,7 @@ public class DeckCardEditUI : CardSelectListUIBase
private void OnClickSwapClassButton(GameObject obj)
{
GameMgr.GetIns().GetSoundMgr().PlaySe(Se.TYPE.SYS_COMMON_BUTTON);
DialogBase dialogBase = UIManager.GetInstance().CreateDialogClose();
dialogBase.SetTitleLabel(Data.SystemText.Get("Card_0274"));
dialogBase.SetText(Data.SystemText.Get("Card_0275"));
@@ -1813,7 +1664,7 @@ public class DeckCardEditUI : CardSelectListUIBase
private void OnClickMyRotationChangeButton(GameObject g)
{
GameMgr.GetIns().GetSoundMgr().PlaySe(Se.TYPE.SYS_COMMON_BUTTON);
MyRotationPeriodSelectDialog.Create(_myRotationInfo, ClassSet.MainClass, delegate(MyRotationInfo selectData)
{
if (_myRotationInfo.Id != selectData.Id)