feat(battlenode): emit engine-resolved cost on every knownList entry (M-HC-3)
The opponent-facing PlayActions knownList now carries the engine-RESOLVED play-time cost (KnownCardEntry.cost), sourced from the headless shadow engine's PlayedCost on the just-resolved card. This closes the spellboost cost-desync BY CONSTRUCTION: the engine already knows the true discounted cost (spellboost + board modifiers folded in), so no bookkeeping is needed. - DTO: add non-nullable cost to KnownCardEntry (prod emits cost 45/45). - SessionBattleEngine.PlayedCardCost(seat, idx, fallback): finds the resolved card by engine Index across in-play/cemetery/hand zones and returns PlayedCost (captured by PlayCard at resolution == discounted Cost), degrading to fallback when the engine is not owned/ready. - PlayActionsHandler sources the played card's cost from ctx.Engine (ShadowIngest already resolved the play before the handler runs). Spellboost-map plumbing stays for now; Task 6 (M-HC-3b) retires it. - Validation: engine-read test (charge-seeded reducer 101314020: base 5, cost 5/1/0 at charge 0/4/5) + handler-emit test asserting knownList[0].cost == 1 (discounted, not base 5) with non-vacuity. Board-dependent (when_evolve_other) case deferred to M-HC-4 (evolve not yet headless); cost is read off the resolved engine so board modifiers are captured by construction once their ops resolve. - Harness: promote alt vanilla follower id (101211120) to AltVanillaFollowerId. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -36,15 +36,19 @@ public sealed record SelectCardEntry(
|
||||
[property: JsonPropertyName("open")]
|
||||
[property: JsonConverter(typeof(JsonNumberEnumConverter<ChoiceVisibility>))] ChoiceVisibility Open);
|
||||
|
||||
/// <summary>One revealed card in a <c>knownList</c>. Vanilla slice fills cardId from the sender's
|
||||
/// deck map and leaves spellboost 0 / attachTarget "" (cost/clan/tribe deferred to the card-master
|
||||
/// port — the receiver re-derives them from cardId).</summary>
|
||||
/// <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>
|
||||
public sealed record KnownCardEntry(
|
||||
[property: JsonPropertyName("idx")] int Idx,
|
||||
[property: JsonPropertyName("cardId")] long CardId,
|
||||
[property: JsonPropertyName("to")] int To,
|
||||
[property: JsonPropertyName("spellboost")] int Spellboost,
|
||||
[property: JsonPropertyName("attachTarget")] string AttachTarget);
|
||||
[property: JsonPropertyName("attachTarget")] string AttachTarget,
|
||||
[property: JsonPropertyName("cost")] int Cost);
|
||||
|
||||
/// <summary>Renamed <c>targetList</c> entry. <c>isSelf</c> is actor-relative and passes through
|
||||
/// verbatim — no perspective flip (bullet-3 audit F2).</summary>
|
||||
|
||||
Reference in New Issue
Block a user