How growordie.io works
growordie.io is a massively-multiplayer snake game running on vanilla Node.js — one shared arena, every collision lethal, ~1,000 players per CPU core. The interesting parts aren't the rules, they're the engine. Below are the five mechanisms that make it hold together, each as an animated diagram. No slides, no walls of text — watch the thing move.
Everything here is server-authoritative: the client sends a heading, the server simulates at a fixed 30 Hz and streams back tiny binary snapshots. For the why-and-numbers of the whole stack, see 1,000 players per CPU core. This page is the how, in motion.
- 01 Incremental spatial hash — O(1) collision upkeep per tick
- 02 Area-of-interest — you only receive what your camera sees
- 03 Netcode timing — 30 Hz sim, 15 Hz binary snapshots
- 04 Swept segment-vs-segment collision — killing the tunnel bug
- 05 Neon without shadowBlur — the stacked-stroke glow
01 · The incremental spatial hash
O(1) per snake per tick· no world rebuild
Collision needs every head tested against every body segment, every tick. We bucket segments into a grid — but we never rebuild it. A snake only grows one segment at its head and drops one at its tail, so each tick is one insert and one remove: constant time, no matter if the snake is 2 m or 100 m.
A 100-meter titan costs the grid exactly as much upkeep as a rookie. The structure scales with player count, not with total mass in the arena.
02 · Area-of-interest (AOI)
you only receive what's near your camera· known-set delta
The server simulates every snake, but sending all of them to everyone is O(N²) bandwidth. Instead each client gets snapshots only for snakes intersecting its viewport — a cheap query on the same grid the collision system already maintains.
The client keeps a known set: a snake crossing into view is a +add, one leaving is a −del. Your bandwidth tracks the density of the action around you, not the server's population.
03 · Netcode timing
30 Hz sim · 15 Hz binary snapshots· 3 B up / ~20–40 B per snake down
Two separate rates run in parallel — the whole idea is that they're separate:
Simulation rate (30 Hz) — how often the server recomputes the world: moves every snake a step, lays body points, checks collisions. This is the game's truth.
Observation rate (15 Hz) — how often the server sends that state to you: the snapshots your screen draws. Between two snapshots the client interpolates, so it looks smooth even though it only hears from the server 15×/s.
In the diagram: the tick marks are the 30 Hz sim, the taller gold ones are the 15 Hz snapshots (every 2nd tick). The little boxes are packets — tiny 3 byte inputs going up, ~20–40 byte snapshots going down. The red bar on the right is how long the current tick took.
When that bar blows past 22 ms (the server is struggling), it has to give something up — and the two options aren't equal. Slow the simulation and each step spans more time → snakes jump → collision tunneling and unfair deaths, for everyone. Slow the observation (15→10 Hz) and the physics stays perfect; your view is just ~50 ms staler, which the interpolation hides. So it always degrades the observation rate, never the simulation. Players forgive a slightly older frame — they never even notice. They don't forgive a collision that ate their run.
04 · Swept segment-vs-segment collision
killing the tunnel bug· lib/geom.js · segSegDist2
The body used to be tested as a row of points sampled every 10 u. A thin, fast head could travel far enough in one 33 ms tick to thread between two of them and come out the other side alive — a classic tunneling bug that ate real runs.
The fix treats the body as continuous segments and the head's motion this tick as its own swept segment, then measures the closest distance between the two (Ericson, Real-Time Collision Detection §5.1.9). If it's under the kill radius, it's a hit — no gap to slip through, and the lateral kill distance stays exactly the same.
05 · Neon without shadowBlur
the no-shadowBlur glow· 0.1 ms/frame · zero GC
Canvas 2D's shadowBlur is the classic perf killer — a per-pixel gaussian that tanks the frame budget. growordie never calls it. The neon halo is faked by stroking the same path several times: a wide, very-low-alpha pass for the outer glow, down to a thin bright core.
Overlapping translucent strokes sum toward white in the middle and fade at the edges — exactly the falloff a blur would give, for a handful of cheap stroke calls and no allocation on the hot path.
— the growordie team