Skip to content

nickjlamb/time-machine-chess

Repository files navigation

♔ Time-Machine Chess

Play the theory of a past era. Chess engines fine-tuned on 150 years of history — face the gambit-happy attackers of 1850, the positional masters of the 1920s, or the iron technique of the Soviet school. Every era bot is validated against the historical record it was trained on.

CI License: MIT Python 3.10+ Built on Maia-2 Live demo

▶ Play it now: chess.pharmatools.ai

Time-Machine Chess — the Romantic Era board with theory snapshot


Why this exists

Modern engines all play the same way: perfectly. But chess style has a history — the King's Gambit ruled 1850 and vanished by 1950; draws tripled; the English Opening rose from nothing. Time-Machine Chess asks: how would masters of each era have approached this position?

Three Maia-2 models, each fine-tuned on games from one era of over-the-board history:

Era Years Era corpus Character
The Romantic Era 1840–1885 10.7k games Gambits, sacrifices, king hunts
The Classical Era 1900–1939 62.8k games Clarity, technique, hypermodern rebellion
The Soviet Era 1950–1985 597k games Preparation, prophylaxis, grinding

Fine-tuning uses balanced ~10–12k-game subsets per era (~2.5M positions total) so every era gets comparable training signal; the full corpora provide the validation baselines below.

The receipts

Each bot played 150 self-play games; identical move-sequence metrics were computed on the bot games and on random samples of the historical corpora. The era gradients reproduce:

Metric Romantic (hist → bot) Classical (hist → bot) Soviet (hist → bot)
King's Gambit rate 14.0% → 22.0% 3.5% → 4.7% 1.25% → 0.7%
1.e4 / 1.d4 / 1.c4 88/6/3 → 97/1/0 48/40/6 → 63/29/4 50/28/11 → 69/15/9
Draw rate 12.0% → 20.7% 25.0% → 15.3% 28.75% → 20.0%

Full analysis, honest residuals included: validation/baselines.md and the live validation page.

Quick start (under 60 seconds)

git clone https://github.com/nickjlamb/time-machine-chess.git
cd time-machine-chess
pip install -r requirements.txt
uvicorn backend.app:app          # open http://localhost:8000

That runs immediately with lightweight heuristic bots. For the real trained era models (~520MB, one command):

pip install maia2 torch
python3 scripts/fetch_models.py  # downloads the fine-tuned checkpoints from Releases

Architecture

flowchart LR
    subgraph Data pipeline
        A[Lumbra's Gigabase\n10M+ dated OTB games] --> B[filter_eras.py\nsplit by year]
        B --> C[prepare_training.py\nMaia-2 rows, mirrored,\nfixed 1900 Elo]
    end
    subgraph Training
        C --> D[finetune_era.py\n1 epoch per era]
        M[Maia-2 pretrained\nNeurIPS 2024] --> D
        D --> E[(models/era.pt\nx3 checkpoints)]
    end
    subgraph Validation
        E --> F[selfplay.py\n150 games/era +\nresignation adjudication]
        F --> G[analyze_selfplay.py\nvs. historical baselines]
    end
    subgraph Serving
        E --> H[FastAPI backend\nlazy LRU model cache]
        H --> I[Zero-dependency frontend\nera boards, SVG pieces,\nWeb Audio sounds]
        G --> J[/validation page/]
    end
Loading

Design choices worth knowing: policy-head sampling with no search (human-like by construction, CPU-cheap); full-diversity temperature in the opening, sharpened after (era character lives in opening diversity); fixed 1900-Elo conditioning since historical games lack ratings (eras differ in style, not strength); optimistic client-side move rendering for zero-latency play.

API examples

# What eras exist?
curl -s localhost:8000/api/eras | jq keys

# Ask the Romantic era to respond to 1.e4 e5 2.f4 (it will probably accept)
curl -s -X POST localhost:8000/api/move -H 'Content-Type: application/json' \
  -d '{"era":"romantic","fen":"rnbqkbnr/pppp1ppp/8/4p3/4PP2/8/PPPP2PP/RNBQKBNR b KQkq - 0 2"}'

# Play a move and get the era's reply in one call
curl -s -X POST localhost:8000/api/play -H 'Content-Type: application/json' \
  -d '{"era":"soviet","fen":"<any FEN>","move":"e2e4"}'

Train your own era

Full pipeline (data → training → validation)
  1. Data: download dated PGNs (e.g. Lumbra's Gigabase OTB time-range files), then python3 scripts/filter_eras.py data/master_games.pgn. Era windows live in config/eras.yaml.
  2. Baselines: python3 scripts/corpus_stats.py
  3. Training rows: python3 scripts/prepare_training.py <era>
  4. Fine-tune: python3 training/finetune_era.py <era> (GPU or Apple Silicon --device mps; one epoch ≈ minutes). Held-out accuracy must beat the pre-finetune baseline.
  5. Gate: python3 -m scripts.sanity_gate — eras must be stylistically distinguishable.
  6. Validate: python3 scripts/selfplay.py <era> --games 150 then python3 scripts/analyze_selfplay.py; results render at /validation.

Add a new era by extending config/eras.yaml — the pipeline picks it up end to end.

Deployment (Railway/Docker)

The Dockerfile pulls model weights from the GitHub release at build time (weights are not in git). railway.json configures the healthcheck. MAX_LOADED_MODELS=1 keeps RAM ~1GB by LRU-swapping era models (~2s swap). See the file comments for details.

Roadmap

  • Year slider — one era-conditioned model instead of three checkpoints, play any year
  • More eras — pre-1840 romantic prehistory; 1990s "engine dawn"; 2010s engine era
  • Era commentary — "a Romantic would never decline this gambit" move annotations
  • Draw-agreement modeling — the missing Soviet-era draw culture (see validation residuals)
  • Era-accurate resignation manners — resign timing varied by era too
  • Mobile PWA polish

Contributing

PRs welcome — see CONTRIBUTING.md. The most valuable contributions right now are new era definitions, better era-signature metrics, and frontend polish. Run pytest before submitting; CI runs the same suite.

License & attribution

Code: MIT © Nick Lamb. Built on Maia-2 (MIT, CSSLab, University of Toronto — paper). Training data: Lumbra's Gigabase (CC BY-NC-SA 4.0, not redistributed here). Piece sets via lichess-org/lila: Merida (A. H. Marroquin), Alpha (E. Bentzen), cburnett (C. M. L. Burnett) — fetch with python3 scripts/fetch_pieces.py; each retains its original license. Sounds are synthesized in-browser with the Web Audio API — no audio files, no licenses.

About

Play chess against the theory of a past era — era-fine-tuned Maia-2 models, validated against history

Topics

Resources

License

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors