Matilda: a free human-like UCI chess engine.
Project description
Matilda
paper/website/discord
Long before brute-force search won, world champion Mikhail Botvinnik argued that a chess program should work the way a master does: don't examine every move — build a small candidate set the way a human would, and spend your calculation only there. He devoted his post-championship career (the PIONEER project; Computers, Chess and Long-Range Planning, 1970) to formalizing that selective, human-feature-focused search. Hardware went the other way, and Botvinnik's programme was shelved.
Matilda's architecture is that idea, made practical by learning the human
part instead of hand-coding it. A frozen human-move model (Maia-3) proposes
the up-to-16 moves a human would actually consider in the position — the
candidate set Botvinnik wanted — and the pluggable search controller
decides how to spend engine effort across only those moves. Stockfish and
Lc0 plug in today; the SearchController API (see
developer.md) exists precisely so anyone can implement their
own allocation policy — deeper on the moves that look most interesting for a
human, shallower elsewhere. The re-ranker then blends what the engine found
back into the human distribution. It should "just work" with any controller:
that composability, not any single engine, is the point.
Matilda speaks the standard UCI protocol, so it plugs into any chess GUI (CuteChess, Arena, BanksiaGUI, En Croissant) or engine runner that can talk to Stockfish. The default backend architecture is a frozen Maia-3 (23M) re-ranked by the paper's trained set-transformer, with optional engine features from any plugged-in search controller (Stockfish, Lc0, or your own). By default we use Stockfish, but we encourage developers to make the engine search build off of Maia's attention layers to focus on more human-like engine searches and not always do max depth searches.
Photo: Harry Pot / Anefo, Dutch National Archives — licensed CC BY-SA 3.0 NL.
What the re-ranker buys over raw Maia-3
Move-prediction improvement (NLL on held-out human games) of the shipped re-ranker over the frozen Maia-3 prior, by rating band — from the paper's results; 95% player-bootstrap CIs and the regeneration command for every number are in the paper repo's RESULTS.md:
| Elo band | vs Maia-3 |
|---|---|
| 1000–2500 | ~0% — no low-Elo collapse; plays as human as Maia-3 does |
| 2500–2800 | +0.5% |
| 2800–2900 | +4.3% |
| 2900–3000 | +12.0% |
| 3000–4000 | +23.0% |
The gains concentrate exactly where a pure human prior runs out of training examples: the top of the rating pyramid, where the engine features carry real signal. Below 2500 the re-ranker gets no engine features and leaves Maia-3's human play intact.
Install & run
pip install matilda-uci
matilda-uci --elo 1500
That's the whole install: the Maia-3 runtime comes along as the
maia3-runtime dependency (the paper's pinned CSSLab/maia3 revision,
republished on PyPI), and everything else is fetched on first run —
the re-ranker checkpoint (base_3k.pt, ~6 MB, sha256-verified) from this
repo's release assets into ~/.cache/matilda-uci, and Maia-3's 23M weights
(~88 MB) from HuggingFace. A git clone needs no downloads for the
checkpoints at all — they're in checkpoints/ via git-lfs (git lfs pull).
Matilda drives a search engine and expects one at startup: by default it
finds stockfish on your PATH (install
Stockfish; macOS brew install stockfish, Debian/Ubuntu apt install stockfish), --engine-cmd points at
any other UCI engine, and --no-engine opts out (raw human prior — much
weaker at high Elo). The per-move engine time follows the game's time
control: roughly 2s in bullet, 15s in blitz, 30s in rapid, 60s in classical,
always bounded by the remaining clock.
The UCI handshake itself is instant — models load on the first move request.
The legacy backend needs neither: matilda-uci --backend maia2 --maia-type rapid.
To use it from a chess GUI, point the "add engine" dialog at the
matilda-uci executable — walkthroughs for CuteChess, cutechess-cli, and
En Croissant are in docs/gui-demo.md.
CLI flags
| Flag | Default | Meaning |
|---|---|---|
--backend |
matilda |
matilda (Maia-3 + re-ranker) or maia2 (legacy) |
--elo |
1500 | Playing strength (the Elo the engine imitates) |
--opp-elo |
follows --elo |
The opponent Elo the model conditions on |
--temperature |
0.0 | 0 = always the most-human move; higher = sample |
--seed |
fresh each start | Sampling seed; pass a value to reproduce a run |
--device |
cpu |
matilda: cpu/mps/cuda; maia2: cpu/gpu |
--name |
Matilda |
Engine name reported to the GUI |
--log-level |
WARNING |
Logging goes to stderr (stdout is the UCI channel) |
Matilda-backend flags:
| Flag | Default | Meaning |
|---|---|---|
--checkpoint |
base_3k.pt (auto-fetched) |
The trained re-ranker weights: a path, or a released name |
--tc-base / --tc-inc |
180 / 0 | Time control fed to the model (blitz default) |
--no-auto-tc |
off | Don't latch the real TC from the first go clocks |
--style-checkpoint |
— | Style transformation weights (pairs with --style-vector) |
--style-vector |
— | A 32-d player embedding to imitate (demos/fit_style_vector.py) |
--engine-cmd |
auto |
Search controller; auto = stockfish from PATH, or e.g. lc0 --weights=... |
--no-engine |
off | Play from the raw human prior alone (no search engine) |
--engine-depth / --engine-nodes / --engine-movetime |
22 / 0 / 0 | Controller search budget; movetime takes the min with the TC-derived cap |
--threads / --cache-size |
0 / 4096 | torch threads; prediction-cache entries |
--maia3-model |
23m |
Maia-3 variant (non-23m warns: untrained-against) |
Legacy maia2-backend flag: --maia-type (rapid/blitz) — only valid with
--backend maia2.
UCI options (matilda backend)
Set from any GUI's engine-options dialog: UCI_LimitStrength, UCI_Elo
(1000–3200), OpponentElo, TimeControlBase/TimeControlInc/AutoLatchTC,
Temperature, Checkpoint, StyleCheckpoint/StyleVector,
EngineCmd/EngineDepth/EngineNodes/EngineMovetime, Device,
Threads, and CacheSize.
The model is rating-conditioned, so UCI_Elo maps directly onto playing style
and strength: the engine plays like a human of that rating, including
human-characteristic mistakes. Time control is a real model input — the same
position is played differently at 60+0 than at 900+10 — and is latched
automatically from the clock at the first go of each game. The same time
control also sets the search controller's per-move budget (bullet ~2s, blitz
15s, rapid 30s, classical 60s), bounded by the remaining clock and any
EngineMovetime / go movetime.
Architecture
UciEngine (protocol loop) ──drives──> MovePolicy (swap seam)
├── MatildaPolicy (default)
│ └── MatildaModel
│ ├── Maia3Wrapper (frozen 23M prior)
│ ├── TXTC re-ranker (base_3k.pt)
│ └── SearchController (optional:
│ Stockfish / Lc0 / custom)
└── MaiaPolicy (Maia-2, legacy)
MovePolicy is a small protocol and the deliberate extension point. The
SearchController protocol is the second seam: any engine that can score up
to 16 candidate moves plugs in, and without one the model degrades gracefully
to the pure human prior. go infinite runs on a worker thread and holds
bestmove until stop, per the UCI spec.
Real games on lichess: Matilda at full strength vs the AI levels
Played live on lichess.org against the real server-side AI (via the Board
API — demos/play_on_lichess.py): Matilda at Elo 3200 with the Stockfish
search controller, 5+2 clock, one game each color per level.
| Opponent | Games | Score |
|---|---|---|
| lichess level 6 | win as White, mate · win as Black, mate | 2 – 0 |
| lichess level 7 | win as White, mate · win as Black, mate | 2 – 0 |
| lichess level 8 | draw as White · draw as Black | 1 – 1 |
5/6 against the machine spectrum, all decided over the board (mates and a
193-ply stalemate grind) — checkmating levels 6–7 outright and holding
level 8, essentially full-strength Stockfish, to two draws. For local
engine-vs-engine testing without a lichess account, demos/play_vs_stockfish.py
reproduces the same opponents offline; sample PGNs in demos/games/.
Demos and docs
- developer.md — Python API, custom search controllers, style personalization, embedding in your own service.
- CONTRIBUTING.md — getting your search engine natively supported by this repo.
- docs/gui-demo.md — playing it from CuteChess & co.
- docs/profiling.md — inference throughput by game phase (~100 predictions/s on an Apple M3 Pro CPU), rliable bootstrap CIs.
- demos/games/ — the local Elo × level matrix: Matilda
{1500, 2000, 2800, 3200} vs lichess levels 6–8, two games per cell. The
scores form the staircase an Elo-conditioned engine should produce
(0–6 at 1500, 5–1 at 3200); regenerate with
demos/play_vs_stockfish.py. - demos/style_demo.py — measure how player-style embeddings condition the policy.
Development
git lfs install && git lfs pull # the vendored checkpoints are git-lfs objects
pip install -e ".[dev]"
python -m pytest
python -m ruff check .
PYTHONPATH=src python scripts/verify_checkpoint.py checkpoints # model parity
The test-suite injects fake policies/models/wrappers, so it runs without the
model runtimes or downloads. scripts/verify_checkpoint.py proves the ported
model matches the paper implementation bitwise (requires the checkpoints and,
for the parity checks, a clone of the paper repo).
License
GPL-3.0 — see LICENSE.
Citation
If you use Matilda in your research, please cite:
@article{carlson2026matilda,
title = {Matilda: Engine-Agnostic Search with Human Policy Guidance},
author = {Carlson, Jason},
year = {2026},
note = {Preprint},
}
To cite this UCI engine / its documentation specifically:
@software{matilda_uci_2026,
title = {matilda-uci: a human-like UCI chess engine},
author = {Carlson, Jason and Hartfield, Justin},
year = {2026},
url = {https://github.com/GarryChess/matilda-uci},
}
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file matilda_uci-0.2.0.tar.gz.
File metadata
- Download URL: matilda_uci-0.2.0.tar.gz
- Upload date:
- Size: 98.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4fa924377339a20d05d59b1be0536c0a55a4efe13449676257465318d6382f8b
|
|
| MD5 |
88ad9790acebaa65fb0cf01f99a2d38c
|
|
| BLAKE2b-256 |
d094290653d588780898b8d3337db87ee7cd6847489abcce9abe318585fd04be
|
Provenance
The following attestation bundles were made for matilda_uci-0.2.0.tar.gz:
Publisher:
release.yml on GarryChess/matilda-uci
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
matilda_uci-0.2.0.tar.gz -
Subject digest:
4fa924377339a20d05d59b1be0536c0a55a4efe13449676257465318d6382f8b - Sigstore transparency entry: 2218351626
- Sigstore integration time:
-
Permalink:
GarryChess/matilda-uci@b13972af97573b4b66ab2e8eb815e540b6ddbff1 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/GarryChess
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b13972af97573b4b66ab2e8eb815e540b6ddbff1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file matilda_uci-0.2.0-py3-none-any.whl.
File metadata
- Download URL: matilda_uci-0.2.0-py3-none-any.whl
- Upload date:
- Size: 76.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e082b4d61a3b15e7cb440feef460d4541fd915fa3899c9ae9df969f53769c09d
|
|
| MD5 |
8fb19dbd0b81399b016945dcd6db7940
|
|
| BLAKE2b-256 |
f32d53653d527ffb31a245666c4dd49c1377e6cb557e2bb1f6f2a51bfdc01554
|
Provenance
The following attestation bundles were made for matilda_uci-0.2.0-py3-none-any.whl:
Publisher:
release.yml on GarryChess/matilda-uci
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
matilda_uci-0.2.0-py3-none-any.whl -
Subject digest:
e082b4d61a3b15e7cb440feef460d4541fd915fa3899c9ae9df969f53769c09d - Sigstore transparency entry: 2218351756
- Sigstore integration time:
-
Permalink:
GarryChess/matilda-uci@b13972af97573b4b66ab2e8eb815e540b6ddbff1 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/GarryChess
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b13972af97573b4b66ab2e8eb815e540b6ddbff1 -
Trigger Event:
push
-
Statement type: