feat(battle-engine-port): M9 COMPLETE — when_play draw resolves headless (hand/deck-delta oracle)

Proves the deck->hand transfer dimension (design §5 draw oracle) — the last
deterministic, non-RNG card-effect class no prior milestone touched (M3/M4/M6/M8
moved stats, M2/M5/M7 the board, M3 the leader).

Card 800114010 (clan-1 ELF cost-1 when_play draw 1 from own deck, ungated, no
evo/preprocess). The resume-guide's skill_target=none/no-RNG shape does not exist
in cards.json — EVERY draw selects from the deck via a random_count filter
(skill_option is always literally 'none'). RNG neutralized structurally: seed the
deck with EXACTLY ONE known card so random_count=1 is deterministic regardless of
seed. New primitive HeadlessEngineEnv.SeedDeck (create via the null-view seam +
engine AddToDeck). Oracle DrawSpellOracleTests asserts: seeded card moves deck->hand
(by id + by reference), deck -1, drawn card IsInHand, spell pays cost + leaves hand
+ resolves to cemetery, board/opponent untouched. Load-bearing confirmed the M7 way
(seed a different id -> the by-id assertion fails).

Shim gap fixed (the predicted M9 cost): Skill_draw's BattleLog tail
(UpdateFusionedCardSkillDrewCard, unguarded; + the IsBattleLog AddLogSkillDrawCard
calls) dereferences BattleLogManager.GetInstance(), an M1 'default!' null singleton
-> NRE after the draw already committed. One-line HEADLESS-FIX (M9) in
BattleLogManager.g.cs returns the existing _instance singleton (all its methods are
no-ops), per the M2/M7 Null*-singleton playbook. No Engine/ edit (drift clean).

9/9 green; check_drift.py clean; engine still 0 Error(s).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-06 08:47:04 -04:00
parent 4f76fb21f0
commit eee8450144
3 changed files with 155 additions and 2 deletions

View File

@@ -82,7 +82,7 @@ public partial class BattleLogManager
public static void DestroyLogItem(BattleLogItem logItem) { }
public static BattleLogItemSet CreateLogItemSet() => default!;
public static void DestroyLogItemSet(BattleLogItemSet logItemSet) { }
public static BattleLogManager GetInstance() => default!;
public static BattleLogManager GetInstance() => _instance ??= new BattleLogManager(); // HEADLESS-FIX (M9): non-null singleton so the draw's unguarded BattleLog tail (UpdateFusionedCardSkillDrewCard, and the IsBattleLog AddLogSkillDrawCard calls) no-ops instead of NRE on a null instance
private BattleLogManager() { }
public void SetUp(Transform parent, BattleManagerBase battleMgr, OperateMgr operateMgr, BattlePlayer battlePlayer) { }
public void Clear() { }