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

@@ -7,9 +7,6 @@ using Wizard.UI.LoginBonus;
public class LoadDetail
{
private const string ARENA_INFO_KEY = "arena_info";
public int RotationFirstCardPackId;
public int RotationLatestCardPackId;
@@ -33,12 +30,8 @@ public class LoadDetail
public UserConfig _userConfig = new UserConfig();
public ChallengeConfig _challengeConfig = new ChallengeConfig();
public Dictionary<int, UserRank> _userRank = new Dictionary<int, UserRank>();
public List<ClassCharaExp> _classCharaExpList = new List<ClassCharaExp>();
public Dictionary<int, int> _userItemDict = new Dictionary<int, int>();
public DateTime _masterResetNextTime;
@@ -55,422 +48,16 @@ public class LoadDetail
public bool[] LootBoxReguration { get; private set; } = new bool[6];
public JsonData UserDeckListRotation { get; private set; }
public JsonData UserDeckListUnlimited { get; private set; }
public JsonData UserDeckListPreRotation { get; private set; }
public JsonData UserDeckListCrossover { get; private set; }
public JsonData UserDeckListMyRotation { get; private set; }
public List<int> AcquiredSkinList { get; private set; } = new List<int>();
public List<string> AcquiredMyPageBGList { get; private set; } = new List<string>();
public List<UserCard> UserCardList { get; private set; } = new List<UserCard>();
public List<RankInfo> RankInfoList { get; private set; } = new List<RankInfo>();
public Dictionary<int, BattlePassLevelInfo> BattlePassLevelInfoList { get; private set; } = new Dictionary<int, BattlePassLevelInfo>();
public List<int> LoadingExclusionCardList { get; private set; } = new List<int>();
public int BattleRecoveryStatus { get; private set; }
public int RoomRecoveryStatus { get; private set; }
public long DefaultEmblemId { get; private set; }
public int DefaultDegreeId { get; private set; }
public NormalData NormalLoginBonusData { get; private set; }
public ContinuousData ContinuousLoginBonusData { get; private set; }
public List<SpecialData> SpecialLoginBonusDataList { get; private set; } = new List<SpecialData>();
public FreeCardPackBoxData FreeCardPackData { get; private set; }
public Dictionary<int, string> UserItemExpireTimeDictionary { get; set; } = new Dictionary<int, string>();
public List<int> UserItemNotStartList { get; set; } = new List<int>();
public List<int> ReprintedBaseCardIds { get; private set; } = new List<int>();
public CardMaster.UpdateInfo CardMasterUpdateInfo { get; private set; }
public int ResourceDlViewID { get; private set; }
public List<string> OpenBattleFieldIdList { get; private set; }
public void ConvertJsonData(JsonData responseData)
{
JsonData jsonData = responseData["data"];
GameMgr.GetIns().GetDataMgr().SetMaintenanceCardIds(jsonData["maintenance_card_list"]);
JsonData jsonData2 = jsonData["unlimited_restricted_base_card_id_list"];
if (jsonData2.IsObject)
{
foreach (string key2 in jsonData2.Keys)
{
if (int.TryParse(key2, out var result))
{
UnlimitedRestrictedCardList.Add(new UnlimitedRestrictedCard(result, jsonData2[key2].ToInt()));
}
}
}
JsonData jsonData3 = jsonData["rotation_card_set_id_list"];
for (int i = 0; i < jsonData3.Count; i++)
{
RotationCardSetList.Add(jsonData3[i]["card_set_id"].ToInt());
}
RotationLatestCardPackId = RotationCardSetList[RotationCardSetList.Count - 1];
RotationFirstCardPackId = RotationCardSetList[1];
JsonData jsonData4 = jsonData["red_ether_overwrite_list"];
for (int j = 0; j < jsonData4.Count; j++)
{
string key = jsonData4[j]["card_id"].ToString();
if (jsonData4[j].Keys.Contains("get_red_ether"))
{
int value = jsonData4[j]["get_red_ether"].ToInt();
OverwriteGetRedEtherDict.Add(key, value);
}
if (jsonData4[j].Keys.Contains("use_red_ether"))
{
int value2 = jsonData4[j]["use_red_ether"].ToInt();
OverwriteUseRedEtherDict.Add(key, value2);
}
}
UserInfo userInfo = _userInfo;
JsonData jsonData5 = jsonData["user_info"];
userInfo.name = jsonData5["name"].ToString();
userInfo.birth_day = jsonData5["birth"].ToString();
userInfo.selected_emblem_id = jsonData5["selected_emblem_id"].ToLong();
userInfo.selected_degree_id = jsonData5["selected_degree_id"].ToInt();
JsonData jsonData6 = jsonData5["country_code"];
userInfo.country_code = ((jsonData6 == null) ? string.Empty : jsonData6.ToString());
PlayerStaticData.IsOfficialUser = jsonData5["is_official"].ToInt() == 1;
PlayerStaticData.IsOfficialUserDisplay = jsonData5["is_official_mark_displayed"].ToInt() == 1;
_userRank[0] = new UserRank();
_userRank[1] = new UserRank();
_userRank[104] = new UserRank();
_userRank[5] = new UserRank();
_userRank[39] = new UserRank();
JsonData jsonData7 = jsonData["loot_box_regulation"];
if (jsonData7.Count > 0)
{
LootBoxReguration[0] = jsonData7["pack"].ToInt() == 1;
LootBoxReguration[1] = jsonData7["arena_2pick"].ToInt() == 1;
LootBoxReguration[2] = jsonData7["arena_sealed"].ToInt() == 1;
LootBoxReguration[3] = jsonData7["arena_colosseum"].ToInt() == 1;
LootBoxReguration[4] = jsonData7["arena_competition"].ToInt() == 1;
LootBoxReguration[5] = jsonData7["special_shop"].ToInt() == 1;
}
else
{
for (int k = 0; k < 6; k++)
{
LootBoxReguration[k] = false;
}
}
_userCrystalCount.SetUserCrystalCount(jsonData["user_crystal_count"]);
_userCrystalCount.ParseSpecialCrystal(responseData);
JsonData jsonData8 = jsonData["user_item_list"];
for (int l = 0; l < jsonData8.Count; l++)
{
PlayerStaticData.InitializeItemNum(jsonData8[l]["item_id"].ToInt(), jsonData8[l]["number"].ToInt());
}
Data.Load.data.UserItemExpireTimeDictionary = new Dictionary<int, string>();
Data.Load.data.UserItemNotStartList = new List<int>();
if (jsonData.Keys.Contains("item_expire_date"))
{
JsonData jsonData9 = jsonData["item_expire_date"];
for (int m = 0; m < jsonData9.Count; m++)
{
JsonData jsonData10 = jsonData9[m];
int num = jsonData10["item_id"].ToInt();
string text = jsonData10["expire_date"].ToString();
if (jsonData10.TryGetValue("start_date", out var value3) && new RemainTime(value3.ToString(), responseData["data_headers"]["servertime"].ToDouble()).Second > 1)
{
Data.Load.data.UserItemNotStartList.Add(num);
}
if (!string.IsNullOrEmpty(text))
{
Data.Load.data.UserItemExpireTimeDictionary[num] = ConvertTime.ToLocal(text);
}
}
}
PlayerStaticData.UserSpotCardPointCount = jsonData["spot_point"].ToInt();
if (jsonData.Keys.Contains("arena_info"))
{
Data.ArenaData = new ArenaData(jsonData["arena_info"]);
Data.ArenaData.ColosseumData.IsFreeEntry = jsonData["is_available_colosseum_free_entry"].ToBoolean();
}
_receiveInviteCount = jsonData["friend_battle_invite_count"].ToInt();
_userTutorial.Update(jsonData["user_tutorial"]);
UserDeckListRotation = jsonData["user_deck_rotation"]["user_deck_list"];
UserDeckListUnlimited = jsonData["user_deck_unlimited"]["user_deck_list"];
if (jsonData.Keys.Contains("user_deck_pre_rotation"))
{
UserDeckListPreRotation = jsonData["user_deck_pre_rotation"]["user_deck_list"];
}
if (jsonData.Keys.Contains("user_deck_crossover"))
{
UserDeckListCrossover = jsonData["user_deck_crossover"]["user_deck_list"];
}
if (jsonData.Keys.Contains("user_deck_my_rotation"))
{
UserDeckListMyRotation = jsonData["user_deck_my_rotation"]["user_deck_list"];
}
if (Data.Master.isMasterDataLoaded)
{
DeckListUtility.SetDeckListDataWithLodeIndex();
}
DefaultEmblemId = jsonData["default_setting"]["default_emblem_id"].ToInt();
JsonData jsonData11 = jsonData["user_emblem_list"];
for (int n = 0; n < jsonData11.Count; n++)
{
long num2 = jsonData11[n]["emblem_id"].ToLong();
if (Data.Master.EmblemMgr != null)
{
Data.Master.EmblemMgr.Acquired(num2);
Data.Master.EmblemMgr.UnsetNew(num2);
}
else
{
_acquiredEmblemList.Add(num2);
}
}
if (jsonData.TryGetValue("user_favorite_emblem_list", out var value4))
{
for (int num3 = 0; num3 < value4.Count; num3++)
{
long num4 = value4[num3].ToLong();
if (Data.Master.EmblemMgr != null)
{
Data.Master.EmblemMgr.SetFavorite(num4, favorite: true);
}
else
{
_favoriteEmblemList.Add(num4);
}
}
}
DefaultDegreeId = jsonData["default_setting"]["default_degree_id"].ToInt();
JsonData jsonData12 = jsonData["user_degree_list"];
for (int num5 = 0; num5 < jsonData12.Count; num5++)
{
int num6 = (int)jsonData12[num5]["degree_id"];
if (Data.Master.DegreeMgr != null)
{
Data.Master.DegreeMgr.Acquired(num6);
Data.Master.DegreeMgr.UnsetNew(num6);
}
else
{
_acquiredDegreeList.Add(num6);
}
}
if (jsonData.TryGetValue("user_favorite_degree_list", out var value5))
{
for (int num7 = 0; num7 < value5.Count; num7++)
{
int num8 = value5[num7].ToInt();
if (Data.Master.DegreeMgr != null)
{
Data.Master.DegreeMgr.SetFavorite(num8, favorite: true);
}
else
{
_favoriteDegreeList.Add(num8);
}
}
}
JsonData jsonData13 = jsonData["user_leader_skin_list"];
for (int num9 = 0; num9 < jsonData13.Count; num9++)
{
JsonData jsonData14 = jsonData13[num9];
if (jsonData14["is_owned"].ToBoolean())
{
AcquiredSkinList.Add(jsonData14["leader_skin_id"].ToInt());
}
}
JsonData jsonData15 = jsonData["user_sleeve_list"];
for (int num10 = 0; num10 < jsonData15.Count; num10++)
{
long num11 = jsonData15[num10]["sleeve_id"].ToLong();
if (Data.Master.SleeveMgr != null)
{
Data.Master.SleeveMgr.Acquired(num11);
Data.Master.SleeveMgr.UnsetNew(num11);
}
else
{
_acquiredSleeveList.Add(num11);
}
}
if (jsonData.TryGetValue("user_favorite_sleeve_list", out var value6))
{
for (int num12 = 0; num12 < value6.Count; num12++)
{
long num13 = value6[num12].ToLong();
if (Data.Master.SleeveMgr != null)
{
Data.Master.SleeveMgr.SetFavorite(num13, b: true);
}
else
{
_favoriteSleeveList.Add(num13);
}
}
}
JsonData jsonData16 = jsonData["user_mypage_list"];
AcquiredMyPageBGList = new List<string>();
for (int num14 = 0; num14 < jsonData16.Count; num14++)
{
AcquiredMyPageBGList.Add(jsonData16[num14].ToString());
}
JsonData jsonData17 = jsonData["user_card_list"];
List<int> favoriteCardList = GameMgr.GetIns().GetDataMgr().FavoriteCardList;
for (int num15 = 0; num15 < jsonData17.Count; num15++)
{
JsonData jsonData18 = jsonData17[num15];
UserCard userCard = new UserCard();
int item = (userCard.card_id = jsonData18["card_id"].ToInt());
userCard.number = jsonData18["number"].ToInt();
if (jsonData18["is_protected"].ToInt() != 0)
{
favoriteCardList.Add(item);
}
UserCardList.Add(userCard);
}
JsonData userClassCharaList = jsonData["user_class_list"];
JsonData userRankMatchList = jsonData["user_rank_match_list"];
GameMgr.GetIns().GetDataMgr().SetClassPrm(userClassCharaList, userRankMatchList);
ParseUserRank(jsonData);
JsonData jsonData19 = jsonData["rank_info"];
int num16 = 0;
for (int num17 = 0; num17 < jsonData19.Count; num17++)
{
JsonData jsonData20 = jsonData19[num17];
RankInfo rankInfo = new RankInfo(jsonData20);
if (num17 >= 24)
{
int num18 = jsonData20["accumulate_master_point"].ToInt();
rankInfo._necessaryMasterPoint = num18 - num16;
num16 = num18;
}
RankInfoList.Add(rankInfo);
}
JsonData jsonData21 = jsonData["class_exp"];
for (int num19 = 0; num19 < jsonData21.Count; num19++)
{
JsonData jsonData22 = jsonData21[num19];
ClassCharaExp classCharaExp = new ClassCharaExp();
classCharaExp.level = jsonData22["level"].ToInt();
classCharaExp.necessary_exp = jsonData22["necessary_exp"].ToInt();
classCharaExp.accumulate_exp = jsonData22["accumulate_exp"].ToInt();
_classCharaExpList.Add(classCharaExp);
}
if (jsonData.TryGetValue("battle_pass_level_info", out var value7))
{
foreach (JsonData value13 in value7.Values)
{
BattlePassLevelInfo battlePassLevelInfo = new BattlePassLevelInfo(value13);
BattlePassLevelInfoList.Add(battlePassLevelInfo.Level, battlePassLevelInfo);
}
}
JsonData jsonData23 = jsonData["loading_exclusion_card_list"];
for (int num20 = 0; num20 < jsonData23.Count; num20++)
{
LoadingExclusionCardList.Add(Convert.ToInt32(jsonData23[num20].ToString()));
}
SettingRecoveryStatus(jsonData["battle_recovery_status"].ToInt(), jsonData["room_recovery_status"].ToInt());
NormalLoginBonusData = null;
ContinuousLoginBonusData = null;
SpecialLoginBonusDataList.Clear();
if (jsonData.Keys.Contains("daily_login_bonus"))
{
ParseLoginBonusInfo(jsonData["daily_login_bonus"]);
}
FreeCardPackData = null;
if (jsonData.TryGetValue("free_card_pack_box_campaign", out var value8))
{
FreeCardPackData = new FreeCardPackBoxData(value8);
}
JsonData jsonData24 = jsonData["reprinted_base_card_ids"];
ReprintedBaseCardIds.Clear();
for (int num21 = 0; num21 < jsonData24.Count; num21++)
{
ReprintedBaseCardIds.Add(jsonData24[num21].ToInt());
}
SpotCardData spotCardData = new SpotCardData();
if (jsonData.Keys.Contains("spot_cards"))
{
spotCardData.SetSpotCardData(jsonData["spot_cards"]);
}
GameMgr.GetIns().GetDataMgr().SpotCardData = spotCardData;
CardMasterUpdateInfo = new CardMaster.UpdateInfo(jsonData);
if (jsonData.Keys.Contains("pre_release_info"))
{
Prerelease.Create(jsonData["pre_release_info"]);
}
if (jsonData.Keys.Contains("gathering_info"))
{
Data.MyPageNotifications.data.IsInviteGathering = jsonData["gathering_info"]["has_invite"].ToInt() != 0;
}
Data.User.ConnectTimeForMasterReset = ConvertTime.UnixTimeToDateTime(responseData["data_headers"]["servertime"].ToInt());
Data.User.ConnectSinceStartUp = Time.realtimeSinceStartup;
_masterResetNextTime = DateTime.MinValue;
Data.Load.data._challengeConfig.UseTwoPickPremiumCard = responseData["data"]["challenge_config"]["use_challenge_two_pick_premium_card"].ToInt() == 1;
Data.Load.data._challengeConfig.TwoPickSleeveId = responseData["data"]["challenge_config"]["challenge_two_pick_sleeve_id"].ToLong();
Data.ParseIsBattlePassPeriod(responseData["data"]);
Data.ParseMaintenance(jsonData["feature_maintenance_list"]);
ResourceDlViewID = jsonData.GetValueOrDefault("card_set_id_for_resource_dl_view", 1);
if (jsonData.Keys.Contains("open_battle_field_id_list") && jsonData.TryGetValue("open_battle_field_id_list", out var value9))
{
OpenBattleFieldIdList = new List<string>();
for (int num22 = 0; num22 < value9.Count; num22++)
{
OpenBattleFieldIdList.Add(value9[num22].ToString());
}
PlayerPrefsWrapper.TurnOnFirsStageIfStageIdListAllOff();
}
if (jsonData.TryGetValue("crossover_info", out var value10))
{
Data.Crossover.Parse(value10);
}
if (jsonData.TryGetValue("my_rotation_info", out var value11))
{
bool isFinishInitializeText = Data.MyRotationAllInfo.IsFinishInitializeText;
Data.MyRotationAllInfo = new MyRotationAllInfo();
Data.MyRotationAllInfo.Parse(value11, responseData["data_headers"]);
if (isFinishInitializeText)
{
Data.MyRotationAllInfo.InitializeText();
}
}
if (jsonData.TryGetValue("avatar_info", out var value12))
{
Data.AvatarBattleAllInfo = new AvatarBattleAllInfo();
Data.AvatarBattleAllInfo.Parse(value12, responseData["data_headers"]);
}
}
public void ParseUserRank(JsonData data)
{
if (data["user_rank"] != null)
{
for (int i = 0; i < data["user_rank"].Count; i++)
{
JsonData jsonData = data["user_rank"][i];
Format format = Data.ParseApiFormat(jsonData["deck_format"].ToInt());
_userRank[(int)format].Initialize(jsonData, format);
}
}
}
public bool IsAcquiredSleeve(long sleeveId)
{
return _acquiredSleeveList.Contains(sleeveId);
@@ -500,95 +87,4 @@ public class LoadDetail
{
return _favoriteDegreeList.Contains(degreeId);
}
public bool IsExistLoginBonusData()
{
return NormalLoginBonusData != null;
}
public bool IsExistFreeCardPackBoxData()
{
return FreeCardPackData != null;
}
public void SettingRecoveryStatus(int battleRecovery, int roomRecovery)
{
BattleRecoveryStatus = battleRecovery;
RoomRecoveryStatus = roomRecovery;
}
public void InitRecoveryStatus()
{
BattleRecoveryStatus = 0;
RoomRecoveryStatus = 0;
}
public List<RankInfo> GetRankInfoRawList(Format format)
{
if (format == Format.Crossover)
{
return Data.Crossover.GetRankInfoRawList();
}
return RankInfoList;
}
public RankInfo GetRankInfo(Format format, int rankId)
{
if (format == Format.Crossover)
{
return Data.Crossover.GetRankInfo(rankId);
}
return RankInfoList[rankId - 1];
}
public bool IsStartRank(Format format, int rankId)
{
if (format == Format.Crossover)
{
return Data.Crossover.IsStartRank(rankId);
}
return RankInfoList[0].RankId == rankId;
}
private void ParseLoginBonusInfo(JsonData info)
{
if (info == null)
{
return;
}
ICollection<string> keys = info.Keys;
if (keys.Contains("normal"))
{
JsonData jsonData = info["normal"];
if (jsonData != null && jsonData.Count > 0)
{
NormalLoginBonusData = new NormalData(jsonData);
}
}
if (keys.Contains("total"))
{
JsonData jsonData2 = info["total"];
if (jsonData2 != null && jsonData2.Count > 0)
{
ContinuousLoginBonusData = new ContinuousData(jsonData2);
}
}
if (!keys.Contains("campaign"))
{
return;
}
JsonData jsonData3 = info["campaign"];
if (jsonData3 != null)
{
for (int i = 0; i < jsonData3.Count; i++)
{
SpecialLoginBonusDataList.Add(new SpecialData(jsonData3[i]));
}
}
}
public void InitializeAfterMasterLoading()
{
Data.MyRotationAllInfo.InitializeText();
}
}