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>
148 lines
6.2 KiB
C#
148 lines
6.2 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using LitJson;
|
|
using Wizard.AutoTest;
|
|
using Wizard.Story;
|
|
|
|
namespace Wizard.Battle.Recovery;
|
|
|
|
public class SetupConditionInfo : BattleConditionInfo
|
|
{
|
|
public bool DidPlayerGoFirst { get; private set; }
|
|
|
|
public int RandomSeed { get; private set; }
|
|
|
|
public int BackGroundId { get; private set; }
|
|
|
|
public string BgmId { get; private set; }
|
|
|
|
public bool HasMulliganInfo { get; private set; }
|
|
|
|
public IEnumerable<int> PlayerMulliganReplaceCards { get; private set; }
|
|
|
|
public IEnumerable<int> PlayerMulliganCompleteCards { get; private set; }
|
|
|
|
public IEnumerable<int> EnemyMulliganReplaceCards { get; private set; }
|
|
|
|
public IEnumerable<int> EnemyMulliganCompleteCards { get; private set; }
|
|
|
|
public int PracticeDifficultyDegreeId { get; private set; }
|
|
|
|
public bool IsPrebuildDeck { get; private set; }
|
|
|
|
public bool IsTrialDeck { get; private set; }
|
|
|
|
public bool IsDefaultDeck { get; private set; }
|
|
|
|
public int QuestStageId { get; private set; }
|
|
|
|
public int QuestEnemyAiId { get; private set; }
|
|
|
|
public int QuestEnemyEmblemId { get; private set; }
|
|
|
|
public int QuestEnemyDegreeId { get; private set; }
|
|
|
|
public int RecoveryPoint { get; private set; }
|
|
|
|
public List<BossRushSpecialSkill> QuestPlayerSkillList { get; private set; }
|
|
|
|
public BossRushSpecialSkill QuestEnemySkill { get; private set; }
|
|
|
|
public int QuestMaxBattleCount { get; private set; }
|
|
|
|
public int QuestCurrentWinCount { get; private set; }
|
|
|
|
public int QuestEnemyEmotionOverride { get; private set; }
|
|
|
|
public int QuestPlayerEmotionOverride { get; private set; }
|
|
|
|
public bool QuestIsExtra { get; private set; }
|
|
|
|
public bool QuestIsMockBattle { get; private set; }
|
|
|
|
public int QuestExtraDeckScheduleId { get; private set; }
|
|
|
|
public BattleManagerBase.MissionNecessaryInformation MissionNecessaryInformation { get; private set; } = new BattleManagerBase.MissionNecessaryInformation(new Dictionary<string, string>());
|
|
|
|
public StoryRecoveryData StoryRecoveryData { get; }
|
|
|
|
public bool ScenarioDeckIsPreBuild { get; private set; }
|
|
|
|
public bool ScenarioDeckIsTrial { get; private set; }
|
|
|
|
public SetupConditionInfo(JsonData jsonData, DataMgr.BattleType battleType)
|
|
: base(jsonData, useDefaultInPlayCardValue: true)
|
|
{
|
|
DidPlayerGoFirst = jsonData.ToBooleanOrDefault("player_start_turn", defaultBoolean: true);
|
|
RandomSeed = jsonData.ToIntOrDefault("random_seed", 0);
|
|
BackGroundId = jsonData.ToIntOrDefault("background_id", -1);
|
|
BgmId = jsonData.ToStringOrDefault("bgm_id", "NONE");
|
|
HasMulliganInfo = jsonData.HasKey("player_mulligan_abandon");
|
|
PracticeDifficultyDegreeId = jsonData.ToIntOrDefault("practice_difficulty_degree_id", 400001);
|
|
IsPrebuildDeck = jsonData.ToBooleanOrDefault("is_prebuild_deck", defaultBoolean: false);
|
|
IsTrialDeck = jsonData.ToBooleanOrDefault("is_trial_deck", defaultBoolean: false);
|
|
IsDefaultDeck = jsonData.ToBooleanOrDefault("is_default_deck", defaultBoolean: false);
|
|
QuestStageId = jsonData.ToIntOrDefault("quest_stage_id", 0);
|
|
QuestEnemyAiId = jsonData.ToIntOrDefault("quest_enemy_ai_id", 0);
|
|
QuestEnemyEmblemId = jsonData.ToIntOrDefault("quest_enemy_emblem_id", 0);
|
|
QuestEnemyDegreeId = jsonData.ToIntOrDefault("quest_enemy_degree_id", 0);
|
|
QuestEnemyEmotionOverride = jsonData.ToIntOrDefault("quest_enemy_emotion_override", 0);
|
|
QuestPlayerEmotionOverride = jsonData.ToIntOrDefault("quest_player_emotion_override", 0);
|
|
QuestIsExtra = jsonData.ToBooleanOrDefault("quest_is_extra", defaultBoolean: false);
|
|
QuestIsMockBattle = jsonData.ToBooleanOrDefault("quest_is_mock_battle", defaultBoolean: false);
|
|
QuestExtraDeckScheduleId = jsonData.ToIntOrDefault("quest_extra_deck_schedule_id", 0);
|
|
if ((battleType == DataMgr.BattleType.Story || DataMgr.IsQuestBattleType(battleType)) && jsonData.HasKey("story_special_battle_player_attach_skill"))
|
|
{
|
|
/* Pre-Phase-5b: SetSpecialBattleSetting is Story/Quest-only; headless is PvP-only */
|
|
}
|
|
if (battleType == DataMgr.BattleType.BossRushQuest)
|
|
{
|
|
RecoveryPoint = jsonData.ToIntOrDefault("recovery_point", 0);
|
|
QuestPlayerSkillList = (from d in jsonData.ToJsonDataCollection("quest_player_skill_list")
|
|
select new BossRushSpecialSkill(d)).ToList();
|
|
QuestEnemySkill = new BossRushSpecialSkill(jsonData.ToObjectOrNull("quest_enemy_skill"));
|
|
QuestMaxBattleCount = jsonData.ToIntOrDefault("quest_max_battle_count", 0);
|
|
QuestCurrentWinCount = jsonData.ToIntOrDefault("quest_current_win_count", 0);
|
|
}
|
|
if (battleType == DataMgr.BattleType.SecretBossQuest)
|
|
{
|
|
QuestPlayerSkillList = (from d in jsonData.ToJsonDataCollection("quest_player_skill_list")
|
|
select new BossRushSpecialSkill(d)).ToList();
|
|
QuestEnemySkill = new BossRushSpecialSkill(jsonData.ToObjectOrNull("quest_enemy_skill"));
|
|
}
|
|
if (jsonData.HasKey("mission_necessary_information"))
|
|
{
|
|
Dictionary<string, string> dictionary = new Dictionary<string, string>();
|
|
foreach (string key in jsonData["mission_necessary_information"].Keys)
|
|
{
|
|
dictionary.Add(key, jsonData["mission_necessary_information"].ToStringOrDefault(key, string.Empty));
|
|
}
|
|
MissionNecessaryInformation = new BattleManagerBase.MissionNecessaryInformation(dictionary);
|
|
}
|
|
switch (battleType)
|
|
{
|
|
case DataMgr.BattleType.Practice:
|
|
/* Pre-Phase-5b: Practice3DfieldId is Practice-only; headless is PvP-only */
|
|
break;
|
|
case DataMgr.BattleType.Story:
|
|
StoryRecoveryData = new StoryRecoveryData(jsonData);
|
|
ScenarioDeckIsPreBuild = jsonData.ToBooleanOrDefault("scenario_deck_is_pre_build", defaultBoolean: false);
|
|
ScenarioDeckIsTrial = jsonData.ToBooleanOrDefault("scenario_deck_is_trial", defaultBoolean: false);
|
|
break;
|
|
}
|
|
if (HasMulliganInfo)
|
|
{
|
|
PlayerMulliganReplaceCards = ParseJsonDataIndexes(jsonData, "player_mulligan_abandon");
|
|
PlayerMulliganCompleteCards = ParseJsonDataIndexes(jsonData, "player_mulligan_complete");
|
|
EnemyMulliganReplaceCards = ParseJsonDataIndexes(jsonData, "enemy_mulligan_abandon");
|
|
EnemyMulliganCompleteCards = ParseJsonDataIndexes(jsonData, "enemy_mulligan_complete");
|
|
}
|
|
}
|
|
|
|
private IEnumerable<int> ParseJsonDataIndexes(JsonData jsonData, string keyName)
|
|
{
|
|
return from j in jsonData.ToJsonDataCollection(keyName)
|
|
select int.Parse(j.ToString().Substring(1));
|
|
}
|
|
}
|