Breaking the plateau, and a trick that did not transfer
Fine-tuning each network from its parent got self-play moving again: +83 Elo over the old brain, +174 over the hand-tuned engine. MTD(f), the biggest search gain in a recent thesis on Azul, measured zero here.
The first self-play run stalled after one generation: three new networks in a row could not beat their parent. The guess then was that each generation threw away too much, because every new network started from random weights and had to relearn everything from 10,000 fresh games plus the previous batch.
Fine-tuning instead of starting over
The second run changed three things: each network starts from its parent's weights, the learning rate drops from 1e-3 to 3e-4 so it adjusts instead of rewriting, and each generation plays 20,000 games instead of 10,000. Everything else stayed the same, including the rule that a network takes over only if it beats its parent.
| Generation | vs its parent (800 games) | vs classic |
|---|---|---|
| 1 (start) | +148 ± 14 | |
| 5 | +53 ± 24 | +172 ± 27 |
| 6 | −4 ± 24, rejected | +147 ± 26 |
| 7 | +13 ± 24 against generation 5 | +174 ± 14 (3,000 games) |
The 800-game checks are noisy, so the two kept networks played generation 1 directly over 3,000 games: generation 5 won by +65 ± 13 and generation 7 by +83 ± 13. Generation 7 is now the brain on the analysis board. Against the hand-tuned engine it takes 73% of the points, up from 70%.
What a thesis on Azul suggested
Mateusz Rzepecki's 2025 master's thesis at the University of Wrocław, Implementing superhuman AI for Azul board game with a variation of NNUE, built the same kind of engine in C++ and took it to first place on Board Game Arena. Its self-play loop also flattened out after about a dozen rounds, which matches what happened here.
Its single biggest search gain was MTD(f): instead of searching the root with a full window, run a series of zero-width searches that close in on the previous iteration's score. In their tests that took 56% of the points against their default. It is a small change, so I tried it: the root became a loop of null-window searches, with a test that it still solves late-round positions exactly like a plain alpha-beta reference.
| MTD(f) against this engine's root search | Games | Elo |
|---|---|---|
| 40 ms per move | 3,000 | +9 ± 12 |
| 200 ms per move | 3,000 | +3 ± 12 |
Nothing. The likely reason is what each one was compared against. Their baseline started every root search with a full window; this engine already uses principal variation search, which tests every move after the first with a zero-width window and re-searches only the ones that beat the best. Most of what MTD(f) saves, PVS was already saving. The change was removed: code that does not win games is not worth keeping.
Next
Keep the fine-tuning loop running while it still finds gains, and look at the deals: early in the game the network's error is mostly the random tiles still to come, so a search that samples the next round's deal more cleverly may matter more than a better evaluation.