Case study · Engine
Bongaclang.
From a Python script to a Rust engine with a neural network I trained. It now beats Stockfish 18 set to 3090, and still opens with the Bongcloud.
- Year
- 2024–26
- Role
- Solo: engine, network training, testing and deployment.
- Stack
- Rust · NNUE · CUDA · Python · Oracle Cloud
- Lichess blitz, live
- 2,444
- Lichess blitz, live
- Stockfish 18 level it beats
- 3090
- Stockfish 18 level it beats
- free Oracle Cloud VM
- 1 core
- free Oracle Cloud VM
Why I built it
In 2024 I wanted to know how a computer picks a chess move, so I wrote one. It was weak. It also opened 1. e4 2. Ke2, the Bongcloud, because that's funny.
In July 2026 I picked it up again to see how far I could push it.
The goal
The strongest engine I can run on a free Oracle Cloud VM: one shared CPU core and under 1 GB of memory. It plays on Lichess around the clock, opens with the Bongcloud every game, and has to win anyway.
One rule since July: nothing ships unless it beats the version before it in a match.
How it got stronger
Hollow dots weren't match-tested. The Rust rewrite won all 200 games, so its bar runs off the top.
Each change played the version before it until the result was clear. The bar is how much stronger it came out, in Elo. The line is the margin of error. Self-play numbers run high and don't simply add up, so the brass checks measure against other engines.
Prologue · Sep to Nov 2024
The original
A weekend project in Python. No tests, just vibes.
Sep 2024
First version
Python and python-chess. Over a dozen versions it went from a random mover to alpha-beta search with tables that score each piece by its square.
UntestedOct 2024
Opening books and a clock
Book moves for the opening, and a time budget that thinks longer in messy positions.
UntestedNov 2024
The Bongcloud
1. e4, then 2. Ke2. Walking the king out on move two is terrible chess. The bot does it every game.
Costs Elo. Worth it.- Then it sat untouched for 20 months.
Bong I · 1 to 3 Jul 2026
Sloth
I came back with one rule: a change only ships if it beats the previous version in a match.
2 Jul
A referee
A harness that plays the new version against the old one over many fast games, and stops as soon as the result is clear.
Every bar below comes from this2 Jul
Three real bugs
It counted every pawn as a passed pawn, stored mate scores wrong, and misread the ends of tactical lines.
+168Elo
40 games · rough read
2 Jul
A faster evaluation
Scoring rewritten as tight bitboard loops, with caching for what repeats. 2.25 times more positions per second.
+162Elo
±41 · 250 games
2 Jul
PyPy on the server
A faster Python runtime on the VM. Same moves, found 1.76 times faster.
1.76× faster2 Jul
Late move reductions
Moves sorted near the end are rarely best, so search them less deeply first.
+77Elo
±30 · 402 games
2 Jul
Pruning near the leaves
Skip moves that can't matter in the last few plies.
The first version lost 22 Elo. Safer margins won.
+61Elo
±27 · 578 games
3 Jul
Time management
Soft and hard time limits, and an instant reply when only one move is legal.
+26Elo
±16 · 1,326 games
3 Jul
A smarter evaluation
King safety, the bishop pair, rooks on open files, and pushing a lone king to the edge to mate it.
+108Elo
±36 · 382 games
3 Jul
Tablebases and a Bongcloud book
Perfect play with five pieces or fewer, and instant book moves after the Bongcloud.
Not match-testedOutside check · 3 Jul
Around 1,800 in Lichess bullet. Against Stockfish 18 it only wins when Stockfish looks one move deep.
Stockfish at fixed depth 1 · my desktop · 10s + 0.1s games
Bong II · 4 to 5 Jul 2026
Ferric
Python had become the bottleneck, so I ported the whole engine to Rust.
4 Jul
Rewrite in Rust
Same search and the same evaluation, checked identical on 607 of 607 test positions. 185 times more positions per second.
Won 200 of 200 games against the Python version.
≥ +727Elo
200 games
4 Jul
Tuned eval, sharper search
An evaluation that blends middlegame and endgame, tuned on 91,000 positions. Plus exchange checks, check extensions and razoring.
+128Elo
±38 · 322 games
5 Jul
Troll mode
When it's safely winning, it promotes pawns to knights. As many as it can. It never risks the win to do it.
Style onlyOutside check · 5 Jul
Beats Stockfish 18 searching seven moves deep. The Python version managed one.
Stockfish at fixed depth 7 · 100 games · my desktop
Bong III · 7 to 8 Jul 2026
Cortex
Next I swapped the hand-written evaluation for a neural network I trained myself.
7 Jul
First network
Trained on my own GPU from public Leela Chess Zero games, and updated move by move instead of from scratch. Still lost to the hand-written eval.
−33Elo
±39 · 300 games
8 Jul
More data, faster maths
Six months of training data, a wider network, and SIMD maths in the output layer. Now it wins clearly.
+151Elo
±30 · 500 games
Outside check · 9 Jul
About 2,990 on the CCRL scale, the standard rating list for engines.
Measured against a ladder of Stash engine versions · 60s + 0.6s
Bong IV · 9 to 24 Jul 2026
Reflex
The search was still 2015-era. I worked through what modern engines do, one tested piece at a time.
9 Jul
Continuation history
Remember which replies worked after which moves, and try those first.
+88Elo
±32 · 466 games
10 Jul
Exchange pruning
Skip moves that simply lose material in a trade sequence.
+28Elo
±17
10 Jul
King-aware network
The network reads the board differently depending on where each king stands.
Only about +10 against outside engines. Self-play flatters.
+36Elo
±18 · 1,012 games
11 Jul
Move ordering, all at once
Capture history, countermoves and capped history scores, shipped as one set. Tried one by one, they had all lost.
+60Elo
±26 · 454 games
13 Jul
Correction history
Learn how wrong the evaluation tends to be in similar pawn structures, and correct for it.
+12Elo
±9 · 3,006 games
13 Jul
Output buckets
Eight network heads, picked by how much material is left on the board.
+12Elo
±9 · 3,138 games
14 Jul
Modern null move
Only try the free-move test when the position already looks good.
+37Elo
±20 · 644 games
14 Jul
Improving flag
Prune and reduce less when the position is getting better.
+34Elo
±19 · 714 games
14 Jul
Smarter clock
Stop early when the best move stays the same from one iteration to the next.
+28Elo
±17 · 876 games
14 Jul
Internal iterative reductions
Search a little shallower where there's no remembered best move.
Lost 12 when I tried it five days earlier.
+15Elo
±11 · 1,920 games
14 Jul
Faster move generation
No memory allocation in the hot loop. 22% more positions per second.
+16Elo
±12
Outside check · 14 Jul
About 3,100 on the CCRL scale.
100 games per reference engine · 20s + 0.2s
17 Jul
Memory in the capture search
Reuse remembered results in the capture-only search at the end of each line.
+13Elo
±13 · 1,312 games
17 Jul
Deeper reverse futility
Cut hopeless branches up to eight plies from the end, not two.
+29Elo
±18 · 794 games
23 Jul
The Ouroboros network
100 million of its own positions, rescored by Stockfish, then rescaled to the old network's confidence.
With raw Stockfish scores it lost 46.
+54Elo
±25 · 420 games
23 Jul
Reductions by node type
Decide how much to reduce from history scores and the kind of node together.
+11Elo
±9 · 2,428 games
Outside check · 22 Aug
About 3,240 on the CCRL scale. Stronger reference engines showed the old ruler read 90 too low.
800 games per reference engine · 20s + 0.2s
Bong IV · Sep to Oct 2026
Fast loop
Tests used to take a day each. I rebuilt the workflow so a change gets its verdict the same day.
30 Sep
Pondering
Think on the opponent's time, plus a batch of search tweaks.
+22Elo
980 games
8 Oct
Pruning batch
Much tighter reverse futility, an adaptive null move, and reductions at the root.
+61Elo
9 Oct
Tuned constants
60,000 games of automatic tuning on 18 search constants, plus two search changes.
+113Elo
±27
Outside check · 9 Oct
Beats Stockfish 18 with its strength set to 3090. Loses at 3190.
940 games at 3090 · 10s + 0.1s · my desktop
10 Oct
Second tuning round
More tuning and a lock-free shared table. Two threads helped on my PC but slowed the one-core VM, so it stays single-threaded.
+10Elo
11 Oct
Faster network maths
Leaner maths inside the network, so the same search runs 47.7% faster on my PC. Plus a third tuning round.
The speedup alone was worth 80 Elo.
+80Elo
Outside check · 11 Oct
Level with Stockfish 18 at 3190, the top of its strength scale. Going higher needs a different ruler.
2000 games at 3190, scored 50.5% · 10s + 0.1s
12 Oct
A capture shortcut
Stop searching a position once a strong capture already proves how it ends. Plus a fix so one kind of check no longer skews the eval corrections.
+6Elo
12 Oct
A speed audit
Same moves, found about 1.45 times faster. Mostly smarter memory use and cheaper move generation, plus a build tuned by profiling the engine.
Not one move changed. The speed alone was worth 78 Elo.
+78Elo
The honest part
What didn't work
Most ideas lose. These lost the most. A few came back later and won, once the rest of the engine had caught up.
Internal iterative reductions, in Python
Won 15 in Rust 12 days later, once the rest of the search could use it.
−72Elo
136 games · rough read
Tighter aspiration windows
Re-searches ate the clock in sharp positions.
−59Elo
±28 · 396 games
Capture history, on its own
Worked two days later as part of the move-ordering set.
−54Elo
±27 · 644 games
Raw Stockfish labels
Stockfish's confidence didn't fit my engine. Rescaling fixed it.
−46Elo
±25 · 392 games
A second round of training data
Most likely too little variety in the games. I kept the old network.
−40Elo
±24 · 422 games
A wider network
Flat: +5 on one opening set, −4 on the other. The data couldn't fill it.
−4Elo
±11
Where it stands
It plays rated games on Lichess from that one free core, every day. The browser version on this site is an older TypeScript engine. Fun to play, and much weaker.