VeryChess 0.9.0
Overview
This release is about search quality and cost. Two changes add strength outright — aspiration windows (+27.9 Elo) and a transposition table in quiescence search (+9.5 Elo) — while three more make the engine do substantially less work for the same playing strength: staged move generation halves the moves generated per node, a negative extension cuts the tree by a further 16%, and a large internal cleanup drops per-thread memory by 92%. The engine also gains exact endgame knowledge (a KPK bitbase and a KBNvK mating technique) that needs no external files.
Strength
In the roundrobin tournament with older versions VeryChess 0.9.0 shows significant increasing strength:
| name | games | wins | draws | losses | score | elo | |
|---|---|---|---|---|---|---|---|
| 1 | VeryChess 0.9.0 | 200 | 121 | 74 | 5 | 158.0 | 325 |
| 2 | VeryChess 0.8.0 | 200 | 74 | 93 | 33 | 120.5 | 217 |
| 3 | VeryChess 0.7.0 | 200 | 53 | 85 | 62 | 95.5 | 153 |
| 4 | VeryChess 0.6.0 | 200 | 39 | 86 | 75 | 82.0 | 115 |
| 5 | VeryChess 0.5.0 | 200 | 14 | 60 | 126 | 44.0 | 0 |
What's new
Aspiration windows
Until now every root iteration was searched with the full (-inf, +inf) window — the engine had never had this mechanism at all. Iteration d is now searched in a ±25 cp window centred on the score of the last completed iteration, widening ~1.5× on each fail-low or fail-high and falling back to the full window after three failures or as soon as the score reaches mate range. On 150 opening positions the engine reaches a fixed depth with ~12% fewer nodes.
Transposition table in quiescence search
Quiescence search is 57% of all nodes and never consulted the transposition table, so millions of positions were re-derived from scratch, stand-pat evaluation included. It now probes and stores in the shared table, with entries deliberately marked shallower than any main-search entry so they cannot masquerade as a deep result, and stored as bounds only (a quiescence value depends on the window it was searched with). Fixed along the way: a quiescence store could overwrite a deep main-search entry for the same position, because the table's "same key → refresh" path ran before its depth guard.
Staged move generation
The engine used to generate and score every legal move at a node before searching any of them. Measurement showed what that cost: 37 moves generated per node, of which only 3.5 were ever searched, and two thirds of beta cutoffs happened before a single quiet move was tried. Moves are now produced lazily in stages — transposition-table move, good captures, promotions, killers, quiet moves, losing captures — so 54% of nodes never generate quiet moves at all and the moves generated per node fall from 36.8 to 18.0. The engine reaches a fixed depth 10% faster.
Singular extensions: the second verdict
When the engine tests whether the transposition-table move is the only good move, that test produces two answers, and only one of them was ever used. If the test shows the move is not singular — there is at least one other move just as good — the engine now searches it one ply shallower in nodes where a cutoff is expected anyway. That removes 16% of all nodes and reaches a fixed depth 17.6% faster.
Embedded endgame knowledge
Exact answers where they can be had without external files: an exhaustively generated KPK bitbase (24 KB, ~11 ms at startup, verified against an independent reference over all 165,676 legal positions), a KBNvK mating technique that drives the weak king to a corner of the bishop's colour — 0.8.0 could not convert this ending at all, while 0.9.0 mates in 71 and 59 plies on the two test positions — and extended endgame scaling keyed on material counts rather than tuned thresholds. This package is strength-neutral by design: it trades no Elo, it removes wrong answers.
Memory
Per-thread memory drops from 771.9 KB to 56.9 KB. Nine search mechanisms that had been implemented, measured as neutral-to-negative and left disabled were removed outright, together with the tables they reserved — 715 KB per thread that was allocated and zeroed on every search. At `Threads=256` the engine's resident memory falls from 335 MB to 155 MB.
Separately, every UCI session used to allocate the hash table twice at startup, peaking at twice the configured `Hash` (131 MB instead of 67 MB with the default setting, and over 1 GB for a GUI configured with `Hash=1024`). Fixed.
Multi-threading: measured, not changed
The long-deferred scaling report was finally done. On a 4+4-core Apple M2, node throughput scales 3.83× at four threads but time-to-depth only 1.71×, and eight threads are no better than four — partly because a depth-limited search finishes when the main thread finishes, so an operating system that parks that thread on an efficiency core slows the whole search regardless of how many helpers are running. Practical advice: do not set `Threads` higher than the number of performance cores.
Under the hood
`Board::isPseudoLegal` was added so the transposition-table move can be played before any move generation happens; it is verified exhaustively against the generator (all 65,536 move encodings on 400 positions), and that test immediately found two unused move-encoding flags being accepted as captures.