Files
SVSimServer/SVSim.BattleEngine/Engine/Wizard/PlayerStaticData.cs
gamer147 2d9a6eea4b 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>
2026-07-03 19:18:54 -04:00

545 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using Cute;
using LitJson;
using UnityEngine;
// TODO(engine-cleanup-pass2): 74 of 75 methods unrun in baseline
// Type: Wizard.PlayerStaticData
// See data_dumps/reports/engine-cleanup/live-methods.baseline.txt
namespace Wizard;
public class PlayerStaticData : MonoBehaviour
{
public class UserTex
{
public enum Type
{
Emblem,
RankIcon,
Country
}
public Texture[] m_loadedList;
public List<UITexture>[] m_requiredAttachList;
public List<string> m_unloadList = new List<string>();
private readonly Type m_type;
private readonly int m_sizeDiffNum;
public int m_loadingCnt;
public Format Format;
private List<string> _rankIconPathList = new List<string>();
public UserTex(Type type)
{
m_type = type;
m_sizeDiffNum = GetSizeDiffNum(m_type);
m_loadedList = new Texture[m_sizeDiffNum];
m_requiredAttachList = new List<UITexture>[m_sizeDiffNum];
for (int i = 0; i < m_requiredAttachList.Length; i++)
{
m_requiredAttachList[i] = new List<UITexture>();
}
}
public void LoadTexture()
{
ResourcesManager resourcesManager = Toolbox.ResourcesManager;
List<string> pathList = null;
if (m_type == Type.RankIcon)
{
pathList = GetRankTexturePath();
for (int i = 0; i < m_sizeDiffNum; i++)
{
m_loadedList[i] = null;
}
}
else
{
for (int j = 0; j < m_sizeDiffNum; j++)
{
m_loadedList[j] = null;
m_loadingCnt++;
string texturePath = GetTexturePath(GetTextureName(), j);
if (pathList == null)
{
pathList = new List<string>();
}
pathList.Add(texturePath);
}
}
resourcesManager.StartCoroutine_LoadAssetGroupSync(pathList, delegate
{
m_loadingCnt = 0;
UpdateTexture();
UnloadTexture();
if (m_type == Type.RankIcon)
{
_rankIconPathList.AddRange(pathList);
}
});
}
private List<string> GetRankTexturePath()
{
List<string> list = new List<string>();
GetRankTexturePathSub(Format.Unlimited, list);
GetRankTexturePathSub(Format.Rotation, list);
m_loadingCnt += list.Count;
return list;
}
private static void GetRankTexturePathSub(Format format, List<string> list)
{
list.Add(Toolbox.ResourcesManager.GetAssetTypePath(UserRank(format).ToString("00"), ResourcesManager.AssetLoadPathType.RankIcon_S));
list.Add(Toolbox.ResourcesManager.GetAssetTypePath(UserRank(format).ToString("00"), ResourcesManager.AssetLoadPathType.RankIcon_L));
}
public void ReLoadTexture(string oldId, string newId)
{
if (m_type != Type.RankIcon && !string.IsNullOrEmpty(oldId) && !m_unloadList.Contains(oldId))
{
m_unloadList.Add(oldId);
}
if (!string.IsNullOrEmpty(newId))
{
m_unloadList.Remove(newId);
LoadTexture();
return;
}
for (int i = 0; i < m_sizeDiffNum; i++)
{
m_loadedList[i] = null;
}
}
public void AttachTexture(UITexture uiTexture, int size)
{
if (m_loadingCnt == 0)
{
uiTexture.mainTexture = m_loadedList[size];
}
else
{
m_requiredAttachList[size].Add(uiTexture);
}
}
private void UpdateTexture()
{
ResourcesManager resourcesManager = Toolbox.ResourcesManager;
for (int i = 0; i < m_sizeDiffNum; i++)
{
Texture mainTexture = (m_loadedList[i] = resourcesManager.LoadObject(GetTexturePath(GetTextureName(), i, isfetch: true)) as Texture);
List<UITexture> list = m_requiredAttachList[i];
foreach (UITexture item in list)
{
item.mainTexture = mainTexture;
}
list.Clear();
}
}
private void UnloadTexture()
{
List<string> list = new List<string>();
foreach (string unload in m_unloadList)
{
for (int i = 0; i < m_sizeDiffNum; i++)
{
list.Add(GetTexturePath(unload, i));
}
}
m_unloadList.Clear();
list.AddRange(_rankIconPathList);
_rankIconPathList.Clear();
Toolbox.ResourcesManager.RemoveAssetGroup(list);
}
private static int GetSizeDiffNum(Type type)
{
return type switch
{
Type.Emblem => 2,
Type.RankIcon => 2,
Type.Country => 2,
_ => 0,
};
}
private string GetTexturePath(string name, int sizeId, bool isfetch = false)
{
int num = 0;
switch (m_type)
{
case Type.Emblem:
num = 42;
break;
case Type.RankIcon:
num = 49;
break;
case Type.Country:
num = 51;
break;
}
return Toolbox.ResourcesManager.GetAssetTypePath(name, (ResourcesManager.AssetLoadPathType)(num + sizeId), isfetch);
}
private string GetTextureName()
{
return m_type switch
{
Type.Emblem => UserEmblemID.ToString(),
Type.RankIcon => UserRank(Format).ToString("00"), // Pre-Phase-5b: headless has no display-high-rank flag
Type.Country => UserCountryCode,
_ => "",
};
}
}
public enum EmblemTexSize
{
S}
public enum RankTexSize
{
S,
L }
public enum CountryTexSize
{
S}
public enum AgreementState
{
Agreed,
NotAgree,
Reset
}
public enum LootBoxType
{
GACHA,
TWOPICK,
SEALED,
COLOSSEUM,
COMPETITION,
SPECIAL_CRYSTAL }
public static AgreementState _tosAgreementState = AgreementState.NotAgree;
public static AgreementState _privacyPolicyAgreementState = AgreementState.NotAgree;
private static UserTex UserEmblemTexture = new UserTex(UserTex.Type.Emblem);
private static UserTex UserCountryTexture = new UserTex(UserTex.Type.Country);
private static UserTex UserRankTexture = new UserTex(UserTex.Type.RankIcon);
public static TimeData UserTime = new TimeData();
public static int UserViewerID => 0; // Pre-Phase-5 chunk 39: aliased Certification.ViewerId — client-only UI accessor, dead headless
public static string UserRegionCode => PlayerPrefsWrapper.GetValue(PlayerPrefsWrapper.CURRENT_REGION_CODE);
public static AgreementState KorAuthorityAgreementState { get; set; } = AgreementState.NotAgree;
public static int UserCrystalCount
{
get
{
if (Data.Load.data == null)
{
return 0;
}
return Data.Load.data._userCrystalCount.total_crystal;
}
set
{
Data.Load.data._userCrystalCount.total_crystal = value;
}
}
public static int UserRupyCount
{
get
{
if (Data.Load.data == null)
{
return 0;
}
return Data.Load.data._userCrystalCount.rupy;
}
set
{
Data.Load.data._userCrystalCount.rupy = value;
}
}
public static int UserRedEtherCount
{
get
{
if (Data.Load.data == null)
{
return 0;
}
return Data.Load.data._userCrystalCount.red_ether;
}
set
{
Data.Load.data._userCrystalCount.red_ether = value;
}
}
public static int UserSpotCardPointCount
{
get
{
if (Data.Load.data == null)
{
return 0;
}
return Data.Load.data._userCrystalCount._spotcardPoint;
}
set
{
Data.Load.data._userCrystalCount._spotcardPoint = value;
}
}
public static int UserOrbNum
{
get
{
return GetItemNum(1000);
}
set
{
UpdateItemNum(1000, value);
}
}
public static long UserEmblemID
{
get
{
return Data.Load.data._userInfo.selected_emblem_id;
}
set
{
UserInfo userInfo = Data.Load.data._userInfo;
long selected_emblem_id = userInfo.selected_emblem_id;
if (selected_emblem_id != value)
{
userInfo.selected_emblem_id = value;
UserEmblemTexture.ReLoadTexture(selected_emblem_id.ToString(), value.ToString());
}
}
}
public static string UserCountryCode
{
get
{
return Data.Load.data._userInfo.country_code;
}
set
{
UserInfo userInfo = Data.Load.data._userInfo;
string country_code = userInfo.country_code;
if (country_code != value)
{
userInfo.country_code = value;
UserCountryTexture.ReLoadTexture(country_code, value);
}
}
}
public static bool IsLootBoxRegulation(LootBoxType type)
{
return Data.Load.data.LootBoxReguration[(int)type];
}
public static int GetItemNum(int itemId)
{
if (Data.Load.data._userItemDict == null)
{
return 0;
}
if (Data.Load.data._userItemDict.ContainsKey(itemId))
{
return Data.Load.data._userItemDict[itemId];
}
return 0;
}
public static void UpdateItemNum(int itemId, int num)
{
if (Data.Load.data._userItemDict == null)
{
Data.Load.data._userItemDict = new Dictionary<int, int>();
}
if (Data.Load.data._userItemDict.ContainsKey(itemId))
{
Data.Load.data._userItemDict[itemId] = num;
}
else
{
Data.Load.data._userItemDict.Add(itemId, num);
}
}
public static void Clear()
{
UserEmblemTexture = new UserTex(UserTex.Type.Emblem);
UserCountryTexture = new UserTex(UserTex.Type.Country);
UserRankTexture = new UserTex(UserTex.Type.RankIcon);
}
public static int UserRank(Format inFormat)
{
if (inFormat == Format.PreRotation)
{
inFormat = Format.Rotation;
}
if (Data.Load.data == null)
{
return 0;
}
return Data.Load.data._userRank[(int)inFormat].rank;
}
public static int UserRankHighAllFormat()
{
int val = UserRank(Format.Rotation);
int val2 = UserRank(Format.Unlimited);
return Math.Max(val, val2);
}
public static void LoadUserRankTexture(Format inFormat)
{
UserRankTexture.Format = inFormat;
UserRankTexture.LoadTexture();
}
public static void AttachUserRankTexture(UITexture uiTexture, RankTexSize size)
{
UserRankTexture.AttachTexture(uiTexture, (int)size);
}
public static bool IsMasterRankCurrentFormat()
{
return IsMasterRank(Data.CurrentFormat);
}
public static bool IsMasterRank(Format inFormat)
{
return Data.Load.data._userRank[(int)inFormat].is_master_rank;
}
public static void UpdateHaveUserGoodsNumByJsonData(JsonData userGoodsList)
{
for (int i = 0; i < userGoodsList.Count; i++)
{
JsonData jsonData = userGoodsList[i];
UserGoods.Type type = (UserGoods.Type)jsonData["reward_type"].ToInt();
long userGoodsId = jsonData["reward_id"].ToLong();
int num = jsonData["reward_num"].ToInt();
UpdateHaveUserGoodsNum(type, userGoodsId, num);
}
}
public static void UpdateHaveUserGoodsNum(UserGoods.Type type, long userGoodsId, int num)
{
switch (type)
{
case UserGoods.Type.RedEther:
UserRedEtherCount = num;
break;
case UserGoods.Type.Crystal:
UserCrystalCount = num;
if (MyPageMenu.Instance != null)
{
MyPageMenu.Instance.UpdateCrystalCount();
}
break;
case UserGoods.Type.Item:
UpdateItemNum((int)userGoodsId, num);
break;
case UserGoods.Type.Card:
{
int cardId = (int)userGoodsId;
/* Pre-Phase-5b: headless has no user card data */
break;
}
case UserGoods.Type.SpotCard:
case UserGoods.Type.SpotCardOnlyLatestCardPack:
{
int cardId = (int)userGoodsId;
/* Pre-Phase-5b: headless has no spot card data */
break;
}
case UserGoods.Type.Rupy:
UserRupyCount = num;
if (MyPageMenu.Instance != null)
{
MyPageMenu.Instance.UpdateRupyCount();
}
break;
case UserGoods.Type.Skin:
/* Pre-Phase-5b: headless has no chara master */
break;
case UserGoods.Type.Sleeve:
Data.Master.SleeveMgr.Acquired(userGoodsId);
break;
case UserGoods.Type.Emblem:
Data.Master.EmblemMgr.Acquired(userGoodsId);
break;
case UserGoods.Type.Degree:
Data.Master.DegreeMgr.Acquired((int)userGoodsId);
break;
case UserGoods.Type.SpotCardPoint:
UserSpotCardPointCount = num;
break;
case UserGoods.Type.MyPageBG:
Data.Load.data.AcquiredMyPageBGList.Add(userGoodsId.ToString());
break;
case (UserGoods.Type)3:
case UserGoods.Type.FreeGachaCount:
break;
}
}
public static int GetHaveUserGoods(UserGoods.Type type, long userGoodsId)
{
int result = 0;
switch (type)
{
case UserGoods.Type.RedEther:
result = UserRedEtherCount;
break;
case UserGoods.Type.Crystal:
result = UserCrystalCount;
break;
case UserGoods.Type.Item:
result = GetItemNum((int)userGoodsId);
break;
case UserGoods.Type.Rupy:
result = UserRupyCount;
break;
case UserGoods.Type.SpotCardPoint:
result = UserSpotCardPointCount;
break;
}
return result;
}
}