Home
All projects

Bulletfish

Optimizing for the game clock, not just the board.

Status
ResearchExploration, spikes and early groundwork.
Started
Role
Research Design, Experiment Design, Architecture, Development Orchestration
Built with
  • C++
  • Stockfish 18
  • UCI
  • NNUE
  • Cute Chess
  • Python
  • PowerShell
  • PGN
  • EPD

Overview

Bulletfish is a research project about building a chess engine specifically for true sudden-death 1+0.

It started with a question: if every millisecond matters, is the objectively strongest chess move always the move most likely to win the game?

Rather than build a chess engine from scratch, I started with Stockfish 18 and focused on the pieces of the objective that conventional engine strength does not explicitly optimize: both clocks, expected opponent think time, speculative computation while the opponent is moving, premove opportunities, and eventually the possibility of sacrificing board evaluation for practical clock pressure.

Before modifying Stockfish, I built the laboratory.

Clock as Part of the Objective

Traditional engines primarily use the clock to decide how long they may search.

In 1+0, I think the clock can become part of what the engine is optimizing.

A slightly worse board move could still increase real win probability if it forces the opponent to spend time, breaks a premove, or creates an unusually expensive decision. The opponent’s thinking time may also become additional compute time: predict their likely reply, calculate the response before they move, and eventually premove when confidence becomes high enough.

One early heuristic was to prefer moves that reduce the opponent’s legal replies. I rejected it quickly. One obvious move may actually save the opponent time.

The better concept became expected opponent clock consumption, not raw branching factor.

Building the Lab First

The project uses an official pinned Stockfish 18 baseline and an equivalent control binary built from the same source.

I created a Cute Chess 1+0 arena and verified timing at the UCI protocol level rather than assuming the tournament configuration meant what I thought it meant. Captured engine traffic confirmed a 60-second starting clock with zero increment.

That instrumentation caught problems immediately. Cute Chess treats pondering differently from an ordinary UCI option, so the first setup failed until I verified there were zero go ponder calls.

A later PGN parser quietly dropped the final move time because the termination reason shared the same comment. Comparing reconstructed timing against raw UCI traffic exposed the bug.

A Useful Failed Benchmark

The first pristine control tournament finished with:

  • 0 wins;
  • 0 losses;
  • 25 draws;
  • 0 crashes;
  • 0 time forfeits.

That was useful rather than disappointing. It showed the baseline was neutral, but starting every game from the initial chess position produced too little signal to evaluate future experiments.

I therefore adopted the official Stockfish opening-book ecosystem and extracted a large UHO Lichess repertoire for paired, color-swapped testing rather than inventing my own opening set.

Current State

The Bulletfish research environment works locally. The baseline is reproducible, true 60+0 timing is verified, and tournament output is preserved in machine-readable formats alongside PGN and logs.

No actual Bulletfish move-selection behavior has been implemented yet.

The next step is still experimental groundwork: validate the paired UHO opening setup, benchmark safe concurrency, and make sure the arena can produce enough decisive games without compromising clock measurements.

Only then does BF001 become the first build that is allowed to behave differently from Stockfish.

Milestones

  1. Research question defined

    Reframed bullet-chess strength around both board position and clock economics.

  2. Pristine control environment built

    Pinned Stockfish 18, verified true 60+0 at the UCI level, and created reproducible tournament tooling.

  3. First 25-game control completed

    Produced 25 draws with zero crashes or flags, proving neutrality but exposing a benchmark with too little signal.