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-determinismfeature 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.glamxis pinned tolibmandscalar-mathworkspace-wide, so glam'sQuat::from_eulerandVec3::normalizeuse the same libm and no SIMD path. - A house lint fails the build on a bare
.sin()orf32::sin(x)in Rust. - Balaur's Rune fork puts
f64::powfandf64::powionlibm, 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
HashMapiteration. - 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.
+ - * /andsqrtare exactly specified and reproducible everywhere.- The VM is a single-threaded interpreter with no native codegen. Evaluation order is fixed.
| Hazard | Status |
|---|---|
sin, cos, exp, pow, ... call the platform libm, which differs across OSes | The 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 entropy | Rune 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 map | Objects, HashMap and HashSet iterate in the order their keys were written, on every machine |
| Wall-clock time leaking into simulation | Simulation 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-run | Off 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
diffof 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 --verifyreads 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.