14 KiB
14 KiB
Replay Implementation Tasks
This plan covers the remaining work needed for first-class replay in the web and CLI clients, durable player-game browsing, and a curated archive of famous games. The persistence/API foundation completed by the database audit is listed first so later work does not duplicate or bypass it.
Foundation Available Now
- Store terminal
resultandend_time_utcon each game. - Persist
white_claimed_byandblack_claimed_by, including claims made on the first valid move after game creation. - Commit each move, first-move claim, and move-caused terminal result in one SQLite transaction.
- Rewind moves and clear a stale terminal result/end time in one transaction.
- Return ordered UCI moves with
fenAfterMovethroughGET /api/v1/games/{gameId}/history. - Return bounded pages through authenticated
GET /api/v1/users/me/games?limit=&offset=. - Provide matching Go client DTOs and methods (
GetGameHistory,GetMyGames) without prematurely adding CLI presentation. - Evict terminal games from memory after a configurable TTL while retaining durable rows and moves.
- Add an async-write barrier and a single SQLite read snapshot for immediate, internally consistent replay reads.
- Configure browser API origin through
/config, with/chessfallback for the static deployment atlixen.com/projects/chess/. - Add debug-level persistence, cleanup, engine queue, and lifecycle logging.
Decisions Required Before Replay UI Work
| Decision | Current behavior | Decision needed |
|---|---|---|
| History visibility | Public to anyone with a game UUID, like live game reads | Keep public, make games private by default, or add per-game visibility |
| Live mutation authorization | Configure, undo, computer-trigger, and unload remain UUID-based; claimed slots protect human moves only | Define owner/opponent/spectator permissions before replay and archive UI expose more game discovery |
| Database retention | Indefinite; only terminal in-memory state is evicted | Retention by account type, archive status, age, or explicit deletion |
| Delete semantics | DELETE /games/{id} unloads memory but retains history |
Name it “close/unload,” or add a separate authorized durable delete |
| Durability guarantee | Gameplay continues after a write failure; health becomes degraded | Keep best-effort, acknowledge writes, retry with an outbox, or fail gameplay closed |
| Result model | white_wins, black_wins, draw, stalemate |
Keep compatibility or split outcome (1-0, 0-1, 1/2-1/2) from termination reason |
| Archived-game owner | Not created | Protected system/demo user, separate archive owner table, or ownerless source records |
| Replay notation | UCI plus authoritative FEN after each move | Add SAN and canonical PGN at import/write time or derive them on read |
Record these choices in an ADR before changing the v1 response contract. Until privacy is decided, do not add searchable public player-game indexes or expose usernames in public history.
Phase 1 — Complete the Durable Game Model
Results and termination
- Represent outcome separately from termination reason. Candidate fields:
outcome,termination, and optionalresult_detail. - Detect and persist all supported draw paths, not only stalemate: insufficient material, repetition, fifty/seventy-five-move rule, and agreed draw if that interaction is added.
- Define behavior for resignation, timeout, abandonment, engine failure, and administrative termination.
- Add constraints covering valid combinations: an end time requires a terminal outcome; an ongoing game has neither.
- Decide whether undoing a finished rated/player game is allowed. If yes, preserve an audit event rather than silently rewriting official history.
Stable participant metadata
- Snapshot display names at game start/end so replay remains readable after a user rename or temporary-account deletion.
- Separate historical participant identity from mutable controller config. Changing a human slot to a computer must never remove the user's game link.
- Decide whether anonymous players receive a durable pseudonym, remain unnamed, or are excluded from archive browsing.
- Add optional clocks/time-control metadata before timeout results are supported.
Notation and integrity
- Add SAN per ply and canonical PGN, or add a deterministic backend converter from the stored initial FEN/UCI line.
- Validate that
move_number,player_color, FEN side-to-move, and the previous position form one legal continuous line. - Add a stored content hash for import idempotency and corruption checks.
- Add a repair/audit CLI command that reports broken game rows without mutating them; make repair an explicit separate operation.
- Define a schema-migration policy beyond v2, including forward-version rejection, backup instructions, and rollback limitations.
Phase 2 — Replay and Library APIs
Player games
- Add filters to the authenticated list:
status,result, color, opponent type, and date range. - Replace offset pagination with a stable
(start_time_utc, game_id)cursor before the table grows large; retain v1 offset parameters during migration. - Return a compact display label/opponent summary so clients do not recreate association logic.
- Define whether an authenticated user may list a game merely created for their random player ID versus one explicitly claimed by them.
- Add authorization tests for expired/deleted sessions and attempts to list another user's games.
Replay payload
- Version the history payload before adding annotations, evaluations, comments, variations, clocks, or PGN tags.
- Include a canonical final FEN and normalized outcome/termination fields.
- Decide whether long games return one payload or paged/chunked moves.
- Add
ETag/If-None-Matchfor immutable finished histories. - Add a downloadable PGN response with correct
Content-Typeand filename. - Return an explicit “ongoing/incomplete” marker when history is requested before a terminal result.
Live-game restoration
- Decide whether a server restart should make unfinished games playable or replay-only.
- If play must resume, load the last persisted FEN, next turn, player config, claims, and move list into memory at startup.
- Mark games interrupted in
pendingstate as recoverablestuckorongoing; never re-submit an engine task blindly. - Define reconciliation when the service previously entered degraded mode and memory contains moves absent from SQLite.
Phase 3 — Curated Famous-Game Archive
Schema and ownership
- Add a game origin such as
player,curated, orimported. - Add searchable archive metadata: title, event, site, event date, round, white/black display names, Elo values, ECO/opening, source URL, source license, attribution text, and import timestamp.
- Add publication state, featured flag, and explicit featured rank/order.
- Create a protected demo/system identity only if ownership remains tied to users. It must not consume temporary-user capacity, expire, authenticate, or be evicted/deleted through normal user tools.
- Prefer a separate protected archive owner over credentials embedded in seed scripts.
- Add only indexes backed by actual archive queries; confirm each with
EXPLAIN QUERY PLANand a representative data volume.
Import pipeline
- Add
chess-server db archive importfor one PGN or a directory. - Parse PGN tags, comments, NAGs, and variations deliberately; document which are preserved and which are discarded in the first version.
- Validate every main-line move from its initial position and generate the authoritative FEN sequence before opening the transaction.
- Import a game and all moves in one transaction.
- Make repeated imports idempotent by source key/content hash.
- Add dry-run, structured error output, per-file summary, and all-or-nothing versus continue-on-error modes.
- Preserve source attribution and verify redistribution rights for every bundled collection.
- Seed a small, reviewed fixture set in tests; keep large archives outside the executable and repository unless licensing and binary size are accepted.
Archive API
- Add a public, bounded curated list endpoint with stable sorting.
- Add exact filters required by the UI (featured, player name, event, year, ECO); do not expose an unconstrained database query API.
- Reuse the same history representation for player and curated games.
- Cache immutable curated list/history responses and invalidate only on archive administration.
Phase 4 — CLI Replay Experience
- Add
games/games mineto callGetMyGames, show pagination, result, colors, opponent/controller, date, and move count. - Add
games featuredafter the curated endpoint exists. - Add
replay <gameId>and allow selection from a prior list result. - Render the initial FEN before ply 1; never assume the standard start.
- Add next/previous/start/end navigation, move-number jump, and optional autoplay speed.
- Display UCI initially and SAN once the backend contract supplies it.
- Clearly separate replay state from live session state: replay commands must not poll, move, undo, configure, or delete the live game.
- Add
pgn save <path>after the PGN endpoint is defined. - Cover empty lists, ongoing histories, custom FEN, malformed/incomplete history, expired auth, server restart, and deleted live-memory state.
Phase 5 — Web Replay Experience
- Add “My games” for authenticated users and a separate “Classic games” collection available without login.
- Build accessible loading, empty, pagination, and error states.
- Add a replay route/deep link, for example
?replay=<gameId>, that works beneath/projects/chess/and does not assume the API shares that path. - Initialize from
initialFen; step by assigning the storedfenAfterMove, not by replaying moves through a browser chess engine. - Add previous/next/start/end buttons, move-list selection, keyboard controls, autoplay speed, pause, and current-ply announcement.
- Disable move, computer-trigger, undo, and player-configuration actions in replay mode.
- Stop live long-polling when replay mode begins and restore it only when a live game is explicitly reopened.
- Show result, termination, players, date/event, source attribution, and custom-start notice.
- Make browser back/forward restore list filters and replay ply.
- Test both embedded
/configand the deployed/chessfallback, including CORS and reverse-proxy headers. - Add responsive and accessibility checks for board orientation, focus, screen-reader labels, reduced motion, and high contrast.
Phase 6 — Durability, Operations, and Scale
- Choose and implement the durability contract from the decision table. For acknowledged persistence, return success only after a writer receipt or use a durable outbox with retries and ordering.
- Expose counters/metrics for queue depth, enqueue rejection, write latency, failed transaction, barrier latency, replay read latency, and terminal-memory eviction.
- Add request/game correlation fields to logs without logging JWTs, passwords, or full private payloads.
- Configure a stable production JWT signing key (prefer a secret file or deployment secret) so persisted sessions can survive a server restart; document rotation and invalidation procedures.
- Add a bounded degraded-mode recovery procedure; current behavior requires operator intervention/restart and cannot reconstruct missing writes.
- Benchmark list and history queries with realistic user/archive sizes and verify query plans in CI.
- Set WAL checkpoint and database backup procedures; test online backup and restore with active reads/writes.
- Define database retention separately for anonymous, temporary-user, permanent-user, and curated games.
- Add authorized durable deletion/anonymization if required by the privacy policy, with archive records protected from accidental cascades.
Required Test Matrix
- Upgrade a production-shaped legacy database to every new schema version and reopen it with foreign keys enabled on multiple pooled connections.
- Read history immediately after create, move, terminal move, slot claim, player reconfiguration, and undo—without sleeps.
- Run concurrent legal moves from one position; exactly one may commit and
the loser must receive
GAME_CONFLICT. - Submit duplicate computer triggers; only one engine task may run.
- Fill the write queue/fault SQLite and assert degraded health, visible logging, and documented client behavior.
- Shut down with queued writes and prove all accepted writes drain.
- Restart after a finished game and replay the exact FEN sequence/result.
- Evict a terminal game from memory and replay it from SQLite.
- Change a claimed human slot to computer and verify “My games” association remains.
- Verify registration duplicate/session failures roll back account creation and capacity eviction.
- Exercise public/private history rules for anonymous, owner, opponent, and unrelated authenticated clients.
- Validate imported PGNs with promotions, castling, en passant, custom FEN, comments, and every supported result.
- Run Go unit/race tests, HTTP integration scripts, JavaScript syntax/tests, and browser end-to-end replay navigation in CI.
Replay Definition of Done
- A finished player game survives restart, appears once in its owner's list, and replays deterministically from the stored initial FEN to the stored final FEN in both clients.
- A curated game is imported idempotently with source attribution, appears in a stable public collection, and uses the same replay path as a player game.
- Undo, player reconfiguration, terminal eviction, and concurrent requests cannot produce a stale result, missing claim, duplicate ply, or mixed history snapshot.
- Privacy, retention, durable deletion, and degraded-write behavior are documented and enforced consistently by API, storage, web, and CLI layers.
- Query plans and benchmarks show no redundant indexes or unbounded list scans at the agreed deployment size.