The live ops console had to feel alive — regions pulsing, metrics ticking, an event stream scrolling — with no backend at all. The interesting engineering isn't the simulation; it's keeping the whole thing smooth without asking React to animate.
Three layers, deliberately separate
The simulation ticks at 4 Hz on a seeded RNG, mutating a plain state object. Deterministic, so it's the same world every load — and unit-testable.
An observable store bumps a version each tick. React's scalar readouts (players online, matches per minute) read it through
useSyncExternalStoreand re-render four times a second — cheap, and exactly as often as the numbers change.The canvas map runs its own
requestAnimationFrameloop, reads the latest state directly, and interpolates the pulse by wall-clock time. 60 fps, zero React re-renders.
Why not just re-render everything?
Because the map's pulse animates every frame, and the data behind it changes four times a second. If React drove the animation, you'd reconcile a tree 60 times a second for motion the reconciler adds nothing to — and the pulse would stutter whenever the sim ticked. Letting the canvas own the hot path means the animation is smooth regardless of the simulation rate.
Decouple the thing that changes fast from the thing that changes meaningfully. React for data; requestAnimationFrame and canvas for motion.
It's the same split I leaned on shipping real-time game UI: the framework renders state, a separate loop renders time. Keep them apart and each stays simple.