Commit Graph

493 Commits

Author SHA1 Message Date
gamer147
62251482e4 feat(battle-node): cross-side gift + Echo-frame token mining
Close the two generated-token gaps that desynced PvP live test #3 (the
Forestcraft Fairy), both sourced from the 2026-06-03 decomp-validation table.

- MineAddOps now returns (idx, cardId, isSelf) and no longer drops isSelf:0.
  isSelf is the sender's perspective tag on CardObj.IsPlayer (RegisterToken.cs:22)
  and a card has one CardObj.Index, so an isSelf:0 add is the opponent's card.
- New shared BattleSessionState.RecordTokensFrom routes isSelf:1 -> sender,
  isSelf:0 -> opponent (the gift lives in the recipient's map, consulted when
  they play it). PlayActionsHandler delegates to it.
- EchoHandler now mines via the same helper but still returns no routes. An
  Echo's orderList carries the same add-op shape as a send (MakeEchoData ->
  MakeCommonSendAndEchoCardData), so MineAddOps applies verbatim; mining != relaying.

Choice/copy/private-group adds stay skipped (no concrete cardId). Full solution
963/963 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 07:59:46 -04:00
gamer147
155ccf0a48 test(battle-node): lock token-reveal knownList shape vs prod capture line 96
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 23:41:59 -04:00
gamer147
d8b5ef950d feat(battle-node): reveal generated tokens on play via remembered identity
PlayActionsHandler mines add ops into BattleSessionState.RecordToken each
frame; a token played in a later frame now synthesizes a knownList from the
remembered cardId instead of degrading. Bullet-3 audit F1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 23:36:44 -04:00
gamer147
b6af8bfb7d feat(battle-node): mine generated-token cardIds from orderList add ops
KnownListBuilder.MineAddOps extracts (idx,cardId) from isSelf:1 add ops,
skipping cross-side gifts and choice tokens. Bullet-3 audit F1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 23:30:47 -04:00
gamer147
4b38a9d3e0 test(battle-node): rename ScriptedBotCtx test helper to FakeOpponentCtx
Pure private-helper rename in the two lifecycle test fixtures for lexical
hygiene — matches the kept ServerBattleFrames.FakeOpponentViewerId. The
fixture is a fake opponent MatchContext, never a "scripted bot". No behavior
change; both fixtures green (20/20).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:57:58 -04:00
gamer147
ac78e809cd refactor(battle-node): clear residual scripted-bot prose from comments/docs
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:52:41 -04:00
gamer147
ba18790156 refactor(battle-node): rename ScriptedLifecycle->ServerBattleFrames, ScriptedProfiles->BattleFrameDefaults
Pure rename. These hold the shared server-authored frame builders used by every
battle mode's handshake/mulligan dispatch — the 'Scripted' name was a historical
accident that hid the PvP/Bot crossover. No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:36:32 -04:00
gamer147
e9493e24c4 refactor(battle-node): drop BattleType.Scripted and the scripted-only builders
Removes the Scripted enum value, the bot's client-shaped emissions (BuildClient*),
the canned opponent turn (BuildOpponent*), and OpponentTurnStartSpin. The shared
server-frame builders (Matched/BattleStart/Deal/Swap/Ready + ComputeHandAfterSwap)
and OpponentJudgeSpin (Bot mode) stay.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:27:57 -04:00
gamer147
b0e3783757 refactor(battle-node): drop dead MatchingResolver options param; fix stray BOM
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:23:57 -04:00
gamer147
f21ab7a38c refactor(battle-node): remove ScriptedBotParticipant and dev-affordance wiring
Deletes the scripted opponent and every entry point that created a
BattleType.Scripted session (the ?scripted=1 query opt-in, the
SoloDefaultsToScripted toggle, the resolver short-circuit, the WS handler case,
the bridge validation arm). Real two-client PvP and the Bot matchmaking-timeout
fallback are untouched. ResolveAsync drops its scriptedOptIn parameter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:15:48 -04:00
gamer147
8085119439 refactor(battle-node): tidy residue after scripted dispatch-arm removal
Remove the now-unused SVSim.BattleNode.Lifecycle using from
FrameDispatchContext (it was only needed for ScriptedLifecycle inside
the deleted IsScriptedBot helper) and reword the SenderPhase doc comment
so it no longer references the removed dispatch-test scripted-bot stub.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:06:25 -04:00
gamer147
ca9ad5db8f refactor(battle-node): remove scripted-bot test-stub arms from dispatch handlers
The IsScriptedBot(ctx.From) forwards in JudgeHandler/TurnStartHandler/TurnEndHandler
and the 'if Type==Scripted' raw-forward only ever fired for ScriptedBotParticipant
emissions; NoOpBot (Bot mode) never emits, so they are dead. Routing is now purely
PvP-vs-Bot. Drops the IsScriptedBot helper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:00:57 -04:00
gamer147
963adbbd1b test(battle-node): delete scripted participant + scripted-only builder tests
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 19:55:00 -04:00
gamer147
3fe378d801 test(battle-node): drop scripted dispatch tests; retarget generic fixture to PvP
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 19:49:49 -04:00
gamer147
3ccd986e65 test(battle-node): drop scripted smoke test; retarget deck-plumbing test to PvP
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 19:42:20 -04:00
gamer147
3feb535072 test(battle-node): drop dead ViewerId const + refresh stale coverage doc
Follow-up cleanup to the two-client PvP conformance drive. The class-level
ViewerId const is no longer referenced (both remaining `ViewerId:` sites are
the MsgEnvelope named ctor arg, passing `vid`/literal 1), and the Coverage
doc-comment still described "a single Scripted session" — refresh it to the
two-client PvP reality. No behavior change; tests 2/2 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 19:37:29 -04:00
gamer147
a916afe924 test(battle-node): drive the conformance oracle via two-client PvP
The golden-match oracle harvested all ten server-authored frames from a single
Scripted client. Re-point it at a two-client PvP session (same shared builders
for handshake/mulligan, real turn-cycle frames for TurnStart/TurnEnd/Judge) so
the oracle survives removal of the scripted bot. Category-based shape check is
unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 19:33:58 -04:00
gamer147
3b6b8d3c94 Merge: BattleNode deterministic-turn translator (vanilla PvP slice)
Per-URI PvP frame translator + live-validated TurnEnd<->Judge handover.
Full vanilla two-client match plays end-to-end (card plays, combat, evolves,
fanfares) synced through BattleFinish. 990/990 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:57:55 -04:00
gamer147
e98bd10dbe fix(battle-node): reflect PvP Judge back to its sender (turn handover)
Live two-client run (data_dumps/captures/battle_test) exposed a turn-handover
stall: ending a turn on client A made BOTH clients show A's turn again; the
opponent never got a turn. Root cause: JudgeHandler routed the {spin:0} Judge to
ctx.Other. The client rule is 'receive opponent TurnEnd -> SendJudge', so the
PASSIVE player (the one taking over the turn) is the Judge sender, and 'receive
Judge -> ControlTurnStartPlayer' starts the RECEIVER's turn. Routing to ctx.Other
delivered the Judge to the player who had just ended their turn, restarting it in
a closed loop while the taker-over sat on 'Opponent's Turn'.

