The engine room · build notes
How AL‑1600 works.
No chess library, no Stockfish behind a curtain, no server doing the thinking. AL‑1600 is under 1,200 lines of plain JavaScript in a single hand-written file, running entirely in your browser — and the reason it feels like playing Alex is everything wrapped around the search. These are the build notes.
AL‑1600 · specificationengine.js
- Search
- 4 plies +6 quiescence
- Hash table
- 65,536 entries
- Temperature
- 130 centipawns
- Rank decay
- ×0.78 per rank
- Blunder ceiling
- 300 cp < one knight
- Opening book
- 293 positions, his games
01representationThe board it thinks on.
The board is a 0x88 array — 128 slots, laid out as if two boards sat side
by side, with the real game on the left half. The layout buys one very cheap trick:
a square is off the board exactly when (sq & 0x88) !== 0, a single
bitwise test, so move generation never bounds-checks anything. Pieces are signed
integers, White positive, Black negative.
Every position also carries a 64-bit Zobrist fingerprint, built from two 32-bit halves drawn from two independent fixed-seed random streams, updated incrementally as moves are made and unmade. The fingerprint powers threefold-repetition detection and the transposition table below — and because the seeds are fixed, the Hint button gives the same advice in the same position, every time.
02searchLooking four moves ahead.
The core is negamax with alpha–beta pruning, four plies deep: assume both sides play their best move, and stop reading any branch the moment it provably cannot change the answer. Pruning only bites if the likely-best moves are read first, so moves are ordered before they are searched — the hash table’s remembered best move, then captures ranked most valuable victim, least valuable attacker, then “killer” moves that caused cutoffs at the same depth, then a history score. After the first move at a node, the rest are searched with a null window just to prove they are worse (principal variation search); only a surprise gets re-searched in full.
At the four-ply horizon the engine does not simply open its eyes mid-trade and call the position even. A quiescence search keeps resolving captures and check evasions for up to six further plies, so every number the engine believes comes from a quiet position. And a 65,536-entry transposition table remembers what was already searched, keyed by the Zobrist fingerprint and stamped by generation so entries from an old position quietly expire.
03evaluationWhat a position is worth.
When the search runs out of depth it asks the evaluation for a number: material (pawn 100, knight 320, bishop 330, rook 500, queen 900) plus piece-square tables — small bonuses that pull knights toward the centre, push pawns forward, and keep the king hidden in the middlegame. That is deliberately all: no pawn-structure terms, no king-safety heuristics. At four plies plus quiescence, the search itself carries the tactics, and a thin evaluation stays fast and debuggable.
One exception earns its keep. In a won endgame — up at least four pawns of material against a nearly bare king — the evaluation switches goals: drive the enemy king to the edge, walk your own king toward it. The middlegame “stay hidden” king table is switched off inside that mode, because it was measured fighting the march — an engine up a whole rook would happily shuffle forever rather than expose its king to deliver mate.
04the weakness modelPlaying worse, on purpose.
A chess engine’s natural state is far stronger than any human it imitates, so the interesting problem runs backwards: how do you make it fallible without making it embarrassing? The usual trick — searching shallower, or mixing in random moves — produces blunders no human would make. AL‑1600 does it differently: it always searches at full strength, then chooses imperfectly. Its mistakes are mistakes of choice, never of sight.
Every legal root move is scored by the honest search, then weighted by a
softmax: a move’s weight is exp((score − best) / 130),
so near-equal moves stay live and clearly-worse ones fade exponentially. Each weight is
then multiplied by 0.78 per rank down the list — and that rank decay, not
the temperature, is the real strength dial. A plain softmax has no term for how many
near-ties exist: in a bushy 32-move position the long tail of “almost as good”
moves collectively outvoted the best move about 20-to-1, and the old bot played its own
best move only 12–14% of the time.
| rank decay | avg loss (cp) | plays best move | reads as |
|---|---|---|---|
| 1.00 | 52.4 | 12% | the old AL-1200 |
| 0.92 | 32.9 | 22% | casual |
| 0.85 | 22.0 | 30% | club-curious |
| 0.78 | 17.4 | 43% | ~1600← shipped |
| 0.74 | 13.6 | 44% | a strong day |
| 0.70 | 11.6 | 52% | stronger than him |
| 0.62 | 8.1 | 60% | no longer human |
0.78 lands the engine around Alex’s own live ratings (blitz 1542, bullet 1665, rapid 1716 when it was tuned) — 1600-ish by construction, not a certified Elo. One more human touch rides on top: a move that recaptures on the square the opponent just took on gets its weight nudged ×40. The search is often right that recapturing can wait a move — but a human takes back on reflex, and the bot should too (measured: it recaptures 99.4% of the time).
The contract, in full — each line is asserted by a test, not hoped for:
- No move more than 300 centipawns below best is ever played — below the value of a knight, enforced by comparing exact scores.
- A move that allows a forced mate is excluded outright, whatever its weight; a mate the bot can see is always played.
- In a won endgame the dice are put away entirely — the bot converts with its honest best move until it mates you.
- The Hint never gambles: it is the same search with the weakness model switched off, deterministic and at full strength.
05openingsThe part that is actually him.
Out of the opening, AL‑1600 does not consult theory — it consults Alex’s own games. A generator replays every 2025-onward game from his chess.com account and distils them into a book: positions he has reached at least three times, replies he has played at least twice, up to sixteen plies deep. Today that is 293 positions and 406 weighted moves. Weights are how often he actually played each reply, so the bot doesn’t just play his openings — it plays them at his frequencies: the Queen’s Gambit as White (the White half of the book is built exclusively from his 1.d4 games), the ...d5/...c5 counterattack against the London and 1...e5 against 1.e4 as Black.
The book belongs to the bot alone — the Hint never reads it, so the advice you get is always what the search honestly believes, never repertoire. And the book does not go stale: a scheduled job runs every Monday morning, pulls his newest games, regenerates the book deterministically, proves every line legal against the engine’s own move generator, and commits only if the repertoire actually moved. He keeps playing; the bot keeps up.
06verificationKeeping it honest.
A bot that carries someone’s name cannot be allowed to look stupid, so the test suite is built to embarrass it. Perft counts every leaf of the move tree from tricky reference positions and matches the published, independently verified node counts (197,281 from the start position at depth four; castling, en-passant, promotion and pin traps included) — any miscount means move generation is broken. A tactics suite checks it finds mates and wins material. A gauntlet throws scholar’s-mate cheese and random movers at it, then audits every move with a wider search — zero choices ≥300cp below best, across everything flagged — and hands it won endgames from awkward corners, where it must force mate against best defence, not shuffle. The opening book passes three separate legality fences before a single move ships.
None of this asks to be taken on faith. The entire site — engine, book, generator, tests, this page — is public at github.com/alexanderli07/website. Read the source. Then come beat it.