A short tour of the engine setup, how positions are evaluated, and how we simulate play weaker than Stockfish's native minimum rating.
Every game and analysis runs entirely in your browser using a single-threaded Stockfish 18 WebAssembly build. We spin up two workers side by side: one dedicated to the opponent's actual moves, and a second one dedicated to background analysis (eval bar, move ranking, and the move-quality labels). Splitting them keeps the opponent responsive even while the analysis engine is still thinking.
You control two knobs that apply to every search: search depth (how many plies deep the engine looks) and a max think time (a safety cap so a single move can't hang forever). If depth completes first, the engine returns early; if the timer fires first, we take the best move found so far.
The eval bar and the analysis card both use Stockfish's own centipawn score. A centipawn is one one-hundredth of a pawn, so "+150" means White is up roughly a pawn and a half of material and position combined. Mate scores are shown as "M5" for "mate in five", and always pin the eval bar fully to that side.
Move quality (best / excellent / good / inaccuracy / mistake / miss / blunder) is derived by comparing the eval before your move to the eval after, from your side's perspective. Small drops are fine, big drops in a winning position are labelled harshly, and a move that hangs mate is always a blunder.
Stockfish's built-in UCI_Elo knob only reaches down to about 1320, and even in its supported range it works by capping search depth and adding random noise to the top candidates — which means blunder magnitude is unbounded (Stockfish at 1800 will still occasionally hang a queen). We don't use that knob for the opponent. Instead, at every Elo below full strength we ask the engine to score every legal move (MultiPV), then pick from that list ourselves against a centipawn-loss ceiling tied to the target rating.
The rule of thumb: the lower the target rating, the more centipawns of loss we tolerate when picking a move. A "very weak" bot happily picks a move that loses a full piece; a "moderately weak" bot might only accept losing a pawn or two. We also apply a couple of safety filters — the bot won't blindly walk into mate, and it won't hang a queen for nothing unless that's the flavour of weakness we're simulating. The result is a bot that makes plausible human-looking mistakes rather than a bot that plays random moves.
Modes with special behaviours (steering for a draw, mimicking your piece type, blundering on purpose, chasing captures) layer the same idea on top: score every candidate, then pick from the pool that fits the rule — and if no move fits, fall back to a normal move at the current rating.
The game modes here were primarily inspired by the "I Want Checkmate" YouTube channel, whose videos on tweaking, weakening, and stress-testing Stockfish sparked most of these ideas. Go watch his stuff — it's great: youtube.com/@iwantcheckmate.
On top of the centipawn-loss ceiling, a few extra layers push the weak-opponent's moves toward something that looks human rather than like a random-move generator:
The rating cap is exposed as 3600, which is above Stockfish's commonly-cited 3190 ceiling. In practice long-time-control Stockfish 18 reaches into that range, and the higher rungs of the ladder give it the depth and time budget to actually play there.