Chessko · Architecture
How Chessko works: a chess engine that runs entirely in your browser
Chessko looks like a toy: squares of lime and strawberry jelly, pieces that wobble when they land. Underneath it is a complete chess program, and almost all of it runs in your browser tab. This is the map of how the parts fit together.
The short version
When you move a piece, four things happen, all on your device:
- A rules engine written in JavaScript checks the move is legal and updates the board.
- The move is sent to an engine worker, a background thread, so the board keeps animating while the computer thinks.
- Depending on the difficulty you picked, the reply comes from Chessko's own alpha-beta search with a small trained evaluation, or from Stockfish 19 compiled to WebAssembly.
- The reply is played on the board, and the jelly does its wobble.
The server is not in that loop. It hands out the page and a few small files, and it remembers how your finished games ended. It never sees a move.
The layers
| Layer | Runs | Job |
|---|---|---|
Rules engine (chess.js) | Browser | Legal moves, castling, en passant, promotion, check and mate, SAN and FEN. Uses a 0x88 board and is checked against the standard perft node counts. |
Jelly search (search.js, eval.js) | Browser, Web Worker | Levels 1 to 5. Iterative-deepening alpha-beta with a learned-plus-classical evaluation. |
| Stockfish 19 (WebAssembly) | Browser, second Web Worker | Levels 6 and 7, and the hint button. |
Opening book and difficulty bandit (ml.js) | Browser | Varied openings for weak levels; suggests when the level is too easy or too hard for you. |
Board and jelly (app.js, pieces.js, CSS) | Browser | Click and drag moves, SVG pieces, animation, sound made with the Web Audio API (no audio files). |
| Backend (PHP) | Server | Serves the model, book and level files with caching, and stores each visitor's results. |
Why a Web Worker matters
A chess search is a tight loop that can run for a second or more. Run it on the page's main thread and the whole interface freezes: no animation, no hover, no sound. Chessko puts the search in a Web Worker. The page sends the position and the level, and the worker sends back a move and some statistics (depth reached, nodes searched, time). While it works, the main thread is free to keep the jelly moving.
Stockfish gets its own worker for the same reason. If a new game starts or you undo a move while Stockfish is thinking, the page sends the UCI stop command and discards the stale answer, so an old search can never play a move in the new position.
What the PHP backend does
The backend is deliberately small, because a browser cannot do three things for itself:
- Serve the trained files (
/api/chessko/model,/book,/config) with anETag, so a return visit costs a 304 response instead of a download. - Remember results. When a game ends, the browser posts the result, the level, the side you played and the number of plies. That row is stored under a random identifier your browser generated and keeps in
localStorage. There is no account, and no IP address or user agent is stored. - Report health (
/api/chessko/health): the model's metadata and how many games have been recorded.
The endpoint validates every field, caps the request body at 16 KB, rate-limits how many games one visitor can record per minute, and keeps only the newest 200 games per visitor.
What is deliberately not here
There is no server-side chess: you cannot lose because the server was slow, and the game keeps working if the network drops mid-game. There is no neural-network training in the page. Training happens offline in Python and only the finished weights (about 2 KB) are shipped. There is no tracking of your moves.
Next: how the search works, or jump to the game.