Games and moves
Overview
Active games are stored as GAME records. The sort key encodes metaGame, completion bit (0 = in progress), and game id:
pk: GAME
sk: <metaGame>#0#<gameid>
When a game completes, the completion bit becomes 1 and summary rows are written to COMPLETEDGAMES* families. The live dashboard is built from CURRENTGAMES# merged with USERGAME# overlays (seen, lastChat) for active games. Legacy USER.games[] on the USER record is retired (Phase 5).
Lifecycle
- Created — from an accepted challenge, tournament/event pairing, or
start_solo_game(1-player, always unrated). - In progress —
submit_movevalidates via@abstractplay/gameslib, updates state, clocks, andtoMove. - Automove — for games with the
automoveflag, if the next player has only one legal move, the backend plays it automatically. - Time loss —
timeloss(auth or internal) ends games when clocks expire. - Abandoned —
abandonedhandles resign/abandon flows. - Completed — archived to
COMPLETEDGAMES#*indexes; removed from active lists after the user has seen the result.
Key queries
| Query | Endpoint | Purpose |
|---|---|---|
submit_move |
auth | Play a move |
get_game |
public / auth | Fetch game state |
timeloss |
auth | Report timeout |
abandoned |
auth | Abandon game |
invoke_pie |
auth | Pie rule — reverse player order |
teaching_grant_undo |
auth | Teaching mode — offer opponent review of last ply (see below) |
teaching_keep_move |
auth | Teaching mode — reviewer keeps the move under review |
teaching_undo_move |
auth | Teaching mode — reviewer rolls back to start of their last ply batch |
start_solo_game |
auth | Start solo play (numPlayers: 1, rated: false, optional challengeSeed) |
set_game_state |
auth | Admin state injection |
update_game_settings |
auth | Per-game settings |
submit_comment |
auth | Game chat (GAMECOMMENTS) |
update_note |
auth | Per-user notes (NOTE) |
Teaching mode (mutual undo)
When teachingMode: true on the live GAME (from the challenge at start), players can cooperatively rewind recent plies. Implementation: lib/games/teachingMode.ts.
GAME fields
| Field | When set | Purpose |
|---|---|---|
teachingMode |
Game create | Immutable flag; gates teaching RPCs. |
teachingUndo |
After teaching_grant_undo |
Handshake: grantorSeat, reviewerSeat, stackAtGrant, grantorSeatAfterKeep (0-based seats). Cleared on keep or undo. |
teachingPlyStart |
Start of each human ply on submit_move |
Snapshot: stackIndex, playerTimes[], lastMoveTime — used to restore clocks on undo when the anchor matches the rollback target. |
Handshake
- Normal play —
submit_moveas usual.invoke_pieis not available on teaching games (seat swap is not recorded as an engine ply and cannot be cooperatively reverted). WhileteachingUndois set,submit_moveis also rejected; onlyteaching_keep_move/teaching_undo_moveapply for the reviewer. - Grant (
teaching_grant_undo) — Caller must betoMove(grantor). Requiresstack.length > 1. Reviewer is the actor of the last completed ply (engine.getPlies()/ gameslib ply walk). SetstoMoveto the reviewer; stack unchanged. - Keep (
teaching_keep_move) — Reviewer only. RestorestoMoveto the grantor; clearsteachingUndo. - Undo (
teaching_undo_move) — Reviewer only. Rolls the engine back to the stack length before the reviewer’s trailing same-actor ply run (sequenced double-activation included). Restores clocks fromteachingPlyStartwhenstackIndexmatches; trimsGAMEEXPLORATION#rows withmove≥ new stack length; setstoMoveto the reviewer for a replacement move.
Grant can be offered again on the next turn (including immediately after an undo, without an intervening move). There is no depth cap except the opening (stack.length === 1), where grant is rejected.
Ply anchor on submit_move
For teaching games, before the human engine.move() in applyMove, the server may refresh teachingPlyStart when the mover is not continuing the same seat’s sequenced batch (shouldRefreshPlyStartForEngine).
Pie rule
invoke_pie reverses the player list in the GAME record and updates watcher summaries. Rejected when teachingMode is true on the live game. Pie eligibility uses engine.getFlags() (not static gameinfo.flags) so variant-gated pie / pie-even matches the front. Challenge and tournament creation use effectiveFlags() for perspective, rotate90, and EOG emails use it for scores. The stream projector refreshes each participant's CURRENTGAMES# row on the next GAME modify.
Playground
Users can save unlimited playground positions. Each save is stored as pk: PLAYGROUND#<userid>, sk: <uuid> with name, metaGame, date, and body (JSON; auto-compressed when large). Queries: list_playground_saves, get_playground_save, create_playground_save, save_playground_save, delete_playground_save.
Record types
- Database schema —
GAME,GAMECOMMENTS,NOTE,COMPLETEDGAMES*,PLAYGROUND
Related
- Challenges — how games start
- Gameslib — rules engine