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

Record where a player is looking in every phase, alive or not #34

Merged opened this

shipped production is serving it deploy b42aa0d

opened this

What you told me found this, not my probing. The readout said 30 Hz in, so the wire was fine — and pressing V stopped it dead.

That second fact is the whole diagnosis. The two views draw with different angles:

const facing = mode === "fp" ? head.angle : eye.angle;

Top-down uses the server's angle; first person uses the browser's. So a disagreement between those two is invisible in one view and violent in the other. That asymmetry is why this looked like a renderer bug for so long.

And they were disagreeing. player.angle = input.angle lived inside movePlayer, which the end and over phases return before ever reaching, and which a dead player never enters (if (!player.alive) return). So between rounds, and for anyone lying dead, the server's copy of your angle froze while your browser kept following your mouse.

yaw.js takes the server's angle only when it disagrees with what the client recently sent. Once those sent angles aged out of SENT_MEMORY, the frozen one stopped looking like an echo and started looking like a correction — so the view snapped back to where you were last recorded looking, again and again.

Measured, a client looking around for twenty seconds:

stateview snaps
alive, live phase0.1 a second
alive, END phase (round just decided)0.8 a second
DEAD, waiting for the round0.8 a second
alive, buy phase0.1 a second

After the fix: 0.1 everywhere, which is the baseline in a live round.

The fix is a rule, not a tuning number: looking around is not moving. Movement is what a phase and a death gate; where your head points is recorded every tick regardless. The assignment moves out of movePlayer into tick, for every player, in every phase.

Why the buy phase was fine and the end phase was not — movePlayer runs during buy — is also why this only ever bit as a round turned over, which is exactly where you said it did.

305 tests. The new one walks a seat through live, dead, end, over and buy and asserts the angle follows; putting the assignment back inside movePlayer fails that test and nothing else.

Worth recording what this was NOT, since I tested each one: your body's prediction (0 backward steps in 7,667 frames even at 40ms jitter and 5% loss), the interpolation of other players across a round-start teleport (0 units), the resolution ladder (descends once, never flaps), and the raycaster's cost (0.04ms a frame whether 0 or 9 bodies are in view). Every one of those came back clean, which is what left the angle as the only thing that could differ between two views of the same data.

Sign in to join this conversation.

↑↓ navigate ↵ open esc close

Keyboard shortcuts