benjivers/val Public Public: anyone can read this; the crew can change it.

The whole thing is choppy: the view is stomped 15 times a second, and remote bodies are smoothed wrong #11

Merged opened this in answer to

shipped production is serving it deploy b42aa0d

opened this

All three causes in the issue are fixed, in three commits, plus tests that fail against the old behaviour.

The view is yours between snapshots (static/yaw.js). yaw is no longer overwritten by every snapshot. The local angle drives the camera and the input timer as before; the server's angle is taken back only when there is no view yet, on a seat change, on a respawn, or when the angle that came back matches nothing we sent in the last second — the server echoes our own angle rounded to a tenth, so a genuine correction (a round start recentring everybody) is distinguishable from the routine echo. The same now applies in the top-down view, where the pointer aim is local and your own arrow is drawn at the facing this browser holds rather than the one the server last heard. Nothing new is sent.

Remote bodies are interpolated, not chased (static/interp.js). Each body keeps its last few samples with arrival times; the frame is drawn at now - delay and the body walks between the two samples bracketing that moment, so it moves at the server's own speed and arrives exactly where the server put it, one interval late. The delay follows the measured arrival rate (1.3 × interval, clamped 45–130ms), so it is right at 15Hz and 30Hz without being told which. A missed snapshot carries the last pair's velocity one interval and then holds — never further. Angles turn the short way round; a body that was out of sight, or that jumped further than anyone can run, starts fresh rather than sliding across the map. I kept a few samples rather than strictly two so the delay has jitter margin on both sides; two exactly would stall the body whenever a snapshot arrived early.

Thirty snapshots a second while the room is small (src/arena.ts). broadcastEvery(filled) sends every tick at two or fewer seats and every other tick above that. Measured on a real socket with one seat: 30.8 Hz, mean 3248 bytes a snapshot, 98 KiB/s per seat (in-process over a live round: min 3.2 KiB, p90 3.8, max 4.5, mean 3.4). At most two seats are ever sent that much, and only while somebody is connected. src/arena.test.ts asserts the payload size so the number here is checked rather than assumed.

Tests: 210 pass (bun test). src/yaw.test.ts and src/interp.test.ts cover the pure functions the issue names — the missed-snapshot hold, the short-way-round angle, and that a time inside a pair is never a position outside it. Two tests are the discriminating ones: src/interp.test.ts runs a real match at 30Hz, samples it the way a snapshot does, draws it at 60Hz and measures the spread of per-frame steps (0.09 now, 0.22 with the old exponential chase, bound 0.15); and src/client.test.ts plays the bundled client over a real socket holding the arrow key and asserts the angle the server is told only ever climbs — against the old stomp it steps back ~0.02 rad each time a snapshot lands.

First thirty seconds of the preview: click the canvas to take pointer lock and swing the mouse left and right — the view tracks the mouse continuously, with none of the twice-a-frame drag back. Then hold still and watch a bot cross an open sightline, or press Tab for the map, where several bodies are moving at once: they glide at a steady pace instead of stuttering and trailing.

Left out deliberately: the snapshot's own contents. 3.4 KiB a tick is mostly the board array — ten players' names, agents and tallies resent thirty times a second though they change once a round — so there is an easy halving there for whoever owns the protocol; it is not this issue. Separately, and pre-existing: a connection that gets a spectator socket (no session, or a second client on one address) receives a snapshot with no me, and paintHud throws on it. I did not touch it here, but somebody should.

🤖 Generated with Claude Code

Closes #10

Sign in to join this conversation.

↑↓ navigate ↵ open esc close

Keyboard shortcuts