DOTS//CORE local · not synced

m07 · Advanced Patterns · reading · 10 min

Determinism — why the same inputs must give the same frame

The property, stated precisely

A simulation is deterministic if running it twice from the same starting state, with the same inputs, produces byte-identical results — every frame, every entity, every float.

That sounds like a nicety. It is in fact the load-bearing property behind three things you will want, and one of them is the module this course is building toward:

Replay. Record inputs, not state. A ten-minute match is a few kilobytes of button presses rather than gigabytes of snapshots — but only if replaying those inputs reconstructs the match exactly.

Rollback. Predict ahead, discover you were wrong, rewind, re-simulate with corrected inputs. This is the heart of Netcode’s prediction, and it works only if re-simulating produces the same result the original run would have.

Lockstep networking. Send only inputs across the network; every client simulates identically. Bandwidth becomes independent of world size. One divergent float and the clients silently drift into different games.

Notice what all three have in common: they re-run the simulation and require the re-run to match. Determinism isn’t about being tidy. It’s the thing that makes re-running meaningful.

Why it’s harder than it looks

The naive expectation is that computers are deterministic by nature — same code, same input, same output. For a single-threaded integer program, roughly true. For a parallel floating-point simulation, false in several specific ways, and it’s worth knowing exactly which.

Floating-point addition is not associative. (a + b) + c does not always equal a + (b + c), because each addition rounds. The difference is one unit in the last place — vanishingly small, and completely fatal, because the simulation feeds the result back into the next frame and the error compounds. Two machines that disagree by one ULP on frame 1 disagree visibly by frame 400.

This is the reason parallel accumulation is the classic determinism killer. Sum a million values across eight cores and the order of combination depends on which core finished first, which depends on cache state, other processes, and luck. Run it twice, get two different sums. Both are “correct” to within floating-point tolerance, and the simulation diverges anyway.

cache line · 64 B fetched 32 B useful · 50% of the bandwidth you paid for the same four values summed in two different orders across cores: both results are valid floating-point, they differ in the last bit, and the simulation feeds that difference forward until it is visible

Structural change timing. Module 2 established that command buffers defer structural changes to a playback point. If two systems enqueue commands and the playback order depends on scheduling rather than a declared order, entity creation order varies — which varies entity indices, which varies iteration order, which varies float accumulation order. The chain from “job scheduling jitter” to “different simulation” is short.

Iteration order. Chunk iteration order is not guaranteed to be stable across runs unless something makes it so. If your logic is order-sensitive — and float accumulation always is — order variation is divergence.

What DOTS actually gives you

Here is the honest picture, and it’s more nuanced than “DOTS is deterministic.”

Burst is deterministic across identical hardware and settings. Given the same target and the same compilation, the same instructions execute in the same order producing bit-identical results. Unity.Mathematics is specified to be deterministic in a way UnityEngine.Mathf is not, which is one more reason for the Module 3 rule about which math types to use.

Single-threaded system execution is deterministic, because ordering is declared (Module 2), not incidental. Same declared order, same execution, same result.

Parallel jobs are deterministic only if the work is order-independent. And here the Module 1 property comes back around with new significance: if each entity’s result depends only on its own data, then the order entities are processed in cannot affect the outcome. Order-independence isn’t just what makes parallelism safe — it’s what makes parallelism deterministic.

That’s the key insight and it reframes everything. The same property that lets a job parallelize is what lets it stay deterministic. A job that writes only per-entity results is deterministic no matter how the scheduler distributes it. A job that accumulates into shared state is not, because the accumulation order varies.

Getting determinism, in practice

The recipe follows from the failure modes, and it is a discipline rather than a setting.

Use a fixed timestep. Variable delta time makes the simulation a function of frame rate, so two machines running at different speeds compute different physics. Simulation runs on a fixed step; rendering interpolates for smoothness. This is why FixedStepSimulationSystemGroup exists as a separate group from the ordinary simulation group.

Keep parallel work order-independent. Per-entity results are free. If you genuinely need a reduction, either do it single-threaded, or use a deterministic reduction that combines in a fixed order regardless of completion order.

Make ordering explicit. Declared system order (Module 2), declared command buffer playback points, and where iteration order matters, sort by a stable key like entity index rather than relying on whatever the chunk gives you.

Use Unity.Mathematics. Module 3’s argument was vectorization; the determinism argument is separate and equally binding.

Seed your randomness explicitly. A Random seeded from the clock is nondeterministic by construction. Seed from something derived from simulation state — tick number, entity index — so the same tick always produces the same sequence.

What this buys you

You can now explain why determinism is a pipeline property rather than a setting, why floating-point non-associativity makes parallel accumulation the classic failure, why order-independence is simultaneously what makes jobs parallel and what makes them deterministic, and why thread-safe does not imply deterministic. Next: system groups and the fixed timestep — the machinery that gives determinism a place to live in the frame.

m07.l02