Drive ATTACK frames through the headless receive conductor and assert on engine
board state (node-native harness). Two cases: follower -> enemy leader (leader
life drops by atk, attacker spent) and a lethal follower-vs-follower trade (both
removed). ATTACK opcode confirmed = 10 (NetworkBattleDefine.PlayActionType).
Headless view-untangle (no Engine logic edits; drift clean):
- IBattlePlayerView.AttackSelectControl -> non-null HeadlessAttackSelectControl
(no-op RegisterAttackPair/ResetCardAfterAttack); IsCardTranslatable left to base.
- IBattleCardView.CardInfo -> backing card via BuildInfo (so IsCardTranslatable
reads authentic IsClass); class/null view ctors now chain : base(buildInfo).
- IBattleCardView._inPlayFrameEffect -> non-null no-op control.
- Seed Certification.viewer_id headless so the IsRecovery target parse
(vid != UserViewerID) does not throw inside SavedataManager and silently drop
the parsed targetList.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- _IfaceImpl.g.cs: extend header to warn about hand-edits; tag all bare
// HEADLESS-FIX lines with their milestone (M13 on GetSideLogControl ×2)
so `grep HEADLESS-FIX` reliably surfaces every block before a regen.
- HeadlessHandViewStub / HeadlessPlayQueueViewStub: narrow from public to
internal sealed — both stubs are consumed only within SVSim.BattleEngine
(via the generated partial impls); no public surface exposes the concrete
type, so internal is correct and aligns with HeadlessIconAnimations.
- SessionBattleEngine.SeedMulliganInfoControl: add one-line comment on the
GetComponent<MulliganInfoControl>() call explaining the shim's lazy
materialisation behaviour (otherwise reads like a guaranteed NRE).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The engine's receive CONDUCTOR fuses each authoritative mutation behind a view
call: the play mutation is an InstantVfx registered to VfxMgr, and the deal hand
is seated by MulliganPhaseBase.StartDeal wired to OperateReceive.OnReceiveDeal.
Headless, the shared VfxMgr no-op'd registration (correct for the direct
ActionProcessor path the M2-M12 oracles use) and OnReceiveDeal was never wired,
so the receive path resolved nothing.
Untangle (Candidate B, zero Engine logic edits):
- InstantVfx.Run() opt-in executor (authored shim).
- HeadlessConductorVfxMgr : VfxMgr runs registered InstantVfx; wired only via the
node's SessionContentsCreator.CreateVfxMgr (verified the receive mgr's VfxMgr
comes from there — BattleManagerBase.cs:768). M2-M12 use HeadlessContentsCreator,
so they're isolated by construction.
- WireMulliganPhase: construct NetworkMulliganPhase + MulliganEventSetting() to
install OnReceiveDeal -> StartDeal (the node never pumps the phase machine).
View no-op surface (the 7 from the probe, minus 1 not hit; +1 emergent):
- Deal wiring (NetworkMulliganPhase) [node seed]
- MulliganInfoControl._partsPlayer/_partsOpponent._exchangeMark/_keepZone/_abandonZone [node seed: prefab + SeedMulliganInfoControl]
- Data.BattleRecoveryInfo (IsMulliganEnd=false) [EngineGlobalInit seed]
- IBattlePlayerView.PlayQueueView -> HeadlessPlayQueueViewStub [_IfaceImpl.g.cs, both getters]
- DetailMgr.DetailPanelControl/SubDetailPanelControl [node seed]
- BattleCardIconAnimations.collection (emergent: UpdateInPlayBattleCardIconLabel) -> HeadlessIconAnimations empty SkillCollectionBase [_IfaceImpl.g.cs]
- BattleMenuBtn (probe item 7): NOT hit on the vanilla path; not seeded.
Oracle (HeadlessConductorTests): node Deal seats 3-card hand; a vanilla
hand-card Play leaves hand (-1), adds board (+1), drops PP by cost.
Regression: 24/24 BattleEngine.Tests oracles (M2-M12) green; 241/241
SVSim.UnitTests BattleNode green. The 2 SessionEngine capture-replay shadow
tests are marked Ignore (superseded): they passed VACUOUSLY when the receive
path resolved nothing; with resolution live they hit the documented
capture-replay draw-misalignment artifact. Node-native battles are the oracle.
Drift: no drift.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Full single-client capture replay (cl1 send=player seat, receive=opponent seat,
ts-ordered) ingests end-to-end: 33 frames, 0 rejects, 0 invariant violations at
turn boundaries (leader life/PP/board/hand).
Headless gaps filled per playbook (no Engine/ drift):
- IsRecovery=true after construction: the engine's own headless replay mode gates
the live view/UI layer off (BattleUIContainer, turn-control UI, VFX waits) while
keeping the live NetworkBattleReceiver (ND4) and authoritative state.
- Seed ToolboxGame.RealTimeNetworkAgent, BattleUIContainer, _backGround, and
per-player NullPlayerEmotion no-ops the receive/turn cycle dereferences.
- _IfaceImpl.g.cs (shim, not Engine/): BattleCardView.BattleCardIconAnimations
returns a lazy non-null no-op so the opponent card-reveal icon-init (deferred
VFX) doesn't NRE.
- HeadlessCardMaster.Load made cumulative: it replaced the global CardMaster each
call, so a Load(deck) evicted the oracle card set and broke tests run after.
Adds board-state accessors (LeaderLife/Pp/HandCount/BoardCount) and CaptureReplay
ts ordering.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First proof that follower DEATH / board-removal commits in the authoritative
part of PlayCard headless (not the cosmetic post-Process tail). Card 800144120
(cost-0 when_play destroy of a select_count=1 enemy follower) resolves via the
M6 selectedCards path: selected enemy follower removed (board -1 + cemetery +1),
un-selected untouched (routing confirmed load-bearing by swapping the selection).
Shim gap fixed (the predicted M7 cost): SkillProcessor.SelectCardToHaveDestroyVoicePlay's
cosmetic death-voice tail NRE'd on three M1 default!/Null* shadows
(IBattleCardView.VoiceInfo, CardVoiceInfoCache.GetCardVoiceInfoForBattle,
ReadOnlyVoiceInfo.GetDestroyVoice — the last unusable as the interface since
m1_stub_gen dropped its : IReadOnlyVoiceInfo base). Fix = one hand shim
HeadlessVoiceInfo : IReadOnlyVoiceInfo returning the engine's own
VoiceAndWaitTime._nullVoice sentinel, wired into the two generated seams with
// HEADLESS-FIX markers. No Engine/ edit (drift clean).
dotnet test SVSim.BattleEngine.Tests -> 7/7 green; check_drift.py clean; engine 0 Error(s).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First green: a zero-skill vanilla follower (100011010, neutral 1/2) resolves to
correct authoritative state HEADLESS via IsForecast/IsRecovery + ActionProcessor.
PlayCard (DP4), no Unity runtime. §5 oracle passes (PP-cost; hand->in-play;
atk/health == CardCSVData base; opponent unchanged; no exception). VERDICT: the
port approach is validated through the resolution path, not just M1's compile path.
VanillaFollowerOracleTests.Vanilla_follower_resolves_to_correct_state — GREEN.
HeadlessCardMaster now loads the follower's real id from cards.json.
Resolution-path shim/engine gaps closed (all mechanical no-op fills or data seams,
never a Unity/logic wall):
- M1 mis-cut copies (DP1/DP3 — pure no-op logic wrongly stubbed to null):
Engine/Wizard.Battle.View.Vfx/NullCardVfxCreator.cs (its GetInstance() singleton
was nulled) + its dep NotEmptyNullVfx.cs. Deleted the generated NullCardVfxCreator
stub + its _IfaceImpl block; both manifested, check_drift clean.
- _IfaceImpl explicit-impl shadow: interface-typed view/mgr calls dispatch to the
explicit impls (which returned default!), shadowing public stubs. Fixed
IBattlePlayerView.GetSideLogControl (SkillProcessor side-log tail) to return a
non-null no-op. KEY M3+ learning: fix _IfaceImpl.g.cs for interface-typed NREs.
(GameMgr/component-model/Resources/IClassBattleCardView shim fills + CardIconControl
copy + the SVSim.BattleEngine.Tests project landed in the prior commit 2b50657.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
base-clause recovery strips interfaces (to dodge CS0535), but copied code converts
the stubs to those interfaces -> ~120 CS0266/CS1503. Two mechanisms:
- _IfaceImpl.g.cs: explicit no-op impls of the FULL (copied) interfaces, layered
onto each hierarchy base (BattleCardView/CardVfxCreatorBase/BattlePlayerView/
BattleEnemyView/ClassInfomationUIBase + NullCardVfxCreator). Explicit form never
collides with existing members; leaves inherit. Walks base-interface chains
(IPlayerView : IBattlePlayerView) and emits events.
- _InterfaceReattach.g.cs: plain ': IFoo' for the empty stub interfaces
(IProcessing, IReplayRecordManager).
- ClassBattleCardViewBase/NullBattleCardView: restore dropped BattleCardView base
so they inherit its IBattleCardView impl.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>