The tick
The engine advances in fixed steps, 8 per sim-second. Each step()runs the same sequence: maybe spawn a task, apply any scheduled charger failure, reprice energy, run the allocation mechanism (auction, central, or fixed queue), step every robot's state machine, grant charger docks, decay the congestion field, and every two seconds sample metrics. The order is fixed and never branches on anything outside the engine.
Determinism
All randomness comes from a single seeded mulberry32 generator, consumed in tick order. Same seed, same config, same session, every time. That one property carries a lot: the lab can feed an identical task stream to three allocation modes and credit every difference to coordination, the stress suite can reproduce any failure from its seed, and a bug report is just a seed and a tick number.
Stores and rendering
A zustand store owns one engine instance for the whole app. SimClock, mounted once in the root layout, drives it from a requestAnimationFrame loop with a real-time accumulator, so the market keeps trading on every page, this one included. The speed control multiplies the accumulator, and catch-up after a background tab is capped at 48 steps so a long absence never causes a death spiral.
The engine mutates its own state in place; after each batch of ticks the store bumps a version counter, and that counter is all the UI subscribes to. The three.js scene interpolates between 8 Hz engine states so motion reads smooth at 60 fps. A separate fxStore watches the event stream for the social layer: hops on wins, slumps on failures, idle chatter with the ElevenLabs voice pack. None of that feeds back into the engine.
Metrics
Every two seconds the engine samples throughput (completions per rolling minute), the median of the last 40 winning bids, the energy price, average congestion, the wealth Gini across the fleet, task counts, and cumulative value and energy spend. Events keep the latest 300 entries, metrics and balance histories the latest 900 samples, so a session can run all day at flat memory.
What's next
The renderer interpolates whatever 8 Hz state it is given, which is the seam for what comes next: moving the market layer behind a WebSocket to a Rust authority server without touching the scene. After that, RL agents get to join the hand-written cast and be judged by the same leaderboard.