Fix: the PvP Judge {spin} reflects back to ctx.From (the sender / turn taker-over),
matching the Bot arm's existing 'Judge to sender only' handover. The sender then
emits TurnStart, which relays to the opponent as {spin}. Updated the dispatch unit
test and the PvpHandshakeAndGameplay integration test to the real handover order
(passive sends Judge -> receives it back -> sends TurnStart -> opponent sees it).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:45:17 -04:00
gamer147
c360d639f2 refactor(battle-node): address final-review minor notes (comments + test backfill)
- PlayActionsHandler doc: drop the phantom 'with a debug log' (handlers are
  stateless singletons with no logger); say token plays degrade silently.
- KnownListBuilder.ExtractMoveTo doc: note first-match-wins semantics and the
  send-side==recv-side 'to' assumption pending recv-capture confirmation.
- KnownListBuilderTests: add multi-move first-match coverage and the
  in-deck-but-no-matching-move null branch for BuildPlayedCard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:26:07 -04:00
gamer147
bca94648f7 test(battle-node): update PvpHandshakeAndGameplay to deterministic-turn translator contract
The end-to-end PvP gameplay test asserted the pre-translator relay contract
(A's TurnEnd broadcasts TurnEnd+Judge to both sides). Tasks 7/8 replaced that
with the per-URI translator: the active player ends its turn by sending TurnEnd
then Judge, the opponent receives the translated {turnState:0}/{spin:0} frames,
and the sender receives nothing. Rewrote the gameplay section to drive and
assert the new contract. PlayActions remains delivered to the opponent (Uri
preserved, body now synthesized by PlayActionsHandler).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:21:01 -04:00
gamer147
f0026972cb test(battle-node): ground synthesized knownList shape against prod recv capture
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:13:42 -04:00
gamer147
f9c671c089 feat(battle-node): TurnEndActionsHandler emits empty body to opponent in PvP
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:11:41 -04:00
gamer147
58994a53c9 feat(battle-node): JudgeHandler emits {spin:0} to opponent in PvP
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:09:44 -04:00
gamer147
3c8a00c928 feat(battle-node): TurnEndHandler emits {turnState:0} to opponent only in PvP
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:07:44 -04:00
gamer147
6e85a6b2db feat(battle-node): TurnStartHandler emits {spin:0} to opponent in PvP
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:05:15 -04:00
gamer147
6b580c622d feat(battle-node): EchoHandler consumes Echo instead of relaying
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:03:19 -04:00
gamer147
506d286529 feat(battle-node): PlayActionsHandler synthesizes knownList (vanilla deck-card slice)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:59:54 -04:00
gamer147
030d3b8057 feat(battle-node): KnownListBuilder pure transforms (knownList synth, target rename)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:56:12 -04:00
gamer147
b295fd8f09 feat(battle-node): per-side idx->cardId map on BattleSessionState
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:53:32 -04:00
gamer147
486f72f4a0 feat(battle-node): typed PlayActionsBroadcastBody + KnownCardEntry/OppoTargetEntry
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:51:02 -04:00
gamer147
268b864e28 refactor(battle-node): delete legacy ComputeFrames switch; dispatch is now lookup-or-drop 2026-06-03 14:48:33 -04:00
gamer147
503c382646 refactor(battle-node): extract ForwardWhenBothReadyHandler; share handler instances via BuildHandlers 2026-06-03 14:33:26 -04:00
gamer147
db2f711894 refactor(battle-node): extract JudgeHandler
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 14:30:40 -04:00
gamer147
aacd7b56ad refactor(battle-node): extract TurnStartHandler
Unions the two legacy TurnStart arms (IsRealForwardableFromScripted case 11 +
BothAfterReady case 12) into TurnStartHandler. Both arms produce (Other, Env, false)
with no extra guards or state mutations — union is behavior-equivalent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 14:27:17 -04:00
gamer147
c03fb3c139 refactor(battle-node): extract RetireKillHandler
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 14:24:35 -04:00
gamer147
d35818360f refactor(battle-node): extract TurnEndFinalHandler 2026-06-03 14:21:54 -04:00
gamer147
538099ff4b refactor(battle-node): extract TurnEndHandler 2026-06-03 14:20:25 -04:00
gamer147
477faf3df3 refactor(battle-node): extract SwapHandler (mulligan barrier) 2026-06-03 14:13:26 -04:00
gamer147
3e2931b085 refactor(battle-node): extract LoadedHandler
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 14:10:33 -04:00
gamer147
e5ec8a0de1 refactor(battle-node): extract InitBattleHandler 2026-06-03 14:07:49 -04:00
gamer147
7c36933c06 refactor(battle-node): extract InitNetworkHandler 2026-06-03 14:04:58 -04:00
gamer147
73d2c4e1b8 refactor(battle-node): add frame-handler contract, context, and empty registry shim 2026-06-03 14:03:11 -04:00
gamer147
57d91236a0 chore: ignore *.bak editor backups 2026-06-03 13:56:59 -04:00
gamer147
4f89463f9c refactor(battle-node): extract frame factories into BattleFrames 2026-06-03 13:56:41 -04:00
gamer147
85c43a9a72 refactor(battle-node): move session phase + post-swap hands into BattleSessionState 2026-06-03 13:47:35 -04:00
gamer147
95554cee04 refactor(battle-node): name ComputeFrames routes as DispatchRoute 2026-06-03 13:43:39 -04:00
gamer147
afe2984075 test(battle-node): drive PvP flow handshakes through the mulligan barrier
The three PvP BattleNodeFlowTests drove each client's handshake to Ready
independently; the new barrier withholds Ready until both sides swap, so the
single-client helper timed out. Split DriveHandshakeAsync into DriveThroughSwapAsync
(stops at SwapResponse) + DrivePvpHandshakeAsync (drives both, then drains the
barrier-released Ready for each). Scripted/Bot single-client paths are unaffected
(non-IHasHandshakePhase opponent releases Ready immediately).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 10:58:33 -04:00
gamer147
feb387d3d5 test(battle-node): real scripted bot drives handshake through the mulligan barrier
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 10:53:51 -04:00
gamer147
2d31037648 fix(battle-node): type-agnostic mulligan barrier withholds Ready until both swap
Ready was sent per-side immediately carrying the placeholder opponent hand, so
one client cleared mulligan before the other. The barrier now releases Ready to
every IHasHandshakePhase participant only once all have swapped, each carrying
the opponent's real post-mulligan hand. No Type check — NoOp (Bot/AINetwork)
isn't a phase impl, so that mode still releases immediately.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 10:52:33 -04:00