A win-probability curve calibrated to humans, and a game review tool built on it.
Every mainstream engine answers what is the outcome of this position with perfect play? That is the wrong question for a human. A 1200-rated player with 45 seconds on the clock is not facing perfect play — they are facing another 1200 with 45 seconds.
swindlefish re-reads your games with an evaluation conditioned on your rating and both clocks. Paste a Lichess or chess.com username and it replays each game position by position, running Stockfish in a Web Worker. It reports where the game was actually decided, how much the clock cost you, and how narrow your position was — the difference between a win one move holds and a win fifteen moves hold.
It runs entirely in your browser. There is no backend and nothing is uploaded.
cd app && npm install && npm run devThe curve is fitted on human games and validated against the two standard alternatives. Held-out log-loss, split by game so no position leaks between train and test:
| log-loss | |
|---|---|
| swindlefish | 0.575 |
| Lichess formula | 0.738 |
| Stockfish WDL | 0.801 |
It was then re-validated off-corpus on 4,000,032 positions from a month used nowhere in fitting, where calibration error came in at 1.86pp against a pre-registered 3pp bar — it improved off-corpus rather than degrading.
Every threshold in this project was written down before the measurement it judges, and reported whether it passed or failed — including the gates that failed.
This is not a move-selection engine. swindlefish set out to answer which move gives a human the best practical chance?, expecting it to differ from Stockfish's choice. It does not. The pre-registered gate required +15 percentage points of top-three uplift; it was run twice and failed twice, most recently at +0.3pp. Eight candidate explanations were each tested and rejected, and the objective was closed — not by passing a fair test, but by showing the unconfounded region does not exist.
Two-move traps are real: 42.2% of losing positions contain one, and 85.4% sit outside Stockfish's top five. The narrower finding is that a human opponent model carries essentially no information about bait attractiveness that the engine's own ranking does not already carry. The traps exist; the modelling advantage does not.
What survived is the curve: it answers is this position worth fighting in, not which move should I play.
The whole arc — the hypothesis, the two failed gates, the eight explanations, and
the instrument defects caught along the way — is written up in
docs/research-closure.md.
- The low-rating scramble corner is mis-levelled. At ≤1000 rated with under 10 seconds, the fitted curve runs 0.445→0.588 against an empirical 0.234→0.375. That corner is 0.24% of the corpus and the fit trades it for global accuracy. Monotonicity is fixed there; calibration is not.
- Coefficients are not individually interpretable. The clock terms correlate at ~0.985, and one flipped sign between v1 and v2 while predictions improved throughout. Judge the clock effect from plotted curves, not coefficients.
- Accessibility is unverified. No screen-reader or keyboard-navigation audit has been done.
- chess.com review takes minutes. Their archives carry no server-side analysis, so every position must be searched locally.
The app needs none of this — the coefficients ship as source, so it builds from a fresh clone.
uv sync --extra dev # pytest and ruff included
uv run python research/scripts/fetch_maia.py # 11MB of weights, not committed
# Stockfish is installed separately (brew install stockfish)data/ and engines/ are never committed and are regenerable. Re-running the
fit additionally needs the Lichess dumps regenerated first.
Layout: app/ the review tool · research/ the Python fit, scripts and tests.
docs/ holds the architecture, the ADRs, the research closure write-up and
the original design — see docs/README.md for what each is for.
Post-game analysis is a study tool and unambiguously fine. A real-time version running alongside a live game is engine assistance and is cheating. This is deliberately a post-game tool, and that boundary is a design decision rather than a missing feature.
GPL-3. The app ships Stockfish compiled to WebAssembly, which is GPL-3, and the project builds on that ecosystem.