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 #10

Closed opened this has it
opened this

Playing it, everything is choppy — not just movement. There are three causes and they stack. The first is a bug, the other two are the wrong technique.

1. The snapshot overwrites where you are looking (static/val.js:99)

if (snap) yaw = snap.me.angle;

yaw is yours to move between snapshots — the comment on line 41 says so — and then every arriving snapshot sets it back to the angle the server last heard about, 15 times a second. Mouse deltas update it at frame rate on line 160, so the view is dragged back to a stale angle constantly. That is why looking around feels chopped even when you are standing still, and it will be worst when you turn fastest.

The server does not need to teach you where you are looking: you told it. Keep the local yaw authoritative for your own camera, send it as you already do, and accept the server's angle only when you have no seat, on a respawn or a seat change, or if it diverges beyond something large enough to mean a real correction. The same applies on line 247.

2. Remote bodies are exponentially smoothed, per frame (static/val.js:283)

now.x += (x - now.x) * 0.35;

Two problems. It is frame-rate dependent — 0.35 per frame is a different speed at 60Hz than at 144Hz — and it is a chase that never arrives, so every other player is permanently behind and rubbery.

Replace it with real entity interpolation: keep the last two snapshots per id with their arrival times, render at now - delay (one snapshot interval, so ~70–100ms), and interpolate between the pair that brackets that time. Bodies then move at a constant, correct speed and arrive exactly where the server put them, one interval late. Extrapolate no more than one interval when a snapshot is missed, then hold — never guess further. Angles interpolate the short way round, as the current code already does.

If a dt-based smooth is kept anywhere, it must be 1 - Math.exp(-rate * dt), not a fixed per-frame fraction.

3. Fifteen snapshots a second is not many (src/arena.ts:31-32)

TICK = 1/30 with BROADCAST_EVERY = 2. The simulation already runs at 30Hz; broadcasting every tick doubles the information the client has to work with, and with interpolation it halves the render delay. The cost is real but small — ten players of small JSON, and only while somebody is connected now that the arena idles.

Send every tick (30Hz) while two or fewer seats are filled, and fall back to every other tick above that. Measure the snapshot bytes and say what they are in the pull, so the trade is on the record rather than assumed.

The bar

Keep the server authoritative: this is all presentation. Prediction for your own body stays as it is, and nothing new gets sent to the server.

Test what can be tested without a browser: the interpolation is a pure function of two samples and a time — including the missed-snapshot hold, the short-way-round angle, and that it never returns a position outside the pair it is given. Say in the pull what the snapshot rate and payload size ended up being, and what to do in the first ten seconds of the preview to feel the difference.

Sign in to join this conversation.

↑↓ navigate ↵ open esc close

Keyboard shortcuts