Determinism is the feature

The rule that shapes this codebase more than any other is small enough to fit on one line: nothing in the simulation may read the wall clock, call Math.random, touch the DOM, or talk to the renderer.

That sounds like an architectural nicety. It is really the load-bearing decision, and everything interesting rests on it.

How time works

There is no requestAnimationFrame anywhere in the game rules. The simulation advances in game frames of 50 ms, and every delay a worker, building or animal waits on is counted in frames through a single event manager — a frame-bucketed queue with a kill list, so cancelling a pending event is as cheap as scheduling one. A tree takes a fixed number of frames to grow; a miner takes a fixed number of frames to produce; a soldier's promotion is a frame count plus a draw from the random generator.

The renderer runs at whatever speed the browser gives it and interpolates between frames. It never tells the simulation anything.

How randomness works

Every random number in the game comes from one seeded generator — an XorShift64* implementation that matches the reference engine's own test vectors bit for bit, which is checked by a test that runs those vectors. There is exactly one instance per game, and it is part of the saved state.

This is stricter than it looks, because it makes the order of draws part of the contract. Two code paths that produce the same result but draw in a different order are not equivalent; they are a bug that will show up hours later as two machines disagreeing about who won a fight.

What it buys

A headless mode. The repository has a script that loads a map, runs N frames with no browser, no canvas and no renderer, and prints a five-integer checksum. Run it twice with the same arguments and the numbers are identical. That script is the cheapest possible regression test for the entire rule set.

Saves that are provably intact. A save is the live object graph, encoded and compressed. Before the save is accepted, the game is restored from it and its checksum is compared against the checksum before saving. A save that would have replayed differently is rejected rather than stored.

Server-verified saves. Because the server can run exactly the same simulation the browser runs, it can rebuild the opening frame of a game from the map and seed alone, and check that a submitted save descends from it. A save is not a blob the server takes on trust; it is a claim the server can re-derive.

Lockstep multiplayer. This is the real prize. Online play is server-authoritative lockstep: the relay runs the real game in Node — the same code the browser and the headless script run — and is the single clock. During play it sends commands and never world state. Each command carries the frame it executes on, and within a frame commands run in slot order rather than arrival order, so every participant computes the same world from the same stream. Clients report a state hash every turn; a mismatch is healed by shipping that client a fresh snapshot rather than kicking it, and joining, reconnecting and healing a desync are all the same code path.

The cost

The cost is discipline, and it is paid daily. A convenience like "just check how long ago that happened" is not available. Floating-point arithmetic has to be treated with suspicion. A tempting refactor that reorders two loops can be a desync.

The rule is therefore not a convention but a lint rule: the simulation directory is forbidden, mechanically, from importing the renderer, the UI framework or the asset pipeline. It is much easier to keep a boundary than to restore one.