Cull, then delta, then binary, in that order

Usurpent is a realtime multiplayer serpent game: a 10,000-unit map, eight thousand pellets, and a 20 Hz snapshot telling every connected client where everything it can see is. Snapshots are the dominant bandwidth cost, and at the start of this work every client received every serpent in full, however far away it was -- measured at 876 KB/s per client on a 32,000-point leaderboard, almost all of it describing serpents nobody could see.

The shrinking happened in three passes, and the order is the argument: interest management, then delta encoding, then binary packing. Each stage is cheaper to reason about than the one after it. Run the same three ideas in the reverse order and you reach the same byte count with a system nobody can debug -- and you spend encoder work every tick compressing data that should never have been sent at all.

The measurements as they landed

Numbers as recorded in the commit messages at the time each stage landed - per client, seven serpents, 20 Hz snapshots. Panel one is the player-array component at three serpent scores (body points in the axis labels); panel two is total per-client traffic under two world arrangements. The binary pass measured as roughly 7x smaller at a populated map and is in the text rather than the chart.

Pass one: decide what deserves to exist

Interest management asks the only question that can make a byte free: should this exist on the wire at all? Everything downstream -- deltas, packing, compression -- can only shrink the cost of a thing that has already been decided should be sent.

Food had been sent by proximity for a while, but the scan that found it was O(pellets x viewers) every tick. A coarse spatial grid, built in the same walk that already indexed the pellets, turns that into a handful of cell lookups per viewer, AABB-trimmed back to the exact reach. Verified identical to the old scan over 400 randomized worlds -- the grid changes the cost of the answer, never the answer.

Serpents had no culling at all. A body is a path rather than a point, so proximity is box overlap: one bounds walk per serpent per tick, which pays for itself the moment it skips a single to_dict. The leaderboard moved to its own rare message, out of the snapshot entirely.

The measured result depends on the world, and the honest way to say that is to measure both worlds: serpents spread over the map, 871 → 379 KB/s; serpents clustered inside one view, 868 → 869 KB/s. Culling is a property of the arrangement, not the code. A benchmark with one map arrangement would have reported one of those numbers as the truth.

Pass two: stop resending what did not change

With the wire carrying only visible serpents, the next question is how much of a visible serpent actually changes. Interior body points never move -- that is the queue invariant the renderer is built on -- and the JSON snapshot was resending all of them twenty times a second anyway.

So a viewer gets a whole body the first time it sees a serpent (a keyframe), and after that only the points appended at the head and a count of those dropped from the tail, tracked per serpent per connection as (epoch, appended, dropped). The epoch handles the cases a delta cannot describe: a respawned body, a serpent that left interest range and came back. Food gets the mirror treatment -- every visible pellet once, then fadd / frem / fmov against a per-connection seen-set, which mostly carries no entries at all, because gravity touches a sharded subset of pellets per tick and the rest are byte-identical.

This is the pass that wins the most, and it wins because of a domain invariant, not a codec:

7 serpents @ 1,000 (206pt)   583K/s -> 31K/s   18.6x
7 serpents @10,000 (251pt)   683K/s -> 31K/s   22.2x
7 serpents @32,000 (311pt)   732K/s -> 31K/s   23.8x

The after-column is flat. Player traffic no longer scales with body length at all -- what remains is the per-serpent scalars, and those are the floor. Every further byte now has to come from spelling, not structure.

The verification is the part worth stealing. A real world was run forward 600 ticks while a client mirror was fed only the delta stream and compared against the full body, every tick, for every serpent: 1198 deltas, 2 keyframes, 0 mismatches. Food deltas reconcile to the exact visible set over the same kind of run. Deltas are the stage where a subtle encoder bug produces a slowly wrong world rather than a broken one, so the harness is the feature.

Pass three: spell what remains as tightly as it deserves

Only now is there something small enough to deserve a binary format. The packed frame reuses the exact dict the JSON encoder already produces -- same logical message, documented quantization (coordinates to ~0.15 units on the 10,000-unit map, heading to ~0.0001 rad, radius to ~0.13) -- and lands roughly 7x smaller at a populated map.

Two decisions matter here. The round-trip test asserts the frame carries the JSON content exactly, within quantization, so "same information, fewer bytes" stays true as the format evolves. And welcome plus leaderboard stay JSON: they are rare and small, and a second format for them would cost more in reasoning than they cost in bytes.

There is deliberately no JSON fallback for snapshots. The binary path is always on. A fallback is a second encoder to keep correct forever, bought for clients that no longer exist.

Why the order is the argument

Each stage has a smaller unit of reasoning than the one before it:

  • Culling is a question about sets -- what is on the wire. Verified by comparing against a full scan: 400 randomized worlds, identical sets.
  • Deltas are a question about values -- how a thing changes. Verified by replaying a mirror against full bodies: 0 mismatches.
  • Binary is a question about spelling -- how a value is written. Verified by round-trip against the dict, within documented quantization.

Every stage's correctness is checked against the representation the previous stage already trusted. Reverse the order and each verification has to reach through a packed frame to ask a semantic question: you are diffing hex instead of dicts, and the mirror harness -- the only reason anyone believes the delta encoder -- becomes nearly impossible to write.

The economic version of the same argument: compressing data you should never have sent is work spent making a design error smaller. Binary-encode first, and every tick pays the packer to spell out 43 serpents a client cannot see, and the credit for the savings lands on the clever codec instead of on the design decision that deserved it.

Measure the real thing, not a model of it

The pairing for all of this is a load harness that connects N headless clients to the real /ws endpoint and runs the server's own bot strategies against each client's local view, reporting per-connection connect time, snapshot rate, latency, drops, and inputs. It reuses the protocol's own field constants and decoder, so it speaks the exact wire format -- the thing under load is the actual server, not a benchmark of your own encoder.

It is also the only benchmark on that board that measures genuine per-connection cost, because everything else is a simulation with fake handlers, and broadcast scaling lives precisely in the part fake handlers skip. One more honesty detail worth keeping: the harness pins the bots' view radius low, because bots do not render, and an inflated view radius would inflate the very numbers it exists to measure. Smoke-tested at five clients: 19.8 snapshots/s against an expected 20, zero drops.

Cull, then delta, then binary. Decide, then diff, then pack -- and let the boring middle stage take the biggest win.