A Stockfish 19 fork for finding forced mates, version 0.1.0; see CHANGELOG.md.
It is distributed as a patch
against the Stockfish 19 release: 6 files and 27,470 bytes of diff, which is
smaller than a binary and shows exactly what was changed.
The base is the Stockfish 19 release: tag sf_19, commit
edb0d9db6731067ec50ce619ff372b463bc4dd5d, whose src/ directory has the git
tree hash 15c9967c8add24a07ef4a34522cb6fb5c71ca580. The patch paths are
relative to src/:
git clone --branch sf_19 --depth 1 https://github.com/official-stockfish/Stockfish.git
cd Stockfish
git rev-parse HEAD:src
cd src
patch -p1 < /path/to/matehunter.patch
make -j build ARCH=x86-64-avx2
git rev-parse HEAD:src must print 15c9967c8add24a07ef4a34522cb6fb5c71ca580.
make validates the NNUE network, and fetches it if it is missing; the patch
does not change it.
Check the build before trusting any result. With MateEval off, MateHunter
searches exactly as stock Stockfish 19 does, so
printf "setoption name MateEval value false\nbench\nquit\n" | ./stockfish
must report Nodes searched of 2497913, the bench recorded in the
Stockfish 19 release commit. With its defaults, bench reports 5314178.
| option | default | effect |
|---|---|---|
MateEval |
true |
replace NNUE's static evaluation with the mate evaluation |
MateEvalNull |
true |
with MateEval, fix the static evaluation at 0, so the only evaluation the search sees is the correction history Stockfish learns during the search. With it off, MateEval uses a king-danger evaluator that ignores material |
The defaults, MateEval=true and MateEvalNull=true with every other option
at its default, are the recommended profile. Scripts should still set every
option they rely on explicitly.
These options exist so the results in FINDINGS.md can be reproduced. None is
part of the recommended profile, and at their defaults they change nothing.
| option | effect |
|---|---|
MateEscapeW, MateCheckW, MateExposeW, MateDefendW, MateAttackW, MateNearW, MateScale |
the king-danger evaluator's weights; they matter only with MateEvalNull=false |
MateEvalMain, MateEvalQS |
route the mate evaluation to one consumer of the static evaluation only: the main search, or quiescence. MateEval overrides both |
MateMode |
switch off razoring, futility and null-move pruning, and relieve one ply of reduction for checks |
MateNoRazor, MateNoFutility, MateNoNull |
the three prunings MateMode switches off, separately |
MateEvalOff |
bitmask switching off one consumer of the static evaluation per bit, listed in engine.cpp and FINDINGS.md; 0 in normal use |
The measurements below say neither engine was tuned on ChestUCI. For MateHunter that is wrong. Its options and recommended profile were chosen on matetrack, and 6,526 of ChestUCI's 6,545 positions are also in matetrack, including 2,460 of the 2,477 at mate in 10 or more. The ChestUCI results measure MateHunter on the positions it was developed on, and do not show that it finds more mates than stock Stockfish in general.
Out of sample the advantage is far smaller, it depends on the clock, and
Huntsman 1 finds more mates than either profile at every clock measured. The
positions below are ones no engine could have been tuned on: generated by walking
back from random checkmates, the shortest mate of each proved by MateProver
0.2.0, every position in matetrack or ChestUCI excluded, and each measurement
planned before it ran. The positions, the plans and every run log are in
MateBench: corpora/, studies/ and rounds/. One thread, paired, every mate claim verified by
MateProver, none refuted.
At equal time. The recommended profile skips NNUE and searches about 2.7 times as many nodes per second as stock (4.0M against 1.5M on the same positions), so time, not nodes, is the fair budget. Verified mates, one thread a search:
| mate in | positions | clock | Stockfish 19 | recommended profile | full king-danger evaluator | Huntsman 1 | recommended vs Stockfish |
|---|---|---|---|---|---|---|---|
| 9 to 11 | 993 | 0.5 s | 293 | 215 | 285 | 630 | +86/-164, p < 0.0001 |
| 9 to 11 | 993 | 1 s | 411 | 374 | 411 | 695 | +100/-137, p = 0.019 |
| 9 to 11 | 993 | 2 s | 473 | 524 | 484 | 741 | +143/-92, p = 0.0011 |
| 9 to 11 | 993 | 5 s | 585 | 650 | 620 | 776 | +125/-60, p < 0.0001 |
| 9 to 11 | 993 | 10 s | 645 | 691 | 670 | 795 | +99/-53, p = 0.0002 |
| 10 and 11 | 304 | 5 s | 161 | 180 | 181 | 248 | +39/-20, p = 0.018 |
| 6 to 8 | 1,171 | 0.5 s | 719 | 699 | 749 | 908 | +121/-141, p = 0.24 |
| 6 to 8 | 1,171 | 1 s | 844 | 844 | 878 | 972 | +103/-103, p = 1.0 |
| 6 to 8 | 1,171 | 2 s | 914 | 929 | 941 | 1,011 | +87/-72, p = 0.27 |
| 6 to 8 | 1,171 | 5 s | 984 | 1,003 | 1,014 | 1,049 | +59/-40, p = 0.07 |
- Against stock, the recommended profile is behind below about 2 seconds a position and ahead from 2 seconds, on mate in 9 to 11. At 5 seconds it finds about 12% more mates than stock at mate in 10 and 11 (repeated at half the machine load: +42/-20, p = 0.007); on ChestUCI, under the same clock at mate in 10 to 13, it found 74% more. On mate in 6 to 8 it is never significantly ahead.
- Which profile to use. The full king-danger evaluator (
MateEvalNull=false) is better at 0.5 and 1 second (+154/-84 and +145/-108 over the recommended profile on mate in 9 to 11), and it is the only profile ahead of stock on mate in 6 to 8 (at 1, 2 and 5 seconds). The recommended profile is better at 2 and 5 seconds on mate in 9 to 11 (+137/-97 and +93/-63), and the two are level at 10 seconds. - Huntsman 1 is ahead of both profiles and of stock at every clock and depth, at p < 0.0001: +74/-6 against the recommended profile at 5 seconds on mate in 10 and 11, and +440/-25 at 0.5 seconds on mate in 9 to 11. It was ahead at equal nodes too (below), so its lead is not speed. The in-sample ChestUCI comparison below, where MateHunter led Huntsman, does not carry over.
At equal nodes the recommended profile has about a third of stock's time, and it is never ahead. On 4,934 generated mates in 1 to 9, split 20/80 by a salt committed before any engine ran:
| positions | budget | Stockfish 19 | recommended profile | Huntsman 1 | MateHunter vs Stockfish | MateHunter vs Huntsman |
|---|---|---|---|---|---|---|
| held out, 972 | 10M nodes | 963 | 963 | 971 | +5/-5, p = 1.0 | +0/-8, p = 0.008 |
| held out, 972 | 100k nodes | 872 | 815 | 914 | +14/-71, p < 0.0001 | +6/-105, p < 0.0001 |
| held out, 972 | 10k nodes | 767 | 642 | 801 | +3/-128, p < 0.0001 | +2/-161, p < 0.0001 |
| development, 3,962 | 10M nodes | 3,938 | 3,935 | 3,956 | +12/-15, p = 0.70 | +3/-24, p < 0.0001 |
and on 3,421 more, one mate length at a time, at 10M nodes:
| mate in | positions | Stockfish 19 | recommended profile | Huntsman 1 | MateHunter vs Stockfish |
|---|---|---|---|---|---|
| 1 to 5 | 1,238 | 1,216 | 1,216 | 1,238 | within 1 or 2 at each |
| 6 | 407 | 390 | 373 | 402 | +3/-20, p = 0.0005 |
| 7 | 486 | 418 | 406 | 438 | +13/-25, p = 0.07 |
| 8 | 297 | 210 | 200 | 233 | +13/-23, p = 0.13 |
| 9 | 689 | 455 | 436 | 537 | +50/-69, p = 0.10 |
| 10 and 11 | 304 | 181 | 169 | 248 | +25/-37, p = 0.16 |
The node-budget rows up to mate in 8 include 39 positions no game can reach, which Stockfish and MateHunter refuse and Huntsman accepts; without them MateHunter against Stockfish is unchanged, and the clock tables above leave them out. A clock result belongs to the machine it ran on (a 16-core Ryzen 9 7945HX, 8 searches at a time), so the 2-second crossover will move on other hardware. Mate in 12 and deeper, and composed problems not in matetrack, are untested. The 19 ChestUCI positions not in matetrack are too few to decide anything: at 10M nodes MateHunter solved 5, Stockfish 19 4 and Huntsman 1 7.
Everything else in this section and in FINDINGS.md stands as a description of
MateHunter on ChestUCI, including the mechanism study. Its margins there are not
evidence of margins that size elsewhere.
Against stock Stockfish 19 on ChestUCI, one thread, paired, with every mate option set explicitly:
| positions | budget | Stockfish 19 | full king-danger evaluator | recommended profile |
|---|---|---|---|---|
| d14+, 953 | 10M nodes | 343 | 586 | 590 |
| d10-13, 1,524 | 10M nodes | 757 | 1,176 | 1,225 |
| d14+, 953 | 5 s | 324 | 592 | 606 |
| d10-13, 1,524 | 5 s | 715 | 1,195 | 1,244 |
With every mate option off the fork matched stock exactly: 343 = 343, zero discordant.
Against Huntsman 1, a Stockfish fork with a MateSearch option, on the same
positions at 10M nodes: 590 against 542 at d14+ and 1,225 against 1,101 at
d10-13. Every mate only one of the two engines reported was re-proved with
MateProver, and none was refuted. On proved claims MateHunter is +220/−97
(p < 0.0001): ahead from mate in 10 to 17, level from mate in 18.
Why, replicated at both depth ranges. The gain comes from replacing NNUE's
evaluation with the correction history Stockfish learns inside the current
search. With MateEvalNull the static evaluation is 0 plus that correction
term. Remove the term from the static evaluation (MateEvalOff=2048) and the
profile falls below stock: 169 at d14+, 732 at d10-13. A genuinely flat
evaluation is worse than NNUE. The king-danger terms add nothing over the
recommended profile, and switching off any single consumer of the evaluation
under NNUE recovers none of the gain. The measurements, the mechanism study and
how to reproduce them are in FINDINGS.md.
This patch is a port of the Stockfish 18 fork to Stockfish 19. The king-danger evaluator was carried over unchanged from the Stockfish 18 fork, not rewritten.
The measurements in FINDINGS.md were made with a development build that also
carried experimental options since removed from the patch: move-ordering,
move-restriction and learned-evaluation experiments, none of them part of any
profile measured here. Removing them does not change the search. Under 13
option settings covering every option that remains, bench reports the same
node count for the development build and for this patch (depth 9 for the two
settings that switch pruning off under NNUE, the default depth otherwise).
| Stockfish base | tag sf_19, commit edb0d9db6731067ec50ce619ff372b463bc4dd5d |
| patch sha256 | d14cb5c22a513c8eb98c2323b3c0c7b35f92a670d86e8a6036c840c7ef8a821a |
| stock Stockfish 19 bench | 2497913 nodes |
MateHunter, MateEval=false, bench |
2497913 nodes |
| MateHunter, defaults, bench | 5314178 nodes |
GPL-3, inherited from Stockfish; see COPYING.txt. This patch is a derivative
work of Stockfish and carries the same terms, which is why it lives apart from
MateProver and MateBench, both MIT.