refactor(battlenode): retire spellboost bookkeeping, engine owns cost+spellboost (M-HC-3)

The headless engine accumulates spell-charge for real on the receive path
(each spell play runs the played card's own AddSpellChargeCount) and resolves
the discounted cost by construction, so the wire-derived spellboost-count
bookkeeping is redundant. Engine-source the knownList spellboost COUNT too
(prod-faithful) via a new SessionBattleEngine.PlayedCardSpellboost, using the
same persist-post-play zone search as PlayedCardCost (SpellChargeCount survives
PlayCard; only ctor/ReturnCard zero it).

- Delete IdxToSpellboost/SpellboostMap/GetSpellboostMap/RecordSpellboostFrom
  (BattleSessionState) and MineAlterSpellboosts (KnownListBuilder); token/choice/
  copy identity maps are untouched.
- BuildPlayedCard takes an engine-sourced spellboost int (drops spellboostMap).
- Seed BattleLogManager fusion lists headless (the per-frame filter cleanup
  NREs on null EnemyFusionCard when a fanfare card registers a CalledCreateFilter)
  so real spell-charge grantor plays resolve.
- Add committed real-charge regression tests (no SeedHandCardSpellboostCost seam):
  one grantor play accumulates +1 on the reducer -> cost 5->4, count 1, persisting
  post-play; handler emits cost 4 + spellboost 1 engine-sourced.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-06 21:48:50 -04:00
parent 51419d15cd
commit 0d7136787a
9 changed files with 261 additions and 194 deletions

View File

@@ -39,9 +39,12 @@ public sealed record SelectCardEntry(
/// <summary>One revealed card in a <c>knownList</c>. <c>cardId</c> from the sender's deck map; <c>cost</c>
/// is the ENGINE-RESOLVED play-time cost (M-HC-3a) — the discounted cost the headless engine actually
/// charged (spellboost + board modifiers folded in by construction), emitted on EVERY entry (prod sends
/// cost 45/45 in captures, so it is NOT omitted). <c>spellboost</c> still carries the count for now
/// (Task 6 retires that bookkeeping once cost is engine-sourced everywhere). attachTarget stays "";
/// clan/tribe remain deferred (receiver re-derives them from cardId).</summary>
/// cost 45/45 in captures, so it is NOT omitted). <c>spellboost</c> is now ALSO engine-sourced (M-HC-3b) —
/// the played card's accumulated spell-charge count read straight off the resolved engine
/// (<c>SessionBattleEngine.PlayedCardSpellboost</c>); the wire-derived spellboost bookkeeping is retired.
/// Cost already folds the discount in by construction; the count rides the entry only to stay prod-faithful
/// (prod sends the real count). attachTarget stays ""; clan/tribe remain deferred (receiver re-derives
/// them from cardId).</summary>
public sealed record KnownCardEntry(
[property: JsonPropertyName("idx")] int Idx,
[property: JsonPropertyName("cardId")] long CardId,