Teaching a network to play a game
nobody has theory for
An AlphaZero-style engine trained from scratch on extinction chess — a variant with no checkmate, where you win by capturing every piece of any single type. No opening books, no engines to learn from, no human games. Just self-play, 1,046 iterations deep and still running.
- Role
- Sole developer
- Timeline
- Sep 2025 — ongoing
- Training
- WATcloud SLURM cluster
- Stack
- PyTorch, C++, Modal, NumPy
The variant
Extinction chess keeps every rule of standard chess — castling, en passant, promotion — and replaces the win condition. There is no check and no checkmate. Kings are ordinary pieces: they can be captured, and you can promote a pawn into one.
You win by driving any one of your opponent's piece types to zero. Take their last knight, and the game ends immediately. That single change cascades through everything.
Standard chess
- Win by checkmating the king
- King is uniquely protected
- Material trades are broadly good when ahead
- Centuries of opening theory
Extinction chess
- Win by eliminating any one piece type
- King is an ordinary piece; promotion to king is legal
- Trading down can lose you the game instantly
- Essentially no published theory
The strategic consequences are strange. Pieces you would happily trade in normal chess become liabilities the moment they're your last of a kind. A rook you'd usually cash in becomes untouchable. And because every piece type is a potential losing condition, the engine has to track seven simultaneous extinction races at once.
Because the variant has no literature, there was no way to check whether the engine was playing well — only whether it was playing better than its former self.
Play it
This is the real engine — az_iter1040, the same
checkpoint described below, running on a GPU that's allocated to your
game for as long as you're playing. It thinks while you think.
Promote to
Engine analysis
No move yet.
Click a move, or use ← → keys.
Architecture
The engine follows the AlphaZero template: a single network with a policy head and a value head, guiding Monte Carlo tree search. Both heads train purely on the outcomes of the network's own games.
The input encoding is where the variant shows up. Alongside the usual piece planes, board history, castling rights and move clock, there's a dedicated endangered plane — an explicit signal for which piece types are close to extinction. The network could in principle infer this by counting pieces, but making it an input rather than something to be learned lets the residual tower spend its capacity on strategy instead of arithmetic.
Terminal-position drilling
Pure self-play produced a specific, persistent weakness: the engine would build a winning position and then fail to actually take the final piece. Extinction wins are single, exact captures, and they show up too rarely in ordinary self-play for the value head to weight them properly.
The fix was a supplementary training phase. After each iteration's normal training, the trainer generates synthetic near-terminal positions — boards one capture away from extinction — and drills the network on them at a reduced learning rate, with a second, harder pool trained more gently still. It's a targeted correction for a failure mode that self-play alone doesn't sample often enough to fix.
Training at scale
Every iteration plays ~400 complete games against itself, harvests roughly 15,000 positions, and trains on a rolling buffer of the last five iterations. Then it does it again.
That works out to well over 400,000 self-play games and several billion network evaluations, spread across months of cluster time on a mix of RTX 3090, 4090 and A10G nodes. Self-play generation is the bottleneck by an enormous margin — the training step itself takes about two minutes of each three-and-a-half-hour cycle.
The infrastructure is most of the work
A training run this long is really an exercise in operations. Jobs hit SLURM wall-time limits and have to resubmit themselves mid-run, preserving the replay buffer and optimizer state. Nodes fail. GPUs crash mid-benchmark. At one point a driver upgrade drained the node that generated supplementary games, and the loss curves quietly improved — not because the model got better, but because a smaller training set is a cleaner, weaker signal. Reading that correctly, rather than celebrating it, mattered.
A separate incident cost a full iteration of logs: a job died with a
node failure, SLURM auto-requeued it, and the requeued run truncated
the original log file on open. The fix was one flag — appending rather
than truncating — but finding it meant reconstructing what happened
from checkpoint timestamps and the raw .npz buffers on disk.
Knowing whether it's improving
With no external opponent to measure against, strength is assessed entirely through self-reference. Every tenth checkpoint triggers an automated battery: head-to-head matches against recent iterations, against distant historical anchors, a fixed tactical suite, and a "win-taking" test that checks whether the engine converts positions where an extinction capture is available.
This is noisier than it sounds. Head-to-head results between adjacent checkpoints sit inside a ±7% noise floor, so a single matchup proves nothing. Distant anchors are the load-bearing signal — if the current model still beats a checkpoint from 900 iterations ago at the same rate, it hasn't regressed, even when neighbouring comparisons look flat.
Reading a plateau correctly
Around iteration 1,020 progress stalled — the model scored 51% against its own predecessor from ten iterations earlier, statistically indistinguishable from a tie. The diagnosis was that MCTS exploration had narrowed: the search kept re-confirming what the network already believed instead of testing alternatives.
Widening the root exploration parameters produced exactly the pattern you'd hope for and exactly the one that looks like failure. Policy loss rose. Head-to-head results against recent checkpoints dipped below 50%. But the tactical suite jumped 15 points, and performance against distant historical anchors held steady. The model wasn't worse — it was playing differently, which depresses head-to-head scores against its own recent lineage. Reverting on the loss curve alone would have thrown away a real improvement.
What I'd do differently
Batched evaluation from the start. The evaluator scores one position at a time, which leaves the GPU badly underutilised during self-play. Batching across parallel games is the single biggest available speedup and it's structurally awkward to retrofit.
A fixed external benchmark. Everything is measured against the model's own history, which detects regression well but can't detect a shared blind spot. Even a modest hand-written alpha-beta opponent would have provided an absolute reference point.
A serialisation format on day one. There's still no FEN equivalent for extinction positions, so positions move around as replayed move histories. That was fine for a training pipeline and is the first thing that needs solving to put the engine on the web.