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

forked from trevato/cs

Your own body stops waiting for the network: your movement is predicted and reconciled against the server's word #316

shipped production is serving it opened this has it
opened this

In a room your position is the server's, arriving sixteen times a second, so a key press moves you a round trip later. src/client/session.ts says it plainly — “Predicting your own movement is #305's remaining half and is deliberately not faked here” — and it is the first thing anybody who has played this game will feel. What the fix needs is already on the wire: Snapshot in src/rooms/wire.ts carries tick and own: { pos, vel } for the member's own body, and the client has the level, because hello carries the map and the seed it was generated from.

Movement only. A shot, a hit, damage, ammunition and the round stay the server's word — a client that predicts damage is a client that lies to its player.

Done looks like

  • [ ] A module named for the one concept — src/client/predict.ts — holds the input history and the replay: plain data in, plain data out, no socket and no browser, so its test runs headless like every other here.
  • [ ] Every input frame is kept with the tick it was sent on. A snapshot for tick T rewinds your body to own.pos/own.vel and replays every kept input after T through the engine's mover on the game's own tuning and hull (move, CS_MOVE, CS_HULL) against the level built from hello's seed. Inputs at or before T are dropped, and the history is capped at the ticks a stated worst-case round trip covers.
  • [ ] The correction is invisible under a stated error and honest over it: under a threshold the remainder is eased out over a stated number of milliseconds, over it the body snaps in one frame. Both numbers are constants with a sentence saying why they are those numbers.
  • [ ] Prediction is off where it would lie: dead, spectating, before hello, and for every body that is not yours — those stay interpolated exactly as today.
  • [ ] Nothing else is overwritten: a test asserts health, armour, ammunition, weapon and score on your own body come from the snapshot and are never touched by the replay.
  • [ ] The proof that it is the same physics, and the test that fails today: a Room stepped headless on a scripted input sequence, the predictor fed the snapshots that room produced, and the predicted position equal to the room's within a stated epsilon at every snapshot.
  • [ ] What it costs: the replay of a 250 ms round trip's worth of ticks measured and asserted under a stated millisecond figure per frame, in the src/budget.test.ts idiom, so this cannot quietly become the frame's problem.
  • [ ] bun test green; the README's “Not yet” list loses the movement line and keeps what is still the server's.

Files I expect to change

src/client/predict.ts and src/client/predict.test.ts (new), src/client/session.ts (holds the predictor, applies the correction), src/client/main.ts (the frame draws the predicted body), src/rooms/wire.ts only if the own-body fields need one more number — say so in the pull if they do, src/budget.test.ts, README.md.

Risks

Divergence is the whole danger: the client's level must be the server's (same seed, same generator — assert it rather than assume it), and the input the replay uses must be identical to the one that went up, so build both from the same Pressed. Landing this without the epsilon test above would be landing a second, subtly different physics in the browser, which is exactly what the boundary work in this repository exists to prevent.

Suggested: Vesper — input that has to feel immediate, measured.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs