← Dev Log
BallOrcs Battle2 min read

BallOrcs Battle boots up: a deterministic war engine on a hex world

Our second game, BallOrcs Battle, just got its beating heart.

In plain words

Our second game, BallOrcs Battle, just got its beating heart. It's a strategy game where huge armies of ball-orcs clash, and for online play every player's computer must simulate the exact same battle from the exact same orders — no drift, ever, or the game falls apart. This week we built that guarantee. There's now a hexagon world map, a store that can hold thousands of units, and a single tidy pipeline that turns commands like "spawn" and "move" into simulation. It all runs without any graphics yet — a pure engine you talk to in numbers — and two copies fed the same orders in a different order end up perfectly identical. You can't play it yet, but the foundation the whole game stands on is now real and tested.

Under the hood

Two plan phases landed back-to-back (bbe89bf Phase 0, 82f3d92 Phase 1):

  • Fixed-point everything: the sim core is engine-agnostic and uses Q32.32 fixed point (portable 128-bit mul/div/sqrt, LUT trig in turns) — no floats anywhere, because float results drift across CPUs. A PCG32 RNG with named streams and a streaming state hash give lockstep desync detection out of the box.
  • Hex world: an axial pointy-top hex library (neighbors, distance, rings/ranges with documented iteration order, exact fixed-point world transforms, odd-r offset + chunk indexing) over a dense HexMap of per-hex layers — biome, elevation, passability, feature, resource.
  • One mutation path: POD Commands (DEBUG_SPAWN/MOVE/STOP) with canonical little-endian serialization drain through a CommandBuffer in a cross-peer canonical order (by player, stable within player) — so arrival order can't change the outcome.
  • Units as data: a struct-of-arrays store with stable index+generation ids and a deterministic (hashed) LIFO free list, capped at 8192. SimWorld::step() drains commands → moves → rebuilds a hex-bucket spatial hash (CSR layout, deterministic query order) → tick++; the state_hash covers tick, RNG, map, and units.
  • Save and replay: versioned full-state snapshots restore into a fresh world and continue with an identical hash stream, and a cmdtrace jsonl format (hex-encoded canonical command bytes) is shared by tests, playtest replay, and net dumps.
  • Proven, not hoped: 62 tests green under MSVC /W4 /WX, including a two-world lockstep test (reversed command arrival → identical hashes) and a determinism corpus pinned by golden hashes. CI's GCC build reproducing those same goldens is the standing cross-compiler determinism proof — the green pipeline this week means Windows and Linux agree bit-for-bit. Rendering and a camera are Phase 2; flow-field pathfinding (the placeholder straight-line movement's replacement) is Phase 3; lockstep networking is Phase 4.
#rts#engine#determinism#multiplayer