← Notes

Notes · July 2026 · Real-time, networks

smoothness is latency.

A remote cursor is the simplest real-time feature you can build: read a position, send it, draw it. The naive version still feels broken. The other cursor stutters and teleports, and more bandwidth doesn’t fix it. The fix is to render the past on purpose.

The naive version

Send every pointermove, draw every arrival. Both halves are wrong.

On the send side, pointermove fires at display refresh rate: 60 to 120 events a second from one hand. Everyone receives everyone else’s stream, so a room of ten is fanning out thousands of messages a second whose content is mostly “the cursor moved half a pixel.”

On the receive side, the network delivers those messages late, unevenly spaced, and sometimes out of order. A cursor drawn at arrival time replays that noise as motion.

The send side

Throttle on a clock and coalesce: every 50ms, send the latest position and discard the backlog. Sequence-number each message so a late arrival can’t overwrite a newer one; last write wins. Twenty messages a second sounds coarse; smoothed on the receiving end, it’s indistinguishable from a hundred at a fifth of the fan-out.

The receive side

Arrival times can’t be repaired, so stop rendering at arrival time. The fix, borrowed from multiplayer games, is entity interpolation: buffer incoming snapshots and render the cursor at a fixed offset in the past, around 100ms, interpolating between the two snapshots that bracket that moment. Jitter disappears into the buffer, and the motion is interpolated between real samples: continuous by construction.

Feel it

The panel below runs your own cursor through a simulated network — the sliders are the network, the toggles are the fixes. Nothing is staged: messages really are delayed, jittered, and dropped when stale, and the counters are measured live.

The tradeoff

Interpolation is bought with delay: the remote cursor runs about 100ms behind the hand that moves it. In my experience the trade is worth it — a steady 100ms reads as presence, a jittery cursor reads as broken, even at a lower average delay. Size the buffer from your jitter distribution; a buffer smaller than the spikes just moves the stutter around.

The principle

The newest data makes the worst motion. Real-time rendering is a scheduling problem: commit to a render time the network can reliably meet, then hold it.