All work

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
Illustration for Bongaclangabcdef♙♚
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

Every change, in orderElo vs the version beforeOutside check

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.

  1. 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.

    Untested
  2. Oct 2024

    Opening books and a clock

    Book moves for the opening, and a time budget that thinks longer in messy positions.

    Untested
  3. Nov 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.
  4. 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.

  1. 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 this
  2. 2 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

  3. 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

  4. 2 Jul

    PyPy on the server

    A faster Python runtime on the VM. Same moves, found 1.76 times faster.

    1.76× faster
  5. 2 Jul

    Late move reductions

    Moves sorted near the end are rarely best, so search them less deeply first.

    +77Elo

    ±30 · 402 games

  6. 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

  7. 3 Jul

    Time management

    Soft and hard time limits, and an instant reply when only one move is legal.

    +26Elo

    ±16 · 1,326 games

  8. 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

  9. 3 Jul

    Tablebases and a Bongcloud book

    Perfect play with five pieces or fewer, and instant book moves after the Bongcloud.

    Not match-tested
  10. Outside 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.

  1. 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

  2. 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

  3. 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 only
  4. Outside 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.

  1. 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

  2. 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

  3. 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.

  1. 9 Jul

    Continuation history

    Remember which replies worked after which moves, and try those first.

    +88Elo

    ±32 · 466 games

  2. 10 Jul

    Exchange pruning

    Skip moves that simply lose material in a trade sequence.

    +28Elo

    ±17

  3. 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

  4. 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

  5. 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

  6. 13 Jul

    Output buckets

    Eight network heads, picked by how much material is left on the board.

    +12Elo

    ±9 · 3,138 games

  7. 14 Jul

    Modern null move

    Only try the free-move test when the position already looks good.

    +37Elo

    ±20 · 644 games

  8. 14 Jul

    Improving flag

    Prune and reduce less when the position is getting better.

    +34Elo

    ±19 · 714 games

  9. 14 Jul

    Smarter clock

    Stop early when the best move stays the same from one iteration to the next.

    +28Elo

    ±17 · 876 games

  10. 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

  11. 14 Jul

    Faster move generation

    No memory allocation in the hot loop. 22% more positions per second.

    +16Elo

    ±12

  12. Outside check · 14 Jul

    About 3,100 on the CCRL scale.

    100 games per reference engine · 20s + 0.2s

  13. 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

  14. 17 Jul

    Deeper reverse futility

    Cut hopeless branches up to eight plies from the end, not two.

    +29Elo

    ±18 · 794 games

  15. 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

  16. 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

  17. 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.

  1. 30 Sep

    Pondering

    Think on the opponent's time, plus a batch of search tweaks.

    +22Elo

    980 games

  2. 8 Oct

    Pruning batch

    Much tighter reverse futility, an adaptive null move, and reductions at the root.

    +61Elo

  3. 9 Oct

    Tuned constants

    60,000 games of automatic tuning on 18 search constants, plus two search changes.

    +113Elo

    ±27

  4. 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

  5. 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

  6. 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

  7. 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

  8. 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

  9. 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.