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:
@@ -16,17 +16,20 @@ internal static class KnownListBuilder
|
||||
/// idx → 0. Prod sends the real count here and the client reads it straight into the card's cost model
|
||||
/// (<c>NetworkBattleReceiver</c> spellboost case), so a wrong value makes the opponent compute the
|
||||
/// card at full price and silently reject the play in <c>OperateReceiveChecker.IsPlayCard</c>
|
||||
/// (PP-over → ConductError → NullOperationCollection → no render/echo). attachTarget stays "";
|
||||
/// cost/clan/tribe remain deferred (receiver re-derives from cardId).</summary>
|
||||
/// (PP-over → ConductError → NullOperationCollection → no render/echo). <paramref name="cost"/> is the
|
||||
/// engine-RESOLVED play-time cost (M-HC-3a) the handler reads off the shadow engine and passes in;
|
||||
/// it lands on the entry verbatim (a vanilla play naturally resolves to its base cost). attachTarget
|
||||
/// stays ""; clan/tribe remain deferred (receiver re-derives from cardId).</summary>
|
||||
public static KnownCardEntry? BuildPlayedCard(
|
||||
IReadOnlyDictionary<int, long> deckMap, int playIdx, object? orderList,
|
||||
IReadOnlyDictionary<int, int>? spellboostMap = null)
|
||||
IReadOnlyDictionary<int, int>? spellboostMap = null, int cost = 0)
|
||||
{
|
||||
if (!deckMap.TryGetValue(playIdx, out var cardId)) return null;
|
||||
var to = ExtractMoveTo(orderList, playIdx);
|
||||
if (to is null) return null;
|
||||
var spellboost = spellboostMap is not null && spellboostMap.TryGetValue(playIdx, out var sb) ? sb : 0;
|
||||
return new KnownCardEntry(Idx: playIdx, CardId: cardId, To: to.Value, Spellboost: spellboost, AttachTarget: "");
|
||||
return new KnownCardEntry(
|
||||
Idx: playIdx, CardId: cardId, To: to.Value, Spellboost: spellboost, AttachTarget: "", Cost: cost);
|
||||
}
|
||||
|
||||
/// <summary>Mine spellboost-count changes from a sender's <c>orderList</c> <c>alter</c> ops. For each
|
||||
|
||||
Reference in New Issue
Block a user