feat(battlenode): engine read surface for played-card spellboost (Phase 2 N2 Task 3 — oracle BLOCKED)

Adds SessionBattleEngine.PlayedCardSpellboost + PeekPlayedCardSpellboost (pre-resolve
read of the acting seat's hand card by Index==playIdx) and a CaptureReplay.InterleavedSends
helper. The non-circular capture oracle (engine-derived spellboost vs prod's independent
emission to cl2: idx2->1, idx14->2) is added but [Ignore]'d: the headless receive path does
not apply the wire's authoritative orderList (Deal/Swap don't seat the mulligan hand, draws
follow the seeded deck top instead of the wire move ops, plays never remove the card, alter
spellboost never accumulates), so the engine cannot yet DERIVE the count. Closing this needs
an Engine/*.cs + VfxMgr-execution logic change (escalation per the N2 playbook), not a
mechanical no-op fill. Read surface, node + engine builds, drift, and the rest of the
SessionEngine suite are green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-06 16:55:47 -04:00
parent eb52890251
commit fcd64c8c11
3 changed files with 104 additions and 0 deletions

View File

@@ -59,6 +59,19 @@ namespace SVSim.BattleEngine.Tests.SessionEngine
return frames;
}
/// <summary>Both clients' SENT frames interleaved in capture (ts) order, each tagged with its
/// seat: cl1 == seat A == player (true), cl2 == seat B == opponent (false). This is the node's
/// both-clients-sends ingest order — the same ts ordering the N1 shadow-replay test uses, here
/// extended to merge both sides' sends rather than replaying one client's full receive stream.</summary>
public static IEnumerable<(MsgEnvelope Env, bool Seat)> InterleavedSends(
IReadOnlyList<CapturedFrame> cl1, IReadOnlyList<CapturedFrame> cl2)
{
return cl1.Where(f => f.Direction == "send").Select(f => (f, Seat: true))
.Concat(cl2.Where(f => f.Direction == "send").Select(f => (f, Seat: false)))
.OrderBy(x => x.f.Ts)
.Select(x => (x.f.Env, x.Seat));
}
/// <summary>The selfDeck idx-&gt;cardId order from the Matched frame (the order the node also
/// computed and handed the client). This is the deck the engine seats for that side.</summary>
public static IReadOnlyList<long> SelfDeckFrom(IEnumerable<CapturedFrame> frames)