Deterministic chaos: a forest allowed to surprise us, a save that never is

← All field notes

The Long Watch wants to be two contradictory things at once. It is a living ecology — grass spreading, plants maturing and dying, dead matter enriching the soil so the next generation grows a shade thicker where the litter fell. And it is a world that, given the same seed, grows exactly the same way every time, down to the last byte of saved state. We call the engineering that lets both be true at once deterministic chaos: the forest is allowed to surprise us; the save file is not.

Why determinism is load-bearing

We don’t treat reproducibility as a nicety. Three things players will touch lean on it directly. The first is shared-seed worlds — hand two people the same seed and they should watch the same terrain rise and the same ecology unfold. The second is replay: a save records its seed plus a timestamped log of what you did, so re-running seed-plus-log rebuilds the world. The third, and the one that matters most day to day, is catching ourselves — if a change quietly alters how the world evolves, we want to know in the same minute, not three sessions later when an old save won’t load.

Everything below follows from one principle.

A change to the world must be intentional, never incidental.

One stream of randomness, branched by name

The first layer is plumbing. All of the randomness that the simulation draws on flows through a single seeded source derived from the world seed. Rendering and interface code are free to use ordinary randomness — a flickering particle never changes the world, so it never has to be reproducible. But anything that touches the saved state draws from the one stream.

That stream is branched, not shared flat. Each subsystem derives its own randomness by mixing the world seed with a named label, and those labels compose only by reaching them through chained steps — you can’t type out a combined label by hand. We branch it further, per species and per plant, so one creature’s draws can never disturb another’s. The point of the discipline is subtle: an accidental correlation between, say, how a plant grows and where it was placed would have to be literally written into the code. It can’t sneak in.

Two smaller habits hold the line. We draw the same dice in the same order at every cell regardless of the outcome — so a skipped draw never desyncs everything downstream of it. And the terrain runs as a slow simulation on the graphics hardware, where the same seed must always carve the same hills; the hardware will silently round the tiniest numbers to zero, so we add a floor to make sure slowly-accumulating values are never lost. A cell of terrain erodes by a vanishingly small amount each tick — many ticks pass before it shifts even a single voxel — which is exactly the regime where rounding bites if you let it.

A stream winding through soft voxel terrain at golden hour, its banks carved into smooth eroded shelves where the slow water has worn the earth away.Concept art · pre‑alpha
A bank this stream carved one vanishing sliver at a time — the same seed must wear it exactly the same way.

The fingerprint that trips on drift

The second layer is a tripwire. We run a fixed seed for a set number of simulation steps, fold the entire resulting world into a single fingerprint number, and lock that number in as the value to watch. If any upstream math changes — even a rounding behaviour, even an ordering — the fingerprint trips. One number stands in for a whole field of values.

Two details earn their keep. We round every position to a fixed grid before folding it in, because raw floating-point math differs subtly across platforms and we don’t want a fingerprint that means “same world” on one machine and “different world” on another. And we guard the fingerprint with one shared piece of code, so the value the test checks is computed by exactly the path the live game runs — never a convenient stand-in that could quietly drift away from reality.

A fingerprint is only trusted after it reproduces identically across repeated same-seed runs — and, for the heavy ones, across separate program launches as well. A number that isn’t stable across runs is a defect, not a value to record: it means the simulation is non-deterministic, and that’s the bug. We keep the watched numbers provisional rather than frozen forever — each one approved by hand — because the shape of a saved world can still change before launch. An intended change means a migration and a fresh, re-approved number; an unexpected shift means we stop and look.

One more thing makes this robust: growth is deterministic by tick count, not frame timing. The world advances about once a second; plant life runs on a faster ecology tick, a few times a second. Heavy growth work gets spread across frames — only so many plants a frame, to stay in budget — but because reproducibility is keyed to the count of ecology ticks and not to how fast frames happen to render, spreading the work across more frames changes nothing about the result.


The loop that fooled the reads

The pivotal lesson came when we finally closed the ecological cycle. Plants seed new plants; growth, death, and decomposition follow; decomposing matter writes fertility back into the soil; new growth reads that fertility and comes up thicker. Birth, growth, death, decomposition, enrichment, and back to birth — a true, self-renewing loop.

Closing it broke fingerprints we thought were unrelated. We were tuning a fertility knob, and we had reasoned that it couldn’t possibly affect the decomposition tripwire — because decomposition reads moisture and temperature, not fertility. That was true of the direct reads, and completely wrong about the world. Over a shared patch of soil, the chain ran anyway: enrichment to fertility to growth to death-timing back to decomposition. The variables were coupled through the loop, not through any line of code that read one from the other.

The honest part is that several careful, independent reviews all ran the same direct-reads check, all were right about the reads, and all shared the same blind spot. Of course they did: a step-by-step audit of what each piece reads structurally cannot see a coupling created by the act of closing the loop. It wasn’t caught by a sharper review. It was caught empirically — by turning the feedback off and watching the old fingerprints reproduce byte-for-byte.

“X does not read Y directly” does not mean X is uncoupled from Y. In a closed loop, the loop is the coupling.

A later refinement sharpened it further. The coupling is real in principle for any check whose scenario spans the loop — but it only actually bites if that scenario’s window runs long enough to reach the first feedback write. A long run drifted exactly as predicted under the same tuning change; a short one held perfectly stable, because no plants had died yet, so no fertility had been written back into the soil. The first deaths arrive only well into the run, and until then there is simply nothing for the loop to couple. So a check isn’t immune because it “doesn’t touch fertility” — it’s immune only if it ends before the loop closes.

A patch of voxel meadow where the grass has come up thicker and darker green than the thinner grass around it, with a few withered, decomposing stems and fallen litter at its edge.Concept art · pre‑alpha
A patch come up thicker than its neighbours — something died here a few seasons ago, and the soil remembers.

What it caught, and what it bought

The tuning that surfaced all this was deliberately by feel: we dialed the fertility yield down and capped it so soil could get rich but not infinite. The captured scenario was the kind of slice that’s only legible because the fingerprint pins it — a good third of the soil enriched, hundreds of plants in the decomposing-litter stage and many more fully gone. State like that is impossible to eyeball; the tripwire is what makes it checkable.

The takeaways travel well beyond this game. When you close a loop, re-analyze every coupling through the loop, not just each step’s direct reads. And verify by reproducing the result, not by reasoning about which lines read what — an inherited “this can’t affect that” is memory the moment it crosses a handoff, and memory is not the same as having re-run it this time.

The forest is allowed to surprise us. The fingerprint is not. That’s the whole bargain of deterministic chaos. We give the ecology room to be unpredictable — grass spreading where the litter fell, a patch coming up thicker than its neighbours because something died there a few seasons ago. And we hold the saved state to a single number that refuses to move unless we move it on purpose. When the failure mode is “the same seed quietly grows a different forest,” a world that distrusts its own arithmetic isn’t paranoia. It’s what lets the forest be alive.

Keep reading

Concept art · pre‑alpha