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.

Alpha–beta in one picture: once one reply already refutes a move, the remaining branches are never read at all. Good move ordering is what makes the crossed-out part most of the tree.

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.

The strength dial, measured — 240 self-play decisions per setting
rank decayavg loss (cp)plays best movereads as
1.0052.412%the old AL-1200
0.9232.922%casual
0.8522.030%club-curious
0.7817.443%~1600← shipped
0.7413.644%a strong day
0.7011.652%stronger than him
0.628.160%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 choice distribution over one position’s ranked moves. Weights fall by temperature and rank; everything worse than best by 300 centipawns — less than a knight — is cut outright, so cleanly hanging a piece is structurally impossible rather than merely unlikely.

The contract, in full — each line is asserted by a test, not hoped for:

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.