Determinism
Given the same build and the same inputs, Tide's simulation gives bit-identical results on every supported platform: Windows, Linux, macOS and the web, on Intel, AMD and ARM. That's what lets players send only their inputs, and lets desktop and web players share one match.
It uses ordinary 32-bit floats, not fixed point. Tide code gets this for free: there's nothing to avoid, and no special types to use. This page is about how the engine keeps it, for the curious, and for anyone writing C that touches the simulation.
Floats
Only the IEEE 754 basic operations, + - * / and square root, give the same result on every machine. Everything else is built from them:
- No fused multiply-add. The compiler never fuses
a * b + cinto one instruction, which rounds differently. - No fast-math. Float operations are never reordered or reassociated.
- The engine's own math library. Vectors, matrices and quaternions are written in-house.
Sin,Exp,Powand the other transcendental functions are computed from the basic operations, never with the platform's math library, which differs between systems. - No approximate instructions, like
rsqrtps, whose results differ between Intel and AMD. - Denormals stay on, on every thread and platform, since WebAssembly can't turn them off.
- No relaxed SIMD on the web, whose fused multiply-add differs between machines, and no x87.
The engine compiles everything, your game included, with the flags that guarantee this.
NaN
NaN in the simulation is a bug: NaN's bit patterns aren't the same everywhere. The engine keeps it out where it can:
- Inputs come from other machines, so NaN and infinite floats in them become the field's default before the game sees them (see Input).
Math.Clampalways returns a value in range,Math.MinandMath.Maxwith one NaN return the other argument, andMath.NormalizeSafegives zero instead of NaN.
Order
Everything that could run in a different order does so in a fixed one:
- Expressions run left to right, as in C#, so spawns and entity IDs come out the same everywhere.
- Systems run in one order (see Systems), and structural changes and events apply in the order they were made.
- Running systems in parallel will never change results: thread timing never decides the order of anything that can be seen.
Integers
Integer arithmetic wraps on overflow, and dividing by zero gives 0, so no input can crash the simulation or reach undefined behavior. Float to int conversion saturates and turns NaN into 0.
How it's tested
The engine's tests hash the exact bits of math results and of whole simulations, and the hashes must match on every platform and in every build configuration: native and WebAssembly, debug and release. The demo's smoke test plays a scripted session through the real platform layer and checks its hash too.
Writing C
Code in C that changes the simulation, like a custom host, follows the same rules: use the engine's tide/math.h, never <math.h>, and don't depend on the order C evaluates function arguments in, which differs between platforms. Draw inputs into locals first.