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>
128 lines
5.6 KiB
C#
128 lines
5.6 KiB
C#
// AUTHORED SHIM (not copied). The battle View / UI / Touch / Replay / RoomMatch
|
|
// presentation tree the engine holds references to but never drives headless
|
|
// (IsForecast suppresses VFX; we never pump input or rendering). Stubbed in their
|
|
// ORIGINAL namespaces so the copied engine's type references resolve. Members grow
|
|
// only as the compile loop demands a specific call. Most are referenced as field/
|
|
// parameter types only, so empty stubs suffice.
|
|
|
|
namespace Wizard.Battle.View
|
|
{
|
|
public partial interface IReadOnlyVoiceInfo { }
|
|
public partial class BattleCardView
|
|
{
|
|
// BuildInfo (14-arg ctor + members) provided by Generated/BattleCardView_BuildInfo.g.cs
|
|
// Parameterless ctor lets the no-op subclass hand stubs (ClassBattleCardViewBase,
|
|
// NullBattleCardView) and any non-chaining stub satisfy their implicit base() call.
|
|
public BattleCardView() { }
|
|
public BattleCardView(BuildInfo buildInfo) { _buildInfo = buildInfo; }
|
|
|
|
// HEADLESS-FIX (M-HC-4a): the receive ATTACK path reads BattleCardView.CardInfo (the backing
|
|
// card) and BattleCardView._inPlayFrameEffect on the resolve path (InPlayCardReflection /
|
|
// ActionProcessor.Attack). The interface getters in Generated/_IfaceImpl.g.cs surface these two
|
|
// fields. CardInfo comes from the stored BuildInfo (cardInfo == the card, IReadOnlyBattleCardInfo,
|
|
// so IsClass etc. are authentic); _inPlayFrameEffect is a non-null no-op frame-effect control
|
|
// whose HideFrameEffect/UpdateCanAttackEffect are empty (Generated/InPlayCardFrameEffectControl.g.cs).
|
|
internal BuildInfo _buildInfo;
|
|
internal IReadOnlyBattleCardInfo HeadlessCardInfo => _buildInfo?.cardInfo;
|
|
internal InPlayCardFrameEffectControl _headlessInPlayFrameEffect =
|
|
new InPlayCardFrameEffectControl(null, null, null);
|
|
|
|
// AttackTargetSelectInfo provided by Generated/BattleCardView_AttackTargetSelectInfo.g.cs
|
|
//
|
|
// HEADLESS-FIX: lazily non-null GameObject so unguarded Unity touches on the IsRecovery
|
|
// path resolve as no-ops instead of NRE-ing on the shim's null default. Matches the
|
|
// existing Component.gameObject lazy pattern (UnityShim.cs:94). The IsRecovery card-create
|
|
// delegate (NetworkBattleManagerBase.cs:379) passes null for cardGameObject, which left
|
|
// BattleCardView.GameObject null and caused Skill_metamorphose.cs:147 (the in-play
|
|
// metamorphose branch — Petrification etc.) to NRE on
|
|
// `metamorphosedCard.BattleCardView.GameObject.transform.rotation = Quaternion.identity`,
|
|
// a purely cosmetic transform reset; making it a no-op preserves the surrounding state
|
|
// mutations (ReplaceInPlay, SetUpInplay, FlagCardAsDestroyedBySkill, RemoveFromInPlay).
|
|
// Live regression: bid 283192092460, A's Petrification on B's in-play card idx 1.
|
|
private UnityEngine.GameObject _gameObject;
|
|
public virtual UnityEngine.GameObject GameObject
|
|
{
|
|
get => _gameObject ??= new UnityEngine.GameObject();
|
|
protected set => _gameObject = value;
|
|
}
|
|
}
|
|
public partial class NonDialogPopup : UnityEngine.MonoBehaviour { } // Close() in Generated/NonDialogPopup.g.cs
|
|
public abstract class BattlePlayerViewBase
|
|
{
|
|
public enum BattleDialogItem { Menu, Retire }
|
|
public bool IsSelecting { get; set; }
|
|
}
|
|
public partial class InPlayCardFrameEffectControl { }
|
|
}
|
|
|
|
namespace Wizard.Battle.UI
|
|
{
|
|
public partial class BattleLogItem : UnityEngine.MonoBehaviour { }
|
|
public partial class BattleLogManager { }
|
|
public partial class BattleLogWindow : UnityEngine.MonoBehaviour
|
|
{
|
|
public enum BattleLogType { Battle, PlayCardLog, Destruction}
|
|
}
|
|
public partial class EvolutionConfirmation { }
|
|
}
|
|
|
|
namespace Wizard.Battle.Touch
|
|
{
|
|
public partial class SkillTargetSelectTouchProcessor { }
|
|
public partial class EvolutionTouchProcessor
|
|
{
|
|
// events dropped by m1_stub_gen (generator does not capture `event` decls)
|
|
public event global::System.Func<BattleCardBase, global::Wizard.Battle.View.Vfx.VfxBase> OnFocusTarget;
|
|
public event global::System.Func<BattleCardBase, global::Wizard.Battle.View.Vfx.VfxBase> OnUnfocusTarget;
|
|
public event global::System.Func<BattleCardBase, global::Wizard.Battle.View.Vfx.VfxBase> OnSelectTarget;
|
|
public event global::System.Action OnNotSelectTarget;
|
|
}
|
|
public partial class SetCardProcessor { }
|
|
public partial class EvolutionSimpleProcessor { }
|
|
public partial class EmotionTouchProcessor { }
|
|
public partial class DetailPanelTouchProcessor { }
|
|
public partial class ClassBuffTouchProcessor { }
|
|
}
|
|
|
|
namespace Wizard.Battle.Replay
|
|
{
|
|
public interface IReplayRecordManager
|
|
{
|
|
void SetupRecording(BattleManagerBase battleMgr);
|
|
void SetupBattleInfoFilter();
|
|
void SetupOperateMgrEvents(BattleManagerBase battleMgr);
|
|
}
|
|
}
|
|
|
|
namespace Wizard.Replay
|
|
{
|
|
}
|
|
|
|
namespace Wizard.RoomMatch
|
|
{
|
|
public partial class WatchDataHandler { }
|
|
// RoomConnectController (members + BattleRule/PositionMode enums + InitializeParameter)
|
|
// provided by Generated/RoomConnectController*.g.cs
|
|
}
|
|
|
|
namespace Wizard.Story
|
|
{
|
|
public class StoryRecoveryData
|
|
{
|
|
public StoryRecoveryData(LitJson.JsonData jsonData) { }
|
|
public StoryRecoveryData(SelectedStoryInfo data) { }
|
|
public int ChapterCharaId { get; }
|
|
public LitJson.JsonData ToJsonData() => default!;
|
|
}
|
|
}
|
|
|
|
namespace Wizard.UI.Common
|
|
{
|
|
public partial class TabList : UnityEngine.MonoBehaviour { }
|
|
}
|
|
|
|
namespace Wizard.UI.Dialog.ImageSelection
|
|
{
|
|
public partial class ImageSelection : UnityEngine.MonoBehaviour { }
|
|
}
|