Concepts
A byte per cell
The Ataraxos simulator was written in CUDA to step 1,536 games in lock step at about 10 million state updates per second on one H100. The design goal here was to match that rate on a few CPU cores without a GPU, keep the state small enough to copy freely during search, and still enforce every competitive rule.
Identities, not types
The cell byte holds a piece identity from 0 to 39 rather than a type. The identity layout is fixed for every game: 0 is the Flag, 1 the Spy, 2 to 9 the Scouts, 10 to 14 the Miners, and so on up to the six Bombs at 34 to 39. A constant table maps identity to type, so battle resolution is one lookup into a 13 by 13 table built at compile time. Storing identities means the engine can keep per-piece history in flat arrays of forty entries per army: where each piece stands, whether it has moved, whether it has been revealed. The features the paper computes over piece history (threats, evades, protections, causes of death) attach naturally to identities that persist as pieces move.
The state
The whole arbiter state is 440 bytes and Copy: 100 cells, 80 piece
squares, 80 flag bytes, a Zobrist key, the counters, the two-square
trackers (each player's last three moves and a same-piece run count) and
the chase tracker with its 32 position keys. No pointers, no heap. A
search that wants to try a move copies the state in about 8 ns and applies
the move in place.
Move generation
Generation walks the 33 movable identities of the side to move instead of scanning the hundred cells, looks up neighbours in a constant table that already has lake squares cut out, and writes into a fixed 256-slot buffer (the most any position can have is 244 moves: eight Scouts with eighteen each plus twenty-five pieces with four). The two-square filter only runs for the piece that just moved; the chase filter only runs when a chase is live and only for quiet moves that end next to an opponent piece.
The end-of-game check after every move needs to know whether the opponent has any legal move at all. It exits on the first move that no anti-chase rule can forbid, so the check costs a handful of comparisons in ordinary positions and only falls back to full generation in the rare positions where every candidate is restricted.
Measured on this machine
The release-mode benchmark on September 18, 2026 (Ryzen AI Max, 32 threads):
| Operation | Time |
|---|---|
| Apply a move, including the end-of-game check | 17 ns, 60 million per second |
| Generate legal moves (mean 28 moves) | 71 ns, 14 million per second |
| Random playout under the competitive rules, one core | 4.5 million plies per second |
| Random playouts on all 32 threads | 84 million plies per second |
| Copy the state | 8 ns |
The paper's 10 million updates per second is three cores' worth here, and their number includes building the observation planes on the GPU, which this engine does separately in the Python bindings. Measuring the observation encoder is part of the training pipeline rather than the engine.
What the engine leaves out
The engine has no networking, clock, storage or policy code. Observations for a seat are produced by a redaction function that keeps the byte-per-cell layout and replaces hidden opponent identities with a single unknown code, plus the public anti-chase trackers so that a searcher can rebuild a rule-exact state from a sampled assignment. Everything else, from the wire protocol to the players, lives in the sibling crates.