Files
SVSimServer/SVSim.BattleEngine/Engine/Wizard/AIAttachEventToBattleModuleUtility.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

270 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using Wizard.Battle.View.Vfx;
namespace Wizard;
public static class AIAttachEventToBattleModuleUtility
{
public static void SetupEventWhenInitGame(BattlePlayerBase ally, BattlePlayerBase opponent, OperateMgr operateMgr, EnemyAI ai)
{
SetupOnAddCemeteryEvent(ally, opponent, ai);
SetupOnTurnStartComplete(ally, opponent, ai);
SetupOnTurnEndStart(ally, opponent, ai);
SetupOperateMgrEvent(operateMgr, ai);
}
private static void SetupOnAddCemeteryEvent(BattlePlayerBase ally, BattlePlayerBase opponent, EnemyAI ai)
{
ai.PlayerBattleEvent.AllyAddCemeteryEvent = delegate(BattleCardBase card, BattlePlayerBase.CEMETERY_TYPE cemeteryType, bool isOpen, SkillBase skill)
{
if (cemeteryType == BattlePlayerBase.CEMETERY_TYPE.NORMAL && !(card is NullBattleCard) && card.SelfBattlePlayer.ClassAndInPlayCardList.Contains(card) && card.HasSkillWhenDestroy && ai.LatestAction != null)
{
ai.LatestAction.CreateRandomTargetInformation(card, (SkillBase skillBase) => skillBase.IsWhenDestroySkill && RegisterTool.IsSkillRandom(skillBase));
}
};
ally.OnAddCemeteryEvent += ai.PlayerBattleEvent.AllyAddCemeteryEvent;
ai.PlayerBattleEvent.OpponentAddCemeteryEvent = delegate(BattleCardBase card, BattlePlayerBase.CEMETERY_TYPE cemeteryType, bool isOpen, SkillBase skill)
{
if (cemeteryType == BattlePlayerBase.CEMETERY_TYPE.NORMAL && !(card is NullBattleCard) && card.SelfBattlePlayer.ClassAndInPlayCardList.Contains(card))
{
RecordOpponentBarbarossa(card, ai);
if (card.HasSkillWhenDestroy && ai.LatestAction != null)
{
ai.LatestAction.CreateRandomTargetInformation(card, (SkillBase skillBase) => skillBase.IsWhenDestroySkill && RegisterTool.IsSkillRandom(skillBase));
}
}
};
opponent.OnAddCemeteryEvent += ai.PlayerBattleEvent.OpponentAddCemeteryEvent;
}
private static void SetupOnTurnStartComplete(BattlePlayerBase ally, BattlePlayerBase opponent, EnemyAI ai)
{
ai.PlayerBattleEvent.AllyTurnStartCompleteEvent = delegate
{
ai.UpdateAICurrentVirtualField();
AIVirtualField currentVirtualField = ai.CurrentVirtualField;
if (ai.LatestAction != null && ai.BeforeLatestActionField != null)
{
currentVirtualField.UpdateTagInformationFromLatestAction(ai.LatestAction, ai.BeforeLatestActionField);
}
ai.LatestAction = null;
ai.BeforeLatestActionField = null;
};
ally.OnTurnStartComplete = (Action)Delegate.Combine(ally.OnTurnStartComplete, ai.PlayerBattleEvent.AllyTurnStartCompleteEvent);
ai.PlayerBattleEvent.OpponentTurnStartCompleteEvent = delegate
{
if (!ai.IsBattleEnd)
{
if (ai.IsOpponentBarbarossaDestroyed)
{
ai.IsOpponentBarbarossaDestroyed = false;
}
ai.UpdateAICurrentVirtualField();
AIVirtualField currentVirtualField = ai.CurrentVirtualField;
if (ai.LatestAction != null && ai.BeforeLatestActionField != null)
{
currentVirtualField.UpdateTagInformationFromLatestAction(ai.LatestAction, ai.BeforeLatestActionField);
}
ai.LatestAction = null;
ai.BeforeLatestActionField = null;
}
};
opponent.OnTurnStartComplete = (Action)Delegate.Combine(opponent.OnTurnStartComplete, ai.PlayerBattleEvent.OpponentTurnStartCompleteEvent);
}
private static void SetupOnTurnEndStart(BattlePlayerBase ally, BattlePlayerBase opponent, EnemyAI ai)
{
ai.PlayerBattleEvent.AllySelfTurnEndEvent = delegate
{
ai.LatestAction = new AIRealActionInformation(AIOperationType.TURNEND, ally.Class, ally.Class, null);
ai.SaveBeforeLatestActionInformation();
for (int i = 0; i < ally.ClassAndInPlayCardList.Count; i++)
{
ai.LatestAction.CreateRandomTargetInformation(ally.ClassAndInPlayCardList[i], (SkillBase skill) => skill.OnSelfTurnEndStart != 0 && RegisterTool.IsSkillRandom(skill));
}
for (int num = 0; num < opponent.ClassAndInPlayCardList.Count; num++)
{
ai.LatestAction.CreateRandomTargetInformation(opponent.ClassAndInPlayCardList[num], (SkillBase skill) => skill.OnOpponentTurnEndStart != 0 && RegisterTool.IsSkillRandom(skill));
}
};
ally.OnTurnEndStart += ai.PlayerBattleEvent.AllySelfTurnEndEvent;
ai.PlayerBattleEvent.OpponentSelfTurnEndEvent = delegate
{
ai.LatestAction = new AIRealActionInformation(AIOperationType.TURNEND, opponent.Class, opponent.Class, null);
ai.SaveBeforeLatestActionInformation();
for (int i = 0; i < opponent.ClassAndInPlayCardList.Count; i++)
{
ai.LatestAction.CreateRandomTargetInformation(opponent.ClassAndInPlayCardList[i], (SkillBase skill) => skill.OnSelfTurnEndStart != 0 && RegisterTool.IsSkillRandom(skill));
}
for (int num = 0; num < ally.ClassAndInPlayCardList.Count; num++)
{
ai.LatestAction.CreateRandomTargetInformation(ally.ClassAndInPlayCardList[num], (SkillBase skill) => skill.OnOpponentTurnEndStart != 0 && RegisterTool.IsSkillRandom(skill));
}
};
opponent.OnTurnEndStart += ai.PlayerBattleEvent.OpponentSelfTurnEndEvent;
}
private static void SetupVirtualPairOnAfterPickCard(BattlePlayerPair pair, AIOperationSimulatorAccessor accessor)
{
pair.Self.OnAfterPickCard += delegate
{
accessor.UpdateCurrentField(pair, EnemyAI.EmptyPlayPtn);
};
pair.Opponent.OnAfterPickCard += delegate
{
accessor.UpdateCurrentField(pair, EnemyAI.EmptyPlayPtn);
};
}
private static void SetupOperateMgrEvent(OperateMgr operateMgr, EnemyAI ai)
{
SetupOperateMgrOnSetCardSuccess(operateMgr, ai);
SetupOperateMgrOnSetCardExecuted(operateMgr, ai);
SetupOperateMgrOnEvolveSuccess(operateMgr, ai);
SetupOperateMgrOnEvolveComplete(operateMgr, ai);
SetupOperateMgrOnBeforeAttack(operateMgr, ai);
SetupOperateMgrOnAttackExecuted(operateMgr, ai);
SetupOperateMgrOnBeforeFusion(operateMgr, ai);
SetupOperateMgrOnAfterFusion(operateMgr, ai);
}
private static void SetupOperateMgrOnSetCardSuccess(OperateMgr operateMgr, EnemyAI ai)
{
ai.OprMgrBattleEvent.OnSetCardSuccessEvent = delegate(BattleCardBase originalCard, BattleCardBase playCard, IEnumerable<BattleCardBase> selectedCards)
{
ai.LatestAction = new AIRealActionInformation(AIOperationType.PLAY, playCard, originalCard, selectedCards);
ai.SaveBeforeLatestActionInformation();
ai.LatestAction.CreateRandomTargetInformation(playCard, (SkillBase skill) => skill.IsWhenPlaySkill && RegisterTool.IsSkillRandom(skill));
};
operateMgr.OnSetCardSuccess += ai.OprMgrBattleEvent.OnSetCardSuccessEvent;
}
private static void SetupOperateMgrOnSetCardExecuted(OperateMgr operateMgr, EnemyAI ai)
{
ai.OprMgrBattleEvent.OnSetCardExecutedEvent = delegate
{
if (ai.IsBattleEnd)
{
return NullVfx.GetInstance();
}
ai.UpdateAICurrentVirtualField();
AIVirtualField currentVirtualField = ai.CurrentVirtualField;
if (ai.LatestAction != null && ai.BeforeLatestActionField != null)
{
currentVirtualField.UpdateTagInformationFromLatestAction(ai.LatestAction, ai.BeforeLatestActionField);
}
ai.LatestAction = null;
ai.BeforeLatestActionField = null;
return NullVfx.GetInstance();
};
operateMgr.OnSetCardExecuted += ai.OprMgrBattleEvent.OnSetCardExecutedEvent;
}
private static void RecordOpponentBarbarossa(BattleCardBase destroyCard, EnemyAI ai)
{
if (destroyCard.BaseParameter.BaseCardId == 106241020)
{
ai.IsOpponentBarbarossaDestroyed = true;
}
}
private static void SetupOperateMgrOnEvolveSuccess(OperateMgr operateMgr, EnemyAI ai)
{
ai.OprMgrBattleEvent.OnEvolveSuccessEvent = delegate(BattleCardBase originalEvolver, BattleCardBase realEvolver, IEnumerable<BattleCardBase> selectedTargets)
{
ai.SaveBeforeLatestActionInformation();
ai.LatestAction = new AIRealActionInformation(AIOperationType.EVOLVE, realEvolver, originalEvolver, selectedTargets);
ai.LatestAction.CreateRandomTargetInformation(realEvolver, (SkillBase skill) => skill.IsWhenEvolveSkill && RegisterTool.IsSkillRandom(skill), forceCheckEvolveSkills: true);
};
operateMgr.OnEvolveSuccess += ai.OprMgrBattleEvent.OnEvolveSuccessEvent;
}
private static void SetupOperateMgrOnEvolveComplete(OperateMgr operateMgr, EnemyAI ai)
{
ai.OprMgrBattleEvent.OnEvolveCompleteEvent = delegate
{
if (!ai.IsBattleEnd)
{
ai.UpdateAICurrentVirtualField();
AIVirtualField currentVirtualField = ai.CurrentVirtualField;
if (ai.LatestAction != null && ai.BeforeLatestActionField != null)
{
currentVirtualField.UpdateTagInformationFromLatestAction(ai.LatestAction, ai.BeforeLatestActionField);
}
ai.LatestAction = null;
ai.BeforeLatestActionField = null;
}
return NullVfx.GetInstance();
};
operateMgr.OnEvoleComplete += ai.OprMgrBattleEvent.OnEvolveCompleteEvent;
}
private static void SetupOperateMgrOnBeforeAttack(OperateMgr operateMgr, EnemyAI ai)
{
ai.OprMgrBattleEvent.OnBeforeAttackEvent = delegate(BattleCardBase attacker, BattleCardBase target, SkillProcessor skillProcessorOneTime)
{
ai.LatestAction = new AIRealActionInformation(AIOperationType.ATTACK, attacker, attacker, new List<BattleCardBase> { target });
ai.SaveBeforeLatestActionInformation();
ai.LatestAction.CreateRandomTargetInformation(attacker, (SkillBase skill) => skill.IsBeforAttackSkill && RegisterTool.IsSkillRandom(skill));
return NullVfx.GetInstance();
};
operateMgr.OnBeforeAttack += ai.OprMgrBattleEvent.OnBeforeAttackEvent;
}
private static void SetupOperateMgrOnAttackExecuted(OperateMgr operateMgr, EnemyAI ai)
{
ai.OprMgrBattleEvent.OnAttackExecutedEvent = delegate
{
if (!ai.IsBattleEnd)
{
ai.UpdateAICurrentVirtualField();
AIVirtualField currentVirtualField = ai.CurrentVirtualField;
if (ai.LatestAction != null && ai.BeforeLatestActionField != null)
{
currentVirtualField.UpdateTagInformationFromLatestAction(ai.LatestAction, ai.BeforeLatestActionField);
}
ai.LatestAction = null;
ai.BeforeLatestActionField = null;
}
return NullVfx.GetInstance();
};
operateMgr.OnAttackExecuted += ai.OprMgrBattleEvent.OnAttackExecutedEvent;
}
private static void SetupOperateMgrOnBeforeFusion(OperateMgr operateMgr, EnemyAI ai)
{
ai.OprMgrBattleEvent.OnBeforeFusionEvent = delegate(BattleCardBase fusionActor, IEnumerable<BattleCardBase> targets)
{
ai.LatestAction = new AIRealActionInformation(AIOperationType.FUSION, fusionActor, fusionActor, targets);
ai.SaveBeforeLatestActionInformation();
};
operateMgr.OnBeforeFusion += ai.OprMgrBattleEvent.OnBeforeFusionEvent;
}
private static void SetupOperateMgrOnAfterFusion(OperateMgr operateMgr, EnemyAI ai)
{
ai.OprMgrBattleEvent.OnAfterFusionEvent = delegate
{
if (!ai.IsBattleEnd)
{
ai.UpdateAICurrentVirtualField();
AIVirtualField currentVirtualField = ai.CurrentVirtualField;
if (ai.LatestAction != null && ai.BeforeLatestActionField != null)
{
currentVirtualField.UpdateTagInformationFromLatestAction(ai.LatestAction, ai.BeforeLatestActionField);
}
ai.LatestAction = null;
ai.BeforeLatestActionField = null;
}
return NullVfx.GetInstance();
};
operateMgr.OnAfterFusion += ai.OprMgrBattleEvent.OnAfterFusionEvent;
}
public static void SetupVirtualPairEventForOperationSimulator(BattlePlayerPair pair, AIOperationSimulatorAccessor accessor)
{
SetupVirtualPairOnAfterPickCard(pair, accessor);
}
}