feat(replay): API-driven frame scrubbing for recorded sessions#241
feat(replay): API-driven frame scrubbing for recorded sessions#241
Conversation
Test Results113 files - 139 113 suites - 139 2m 11s ⏱️ -41s Results for commit 7ac3eaf. ± Comparison against base commit df04456. This pull request removes 228 tests.This pull request skips 618 tests.♻️ This comment has been updated with latest results. |
CI Report — GateTests: 173/802 passed, 1 failed (628 skipped) Failed tests
Slow tests (>3s): 5
|
Self-reviewWhat's good
Issues to fix in this PR
Noted for v2 (not blocking)
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…PI-driven scrubbing handleGre now stores pendingCtx instead of auto-popping the next frame. handleConnect stores pendingCtx after sending roomState instead of eagerly sending the first GRE bundle. next() pops and sends on demand. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add `replayController` field on LeylineServer (set on first MD connection in replay mode) - Pass it to DebugServer as a lazy lambda in LeylineMain.buildDebugServer() - Add `replayController: () -> ReplayController?` param to DebugServer constructor Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GET /api/replay/status — current frame, total, greType, fileName, atEnd, active - POST /api/replay/next — advance one frame, return updated status - GET /api/replay/index — full ordered frame metadata list - ReplayStatusDto / ReplayFrameDto serializable DTOs - Returns inactive stub when no replay is active (safe for non-replay mode) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Raw proxy captures use NNN_MD_S-C_MATCH_DATA.bin naming in capture/frames/, not S-C_MATCH* in capture/payloads/. Updated format detection and justfile default path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
In-memory SQLite was failing due to Exposed connection pooling. Use the on-disk player.db (has decks/player data needed for FD handshake), fall back to in-memory only if no DB exists. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Proxy captures in capture/frames/ include the TCP frame header (6 bytes: type + length). Must strip before parsing as protobuf. Engine dumps don't have this header. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Recent proxy sessions write decoded payloads (no frame header) to capture/seat-1/md-payloads/. Older sessions use capture/frames/ with 6-byte headers. Detection order: seat-1 payloads > raw frames > engine > legacy. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Seat-1 payloads only contain seat-1's auth response. Familiar (seat-2) needs auth too. Fall back to any available auth and patch the clientId to match the requesting seat. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add replayLock synchronizing next(), handleGre, handleConnect (debug-http thread vs Netty I/O thread access to pendingCtx and greEvents) - Track grePosition by actual frame index from popped payload, not call count — fixes drift when popGreForSeat skips frames Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
7ac3eaf to
2683ec5
Compare
Summary
Replay recorded game sessions through Arena's renderer with frame-by-frame control via debug API. Feed captured protobuf frames back to the client — full card art, animations, board rendering — controlled by
curlor any HTTP client.Use case: "I played a real server game and forgot what cards I saw." Start replay mode, connect Arena, step through frames, visually recall the game.
Closes #238 (partial — v0 forward-only scrubbing, no backward/goto yet)
How it works
just serve-replayloads a recording directory, detects the format (proxy seat-1 payloads, raw proxy frames, or engine dumps), and starts the normal FD+MD server with aReplayHandlerinstead of Forge. Arena connects normally — auth, room state, match entry all work. GRE frames are paused until the debug API advances them.API
GET /api/replay/status— current frame, total frames, GRE type, active statePOST /api/replay/next— advance one frame, returns updated statusGET /api/replay/index— ordered metadata for all GRE framesSmoke tested
Known limitations (v1)
POST /nextfires immediately — no auto-play timer yetChanges
ReplayControllerinterface —FrameInfo,ReplayStatus,next(),status(),frameIndexReplayHandlerimplementsReplayController— pauses on GRE frames, advances vianext()LeylineServerexposes controller,DebugServergets replay endpointscapture/seat-1/md-payloads/>capture/frames/(with header strip) >engine/> legacyplayer.dbin replay mode (fixes in-memory SQLite crash)Test plan
just buildcompiles clean/api/replay/statusshows correct frame count and position/api/replay/indexreturns frame metadata array