Skip to content

Work / Chess / Wiki / Entities

saved-game

Entitydraftverified 2026-10-12

STORAGE.SAVED-GAME

Summary

The one unfinished game the phone keeps, and the shape it is stored in. Draft: the app is built to this; the owner's schema review (T004) is still open.

Canonical values

Key Value Last changed
STORE react-native-mmkv, the same store tic-tac-toe, charades and imposter use. localStorage on web 2026-10-12
KEY chess.game.v1 — one JSON string. Absent when there is no unfinished game 2026-10-12
COUNT one. Starting a new game replaces it 2026-10-12
WRITTEN after every move, on Undo, on Flip, and when the pause sheet opens (that is when the clocks are stamped) 2026-10-12
REMOVED the moment the game ends, by any of the six results. A finished game is never stored 2026-10-12
SOURCE-OF-TRUTH the move list. The board, captures, castling rights, en passant square, fifty-move count, repetition count and the notation are all replayed from it and never stored 2026-10-12
UNREADABLE wrong version, bad JSON, or a move the engine rejects on replay: the key is removed and Home shows its empty state. Nothing crashes 2026-10-12
UNDO every move can be taken back, to the first. Against the computer, Undo takes back your move and its reply. Was: the last 10 positions 2026-10-12

Shape

interface SavedGame {
  version: 1;
  startedAt: number;          // epoch ms
  updatedAt: number;          // epoch ms
  setup: GameSetup;
  moves: MoveRecord[];        // in order, White's first
  clock: ClockState | null;   // null when setup.clock is "none"
  flipped: boolean;           // the Flip button under the board
}

interface GameSetup {
  opponent: "computer" | "friend";
  level: "easy" | "medium" | "hard" | null;      // null for a friend game
  humanSide: "white" | "black" | null;           // resolved: Random is rolled once, at Start. null for a friend game
  seating: "face-to-face" | "pass" | null;       // null against the computer
  clock: "none" | "5" | "10" | "15+10";
}

interface MoveRecord {
  from: string;               // "e2"
  to: string;                 // "e4"
  promotion?: "queen" | "rook" | "bishop" | "knight";
}

interface ClockState {
  whiteMs: number;            // time left when last written
  blackMs: number;
}

Rules

  • The move list is the game. Storing the board beside it gives two copies that can disagree; replaying 80 moves costs under a millisecond.
  • A saved clock is a stopped clock. Time does not run while the app is closed. Resume starts the mover's clock from the stored value.
  • Random is never stored. The roll happens once, when Start game is tapped, so Resume cannot hand the player the other colour.
  • Castling and en passant squares are not stored. from and to are enough: the engine works out that e1→g1 by a king is castling.
  • A version bump is a new key. chess.game.v2 would read v1 once, convert or discard it, and remove it.
  • Nothing leaves the phone. There is no server to send it to.

Not stored

  • The result. A result screen is in memory; killing the app on it opens Home empty.
  • The selected square, the open sheet, the "computer is thinking" state.
  • How many rematches since the last full-screen ad. That count lives in memory and resets when the app restarts.

Related

Sources

  • .context/features/0005-resume-game.md
  • .context/designs/mobile/home/home.html, game/setup-computer.html, game/setup-friend.html