The reconciliation system caused visible teleportation at high latency
(e.g. 250ms Germany→Korea): position corrections snapped the local
player and thrown items back to stale server states.
- Drop the inputAck/seq tracking, InputBuffer, and applyReconciliation.
The client predicts locally and the server stays authoritative via
the existing worldUpdate broadcast; own-doll updates remain ignored.
- Override Item.setUpdateData on the client so in-flight items run free
on local Box2D physics (client and server share the same throw
impulse) and only sync when stationary or far out of sync. This keeps
grab-sensor contact accurate while eliminating mid-flight snapping.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Socket.IO (TCP) holds back later packets while it retransmits a lost
one, which stalls worldUpdate delivery on lossy long-distance links —
exactly the pattern game state suffers worst from. WebRTC DataChannels
in unreliable mode (ordered:false, maxRetransmits:0) drop late packets
instead of queueing them, which is what we want for high-frequency
state sync.
Adds a per-user WebRTCTransport on top of the existing Socket.IO
connection. Socket.IO stays in charge of bootstrap, signaling
(SDP/ICE exchange), and control messages — only gameCommand payloads
get routed onto the unreliable channel once it's open. If WebRTC
fails to negotiate, gameCommand transparently falls back to
Socket.IO, so the game keeps working unchanged.
A new StatsLogger writes per-session JSONL events (session_start,
webrtc_ready with negotiation time, per-second stats with transport,
RTT samples, recv/send rates, seq gaps) so we can compare real-world
runs (e.g. Germany server <-> Korea client) instead of guessing.
URL flag ?webrtc=0 forces fallback for A/B testing.
scripts/webrtc-browser-test.js spins up a headless Chromium against
a freshly-started server and asserts the unreliable channel opens
and gameCommand traffic actually rides it.
The old PUNKBUSTER check compared client-reported position to server
position and snapped the player back when latency made them diverge,
which felt like getting teleported under any real network conditions.
Replaces that with proper client-side prediction + reconciliation:
client tags each input with a sequence number and keeps an input
buffer; server tracks the last processed sequence and reports its
authoritative position via a per-user inputAck alongside each
worldUpdate. The client only corrects when the actual disagreement
exceeds what the unacked input time can explain — so steady-state
movement runs purely on local physics, and only genuine unexpected
events (collisions, being hit) trigger a smooth blend toward the
server state.
Includes adaptive threshold scaling so high-latency sessions don't
false-positive corrections during normal running.