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

@@ -79,31 +79,14 @@ public class AchievementWindowBase : MonoBehaviour
[SerializeField]
private UILabel _labelTopRight;
[SerializeField]
private UILabel _missionStartTime;
[SerializeField]
private UILabel _missionTimeOver;
[SerializeField]
private UILabel _applyFinish;
private const int ACHIEVEMENT_STARS_MAX = 5;
private ResourceHandler _resourceHandler;
private const string SPRITE_PREFIX_BUTTON_BLUE = "btn_common_02_s_";
private int _viewMailId;
private QuestRewardInfo _questRewardInfo;
private Action _onReceivceAchievementSuccess;
private CrossoverRewardInfo _crossoverRewardInfo;
private const int BINGO_MISSION_SPRITE_WIDTH = 752;
private void Awake()
{
LabelDetailBtn.text = Data.SystemText.Get("Common_0022");
@@ -117,11 +100,6 @@ public class AchievementWindowBase : MonoBehaviour
UIManager.SetObjectToGrey(goButtonReward, typeBase == AchievementType.Nonattainment || typeBase == AchievementType.PointRunning);
}
public void SetActiveGaugeUI(bool isActive)
{
GaugeUI.gameObject.SetActive(isActive);
}
public void OnRewardClick()
{
AchievementReceiveRewardTask achievementReceiveRewardTask = new AchievementReceiveRewardTask();
@@ -129,14 +107,6 @@ public class AchievementWindowBase : MonoBehaviour
StartCoroutine(Toolbox.NetworkManager.Connect(achievementReceiveRewardTask, OnRequestRewardAchievement, BaseTask.OnRequestFailed, BaseTask.OnFailedErrorCode));
}
public void OnDetail()
{
DialogBase dialogBase = UIManager.GetInstance().CreateDialogClose();
dialogBase.SetTitleLabel(Data.SystemText.Get("Mission_0007"));
dialogBase.SetText(strAchievementData);
dialogBase.SetButtonLayout(DialogBase.ButtonLayout.CloseBtn);
}
private void OnRequestRewardAchievement(NetworkTask.ResultCode error)
{
OnRequestReward(error, Data.MissionInfo.data.total_reward_list);
@@ -227,7 +197,7 @@ public class AchievementWindowBase : MonoBehaviour
component.onClick.Clear();
component.onClick.Add(new EventDelegate(delegate
{
GameMgr.GetIns().GetSoundMgr().PlaySe(Se.TYPE.SYS_COMMON_BUTTON);
OnRewardClick();
}));
}
@@ -260,49 +230,6 @@ public class AchievementWindowBase : MonoBehaviour
}
}
public void SetCrossoverReward(CrossoverRewardInfo reward, AchievementType type, ResourceHandler resourceHandler, Action onReceiveReward)
{
_crossoverRewardInfo = reward;
_resourceHandler = resourceHandler;
SetType(type);
string texName = UserGoods.GetUserGoodsImageName((UserGoods.Type)reward.RewardType, reward.RewardDetailId);
string assetTypePath = Toolbox.ResourcesManager.GetAssetTypePath(texName, ResourcesManager.AssetLoadPathType.Item);
_resourceHandler.Add(assetTypePath, delegate
{
if (reward.RewardDetailId == _crossoverRewardInfo.RewardDetailId)
{
string assetTypePath2 = Toolbox.ResourcesManager.GetAssetTypePath(texName, ResourcesManager.AssetLoadPathType.Item, isfetch: true);
achievementIconTexture.mainTexture = Toolbox.ResourcesManager.LoadObject<Texture>(assetTypePath2);
}
});
RankInfo rankInfo = Data.Load.data.GetRankInfo(Format.Crossover, reward.Rank);
labelAchievementTitle.text = Data.SystemText.Get("Profile_0042", Data.SystemText.Get(rankInfo.rank_name));
labelAchievementData.text = ReceiveReward.getTitle((UserGoods.Type)reward.RewardType, reward.RewardDetailId, reward.RewardCount);
GaugeUI.gameObject.SetActive(value: false);
UIButton component = goButtonReward.GetComponent<UIButton>();
component.GetComponentInChildren<UILabel>().text = Data.SystemText.Get("Mail_0023");
goButtonReward.gameObject.SetActive(type != AchievementType.PointReceived);
UIManager.SetObjectToGrey(goButtonReward, type != AchievementType.PointClear);
component.onClick.Clear();
component.onClick.Add(new EventDelegate(delegate
{
GameMgr.GetIns().GetSoundMgr().PlaySe(Se.TYPE.SYS_COMMON_BUTTON);
ReceiveCrossoverReward(reward.RewardId, onReceiveReward);
}));
CopyAnchor(_labelTopRight.rightAnchor, labelAchievementTitle.rightAnchor);
}
private void ReceiveCrossoverReward(int rewardId, Action onReceiveReward)
{
CrossoverReceiveRankRewardTask task = new CrossoverReceiveRankRewardTask();
task.SetParameter(rewardId);
StartCoroutine(Toolbox.NetworkManager.Connect(task, delegate
{
onReceiveReward.Call();
DialogCreator.CreateRewardReceiveDialog(task.ReceivedRewardList);
}));
}
public void SetQuestPoint(QuestRewardInfo reward, AchievementType type, ResourceHandler resourceHandler, Action onRequestRewardPointCallBack)
{
_questRewardInfo = reward;
@@ -329,7 +256,7 @@ public class AchievementWindowBase : MonoBehaviour
component.onClick.Clear();
component.onClick.Add(new EventDelegate(delegate
{
GameMgr.GetIns().GetSoundMgr().PlaySe(Se.TYPE.SYS_COMMON_BUTTON);
OnQuestPointReceive(reward.Id, onRequestRewardPointCallBack);
}));
GetComponent<UISprite>().spriteName = string.Empty;
@@ -378,7 +305,7 @@ public class AchievementWindowBase : MonoBehaviour
component.GetComponentInChildren<UILabel>().text = systemText.Get("Mission_0029");
component.onClick.Add(new EventDelegate(delegate
{
GameMgr.GetIns().GetSoundMgr().PlaySe(Se.TYPE.SYS_COMMON_BUTTON);
ChangeMission(mission.id, mission.mission_name, onChangeMissionSuccess);
}));
goButtonReward.SetActive(displayChange && !mission.default_flag);
@@ -452,9 +379,7 @@ public class AchievementWindowBase : MonoBehaviour
private bool SetMissionWait(UserMission mission)
{
MissionInfoTask missionInfoTask = GameMgr.GetIns().GetMissionInfoTask();
long num = (long)Time.realtimeSinceStartup - missionInfoTask.RequestTime;
long num2 = missionInfoTask.ServerTime + num;
long num = 0L; long num2 = 0L; // Pre-Phase-5b: MissionInfoTask headless-unreachable
TimeSpan timeSpan = TimeSpan.FromSeconds(mission.start_time - num2).Add(new TimeSpan(0, 1, 0));
int num3 = timeSpan.Hours;
int num4 = timeSpan.Minutes;
@@ -495,7 +420,7 @@ public class AchievementWindowBase : MonoBehaviour
_labelMissionPeriod.gameObject.SetActive(value: false);
return;
}
long nowUnixTime = GameMgr.GetIns().GetMissionInfoTask().NowUnixTime();
long nowUnixTime = 0L; // Pre-Phase-5b: headless has no MissionInfoTask
string remainingTime = ConvertTime.GetRemainingTime(TimeSpan.FromSeconds(mission.GetMissionPeriodSec(nowUnixTime)));
goButtonReward.gameObject.SetActive(value: false);
_labelMissionPeriod.gameObject.SetActive(value: true);
@@ -531,26 +456,6 @@ public class AchievementWindowBase : MonoBehaviour
}, BaseTask.OnRequestFailed, BaseTask.OnFailedErrorCode));
}
public void SetHistoryItem(ItemAcquireHistory item, bool enableSeparator, ResourceHandler resourceHandler)
{
_resourceHandler = resourceHandler;
SystemText systemText = Data.SystemText;
if (item.RewardType == 4)
{
ReceiveReward.SetTicket(item.RewardUserGoodsId, item.RewardCount, achievementIconTexture, labelAchievementTitle, _resourceHandler);
}
else
{
ReceiveReward.SetTexture((UserGoods.Type)item.RewardType, achievementIconTexture, _resourceHandler);
labelAchievementTitle.text = ReceiveReward.getTitle((UserGoods.Type)item.RewardType, item.RewardUserGoodsId, item.RewardCount);
}
labelAchievementData.text = item.Message;
labelAchievementCount.text = systemText.Get("Mail_0043", ConvertTime.ToLocal(item.AcquireTime));
GaugeUI.gameObject.SetActive(value: false);
goButtonReward.SetActive(value: false);
_Separator.gameObject.SetActive(enableSeparator);
}
public void SetMail(MailData mail, Action<int, int> OnReadMail, ResourceHandler handler)
{
_resourceHandler = handler;
@@ -618,160 +523,4 @@ public class AchievementWindowBase : MonoBehaviour
{
UIManager.SetObjectToGrey(goButtonReward, b: true);
}
public void SetLottery(LotteryMissionData lotteryData, bool needCeparator)
{
string userGoodsImageName = UserGoods.GetUserGoodsImageName(lotteryData.UserGoodsType, lotteryData.ItemId);
string assetTypePath = Toolbox.ResourcesManager.GetAssetTypePath(userGoodsImageName, ResourcesManager.AssetLoadPathType.Item, isfetch: true);
base.gameObject.GetComponent<UISprite>().width = 800;
achievementIconTexture.mainTexture = Toolbox.ResourcesManager.LoadObject<Texture>(assetTypePath);
_Separator.gameObject.SetActive(needCeparator);
labelAchievementTitle.text = lotteryData.MissionTitle;
if (lotteryData.UserGoodsType == UserGoods.Type.Item)
{
labelAchievementData.text = ReceiveReward.SetTicketTitle(lotteryData.ItemId, lotteryData.ItemCount);
}
else
{
labelAchievementData.text = ReceiveReward.getTitle(lotteryData.UserGoodsType, lotteryData.ItemId, lotteryData.ItemCount);
}
goButtonReward.SetActive(value: false);
if (lotteryData.StartTime.Second > 0)
{
_missionStartTime.text = Data.SystemText.Get("Mission_0077", lotteryData.StartTime.LocalTime);
_missionStartTime.gameObject.SetActive(value: true);
}
else
{
_labelMissionPeriod.text = lotteryData.EndTime.GetShowText("Mission_0062", "Mission_0060", "Mission_0061");
_labelMissionPeriod.gameObject.SetActive(value: true);
}
CopyAnchor(_labelTopRight.rightAnchor, labelAchievementTitle.rightAnchor);
_applyFinish.gameObject.SetActive(lotteryData.IsCleared);
_labelMissionPeriod.gameObject.SetActive(!lotteryData.IsCleared && !lotteryData.IsTimeOver);
_missionTimeOver.gameObject.SetActive(lotteryData.IsTimeOver);
GaugeLabel.text = lotteryData.MissionCurrent + "/" + lotteryData.MissionMax;
GaugeUI.Value = lotteryData.MissionRatio;
bool active = true;
if (lotteryData.IsCleared || lotteryData.MissionMax == 0)
{
active = false;
}
GaugeUI.gameObject.SetActive(active);
}
public void SetBingoMission(BingoInfoTask.BingoMissionData missionData, bool needCeparator, ResourceHandler handler)
{
_resourceHandler = handler;
base.gameObject.GetComponent<UISprite>().width = 752;
base.gameObject.GetComponent<UISprite>().enabled = false;
alreadyReceived.gameObject.SetActive(missionData.IsCleared);
labelAchievementData.text = ReceiveReward.getTitle((UserGoods.Type)missionData.Reward.reward_type, missionData.Reward.rewardUserGoodsId, missionData.Reward.reward_count);
string textureName = UserGoods.GetUserGoodsImageName((UserGoods.Type)missionData.Reward.reward_type, missionData.Reward.rewardUserGoodsId);
string assetTypePath = Toolbox.ResourcesManager.GetAssetTypePath(textureName, ResourcesManager.AssetLoadPathType.Item);
_resourceHandler.Add(assetTypePath, delegate
{
string assetTypePath2 = Toolbox.ResourcesManager.GetAssetTypePath(textureName, ResourcesManager.AssetLoadPathType.Item, isfetch: true);
achievementIconTexture.mainTexture = Toolbox.ResourcesManager.LoadObject<Texture>(assetTypePath2);
});
CopyAnchor(_labelTopRight.rightAnchor, labelAchievementTitle.rightAnchor);
_titleLine.SetAnchor((GameObject)null);
_titleLine.spriteName = "quest_line_05";
_titleLine.SetDimensions(610, 2);
_Separator.spriteName = "quest_line_02";
_Separator.gameObject.SetActive(needCeparator);
goButtonReward.SetActive(value: false);
GaugeLabel.text = missionData.MissionCurrent + "/" + missionData.MissionMax;
GaugeUI.Value = missionData.MissionRatio;
labelAchievementTitle.text = missionData.MissionTitle;
}
public void SetBingoRewardDetails(ReceivedReward reward, bool needCeparator, ResourceHandler handler)
{
_resourceHandler = handler;
base.gameObject.GetComponent<UISprite>().width = 752;
base.gameObject.GetComponent<UISprite>().enabled = false;
goButtonReward.SetActive(value: false);
_titleLine.SetAnchor((GameObject)null);
_titleLine.spriteName = "quest_line_05";
_titleLine.SetDimensions(610, 2);
_Separator.spriteName = "quest_line_02";
_Separator.gameObject.SetActive(needCeparator);
labelAchievementTitle.text = string.Format(Data.SystemText.Get("Bingo_0004", reward.lineNum.ToString()));
labelAchievementData.text = ReceiveReward.getTitle((UserGoods.Type)reward.reward_type, reward.rewardUserGoodsId, reward.reward_count);
GaugeUI.gameObject.SetActive(value: false);
string textureName = UserGoods.GetUserGoodsImageName((UserGoods.Type)reward.reward_type, reward.rewardUserGoodsId);
string assetTypePath = Toolbox.ResourcesManager.GetAssetTypePath(textureName, ResourcesManager.AssetLoadPathType.Item);
_resourceHandler.Add(assetTypePath, delegate
{
string assetTypePath2 = Toolbox.ResourcesManager.GetAssetTypePath(textureName, ResourcesManager.AssetLoadPathType.Item, isfetch: true);
achievementIconTexture.mainTexture = Toolbox.ResourcesManager.LoadObject<Texture>(assetTypePath2);
});
}
public void SetBingoSideBarRewards(string lineNum, ReceivedReward reward, bool isCleared, bool needCeparator, ResourceHandler handler)
{
_resourceHandler = handler;
_Separator.gameObject.SetActive(needCeparator);
goButtonReward.SetActive(value: false);
labelAchievementTitle.text = string.Format(Data.SystemText.Get("Bingo_0004", lineNum));
labelAchievementData.text = ReceiveReward.getTitle((UserGoods.Type)reward.reward_type, reward.rewardUserGoodsId, reward.reward_count);
alreadyReceived.gameObject.SetActive(isCleared);
string textureName = UserGoods.GetUserGoodsImageName((UserGoods.Type)reward.reward_type, reward.rewardUserGoodsId);
string assetTypePath = Toolbox.ResourcesManager.GetAssetTypePath(textureName, ResourcesManager.AssetLoadPathType.Item);
_resourceHandler.Add(assetTypePath, delegate
{
string assetTypePath2 = Toolbox.ResourcesManager.GetAssetTypePath(textureName, ResourcesManager.AssetLoadPathType.Item, isfetch: true);
achievementIconTexture.mainTexture = Toolbox.ResourcesManager.LoadObject<Texture>(assetTypePath2);
});
}
public void SetPracticePuzzleMission(PracticePuzzleMissionData mission, ResourceHandler resourceHandler, bool canChangeMissions, bool enableSeparator, bool displayChange, Action onChangeMissionSuccess = null)
{
_resourceHandler = resourceHandler;
_Separator.gameObject.SetActive(enableSeparator);
goButtonReward.SetActive(value: false);
_ = Data.SystemText;
if (mission.UserGoodsType == UserGoods.Type.Item)
{
ReceiveReward.SetTicket(mission.ItemId, mission.ItemCount, achievementIconTexture, labelAchievementData, _resourceHandler);
}
else
{
ReceiveReward.SetTexture(mission.UserGoodsType, mission.ItemId, achievementIconTexture, _resourceHandler);
labelAchievementData.text = ReceiveReward.getTitle(mission.UserGoodsType, mission.ItemId, mission.ItemCount);
}
labelAchievementTitle.text = mission.Name;
int totalMissionCount = mission.TotalMissionCount;
bool flag = totalMissionCount > 0;
GaugeUI.gameObject.SetActive(flag);
if (flag)
{
int num = ((mission.TotalMissionCount > mission.CurrentClearCount) ? mission.CurrentClearCount : mission.TotalMissionCount);
GaugeLabel.text = num + "/" + totalMissionCount;
if (num != 0)
{
float value = (float)num / (float)totalMissionCount;
GaugeUI.Value = value;
}
else
{
GaugeUI.Value = 0f;
}
}
alreadyReceived.gameObject.SetActive(mission.IsCleared);
labelAchievementCount.gameObject.SetActive(value: false);
CopyAnchor(_labelTopRight.rightAnchor, labelAchievementTitle.rightAnchor);
}
public void SetRedEtherMission(RedEtherCampaignRewardData rewardData, ResourceHandler resourceHandler)
{
_resourceHandler = resourceHandler;
goButtonReward.SetActive(value: false);
ReceiveReward.SetTexture(rewardData.UserGoodsType, 0L, achievementIconTexture, _resourceHandler);
labelAchievementData.text = ReceiveReward.getTitle(rewardData.UserGoodsType, 0L, rewardData.ItemCount);
labelAchievementTitle.text = rewardData.MissionText;
alreadyReceived.gameObject.SetActive(rewardData.IsCleared);
GaugeUI.gameObject.SetActive(value: false);
}
}