Skip to main content

Determinism

Identical inputs produce a bit-for-bit identical simulation on every platform. Replays, lockstep networking and content-change checks depend on it.

For multiplayer games

Balaur for multiplayer: lockstep, rollback, and a replay to attach to a bug report.

Engine measures​

  • Rapier's enhanced-determinism feature is on workspace-wide, for the 2D and 3D worlds.
  • Physics and animation step on one app-owned fixed 60 Hz tick (FixedUpdate), never the frame delta.
  • Transcendentals on a simulation path (animation easing, euler↔quaternion conversion, slerp) run on libm. glamx is pinned to libm and scalar-math workspace-wide, so glam's Quat::from_euler and Vec3::normalize use the same libm and no SIMD path.
  • A house lint fails the build on a bare .sin() or f32::sin(x) in Rust.
  • Balaur's Rune fork puts f64::powf and f64::powi on libm, the stdlib's only two platform float calls.
  • Network completions enter the simulation once per tick, at the start of the frame, in arrival order, never from an I/O thread. A recording replays without the network, logins and retries included.
  • Order-sensitive Rust iteration uses an insertion-ordered map type, never bare HashMap iteration.
  • Scene instantiation order and scene-key application order are deterministic.
  • Rendering and audio are observers of simulation state. Headless, offscreen and windowed runs compute the same simulation.
  • Input is captured once per frame into a snapshot. A replay records that snapshot per tick.
  • Dev-mode and shipped bytecode come from one compiler configuration.
  • A repository test asserts two runs match bit for bit.

Scripting and determinism​

  • Rune numbers are IEEE-754 doubles and 64-bit integers; an integer meeting a float becomes that float, exactly.
  • + - * / and sqrt are exactly specified and reproducible everywhere.
  • The VM is a single-threaded interpreter with no native codegen. Evaluation order is fixed.
HazardStatus
sin, cos, exp, pow, ... call the platform libm, which differs across OSesThe math module (math::sin, math::pow, math::PI, ...) is pure-Rust libm, bit-identical everywhere. Rune has no transcendentals of its own. Its float methods sqrt, abs, floor, ceil, round, min and max are IEEE-exact; powf and powi are on libm in Balaur's fork
A random source seeded from entropyRune has none. The random module (random::seed/float/range/int) is an engine-owned seeded PCG32 stream with a fixed default seed
Iteration order over an object or a mapObjects, HashMap and HashSet iterate in the order their keys were written, on every machine
Wall-clock time leaking into simulationSimulation goes in fixed_update(dt), on the 60 Hz step with a constant dt; update(dt) is for presentation. engine::tick() is the clock
Hot reload mid-runOff for a run to be verified

Checking, recording, replaying​

--fixed-tick pins the step. --trace-digest writes a hash of the world each tick.

balaur run my-game --fixed-tick --trace-digest run-a.txt
  • Two runs that differ parted at the first differing line.
  • The cross-platform check is a diff of two machines' files. CI runs it for every example on Linux, macOS and Windows.
  • A recording stores input, not state: minutes of play is kilobytes. A replay re-checks every tick.
balaur run my-game --fixed-tick --record run.blr
balaur replay run.blr --verify # stops at the first tick that disagrees
balaur replay run.blr --entries-at 25 # that tick, one digest per node and component
  • Network replies are recorded. A replay never touches the network.
  • Rollback: a snapshot restores a tick and the simulation re-runs the same inputs forward.
  • The engine's determinism guide lists the rules and the remaining hazards.

In the editor​

  • Every play-in-editor run records its input: keys, mouse, gamepads, network replies. The Recordings panel lists them, ten per game, under the editor's data directory.
  • Opening one rebuilds the scene from the document and feeds the recording in.
  • Lanes show the ticks where keys and buttons went down, network requests returned, the log wrote and the debugger stopped. The rows below list everything recorded at the playhead's tick.
  • The ruler scrubs. Forward steps the engine; backward rebuilds the world and runs from the start. Pause holds the simulation.
  • The verify toggle hashes the world every tick while recording. A replay then reports where it parted.
  • A recording whose scripts were edited since still plays. The dock flags it.
  • Export copies one into the project, where balaur replay --verify reads it.

Tests​

  • The demo project's ball touches down at the same simulation time in headless and windowed runs, and from the exported pack.
  • Pack export writes in sorted key order: the same sources give the same bytes on any machine.
  • CI exports every example twice per platform and compares digests across the matrix.