{"slug": "a-prompt-for-coding-agents-add-robust-near-zero-lag-online-multiplayer-to-any", "title": "A prompt for coding agents: add robust, near zero-lag online multiplayer to any real-time game", "summary": "A developer published a reusable prompt for coding agents that guides them through adding near-zero-lag online multiplayer to real-time games, covering client-authoritative movement with server-side refereeing, a shared netcode module, a small realtime relay server, and dual transports combining reliable server connections with unordered peer-to-peer links. The prompt instructs agents to first read the game loop, physics and input handling, then map each section onto the specific engine and stack, and it explicitly rules out full server-authoritative rollback for casual games and excludes turn-based titles and large MMOs.", "body_md": "Paste everything below the line into your coding agent at the root of your game's repo. It assumes nothing about your engine, language, hosting or backend.\n\nAdd live online multiplayer to this game: friends join a room by code or invite link and play together in real time with no felt lag.\n\nBefore writing code, read the game loop, the simulation/physics and the input handling. Then write a short plan that maps every section below onto this game and this stack: what the \"player entity\" is, which state goes on the wire, what a \"round\" is, which transport and hosting options this platform has. Ask me only about real design choices (room size, whether players can collide, what ends a round, where to host). Everything else below is decided: it is what held up after many iterations on a shipped real-time game with fast-moving players, and the \"do not\" items were tried and failed. It fits any game where each player controls their own character, vehicle, ship or avatar in real time (racers, platformers, brawlers, co-op action, sports, shooters, open-world hangouts). Section 0 says how to adapt it.\n\n- **Who owns what.** Each player owns their own entity: their client simulates it and everyone else predicts it. Shared things nobody controls alone (a ball, a crate, enemies, pickups, doors) get ONE owner: the server, or the host or nearest player with ownership handed over on contact. Never let two clients simulate the same object.\n- **Hits and damage (shooters, brawlers):** keep movement client-owned as below, but have the server validate hits using where the target was drawn on the shooter's screen at fire time (rewind remote players by the shooter's measured delay, capped). Health, score and deaths are server-decided.\n- **Collisions between players:** optional per game. Racing and sports usually want them; co-op and hangouts often do better as pass-through.\n- **Rounds:** a race, a match, a level, a round of a party game, or none at all (a persistent hangout). Keep the lobby, ready and start flow where there are rounds; drop the parts that do not apply.\n- **Tick rate:** 60 Hz for fast movement; 20-30 Hz is enough for slower games, with the same prediction.\n- **Not covered:** turn-based games (no prediction needed) and large MMOs (they need interest management and server-side simulation).\n\n- **Client-authoritative movement, server as referee.** Each client simulates its own player locally at full rate, so its own controls have zero added latency. It sends its state and never waits for the server. The server relays states, keeps its own copy of every player (for scoring, results and checks) and sanity-checks them. Do not build full server-authoritative movement with rollback for a casual or friends game: it adds input lag or visible mispredictions for little gain. (Fighting games with frame-exact inputs are the exception: use input delay plus rollback there, and say so in the plan. If cheating matters, say so too; the sanity checks below are the minimum.)\n- **Three parts:**  - A **shared netcode module** : wire format, clock sync, remote-entity prediction and smoothing, lag simulator. Pure logic: no rendering, no UI, no platform APIs, so the client, the server and headless tests all run the same code. Enforce that boundary with a test or lint rule.\n  - A small **realtime server** : rooms, authentication, relay, round control, peer-to-peer signalling.\n  - The **game client** : drawing remote players, room UI, session glue.\n- A \n- **Two transports:**  - A reliable, ordered connection to the server (e.g. WebSocket or TCP) for control messages, plus a copy of every state.\n  - **Direct peer-to-peer links** for state updates where the platform allows them (e.g. WebRTC data channels, or UDP with NAT traversal), set to unordered with no retransmits. They skip the server hop and head-of-line stalls. The server does the signalling; the lower slot number initiates each link. Provide a relay (TURN or equivalent) when you can; players who cannot link directly stay on the server path. Once a direct link is up, tell the server which players arrive directly so it stops relaying them (it still receives every state).\n- **Versioning.** One protocol version constant; the server rejects other versions with an \"update required\" error the client shows clearly. Bump it on every wire change. Also exchange a simulation/rules version so mismatched physics never share a room.\n\n- **State updates at a steady rate** (60 Hz worked for a fast game), paced on a fixed schedule: the next send is due a fixed interval after the last one was*due* , not after it went out.\n- **Binary and small** (about 60 bytes): position at full precision, velocities as half floats, orientation as a \"smallest three\" quaternion packed in 32 bits, a sender timestamp and a sequence number, plus the few extras other players need to draw and hear the entity (inputs, animation state, flags). Include acceleration, so receivers can predict second order. In 2D, one angle replaces the quaternion.\n- **States are unreliable and droppable; everything else is reliable and ordered** (join, ready, start, results).\n- **Server checks:** rate-limit states per player (about 1.5x the send rate) and catch only the absurd: speed far above the maximum, or a teleport (moved more than twice the distance the top speed allows over the arrival gap, plus slack, because packets bunch up in transit).\n\nMost visible lag and wobble comes from clock and timestamp errors, not from prediction. Measure before tuning.\n\n- **Clock sync:** ping the server fast while warming up (every 250 ms, about 16 samples), then every couple of seconds. Trust only the lowest-RTT samples; slew small changes and jump only on big ones (first sync, reload). Draw nothing remote until the clock is synced.\n- **Re-stamp on arrival with one shared estimator.** Map each sender's stamps onto the receiver's clock: the fastest stamp-to-arrival gap over a long window (about 15 s) is the fastest one-way trip plus the clock offset; subtract half the fastest round trip of the last minute. Let the mapping slide slowly (a few ms per second at most) so it never reads as the entity surging; take big changes at once.**The server uses it to re-stamp relayed states, and each client uses the same estimator for its direct links** , so both paths agree and switching between them does not jump the entity.\n- **Stamp at the frame's own time** , not whenever the send code happens to run: work earlier in the frame varies by milliseconds, which shows as jitter on anything fast.\n\n- **Extrapolate to the present.** Place each remote entity where it is*now* : its newest update carried forward by its age (velocity, acceleration, turn rate; smooth Hermite between snapshots inside the buffer). Both players then see each other at the same moment, so positions, contacts and \"who got there first\" agree. Do not interpolate 100 ms in the past: it looks smooth but everyone is drawn behind where they really are, and contacts disagree between screens. (For erratic movers that change direction instantly, such as platformer or shooter characters, keep the prediction cap short and lean on the smoothing; for vehicles, ships and anything with momentum, predict further.)\n- **Cap the prediction** (about 300 ms). Past it the entity is stale: keep it moving at constant speed and turn rate for up to about a second rather than freezing it (freezing a fast entity and then catching up reads as a big jump). After about 500 ms with no news, measured by arrival time and not the sender's clock, make it a**ghost** : see-through and not collidable. Hide it after about 2 s of silence.\n- **Error-decay smoothing:** when a new update moves the prediction, keep drawing the entity where it was and turn the difference into an offset that fades out (time constant about 0.15 s). It never trails a moving target. Snap corrections larger than a respawn or teleport distance.\n- **Path choice per remote player:** draw from whichever path (server or direct) has the*newest data* , never the latest arrival, and switch only when the other path is ahead by a margin (about 30 ms of data) so they never alternate. During a direct-link outage, stay on the last direct update until the server's copy is actually newer.\n- **Collisions (if any):** collide only with remote entities whose position is trusted. One that just appeared, or was just corrected by more than about its own width, cannot be touched for about 400 ms. What you can hit is exactly what is drawn. Respawned players stay ghosts (or invulnerable) until clear of everyone.\n\nJudging netcode from screen recordings does not work. Build this early and judge every netcode change by its numbers.\n\n**Why it matters.** Without a bench, every netcode change is judged by eye, on one network, on one day. Wobble, jumps and lag look alike on video but have opposite causes. The bench splits the error by cause (on our game it showed most of the lag was clock and timestamp error, not prediction, so we tuned the right thing), and it keeps fixes from regressing.\n\n**How to build it:**\n\n1. **One simulated clock for everything.** Run two clients and the server room in a single process on simulated time, so a two-minute run takes seconds and every run is repeatable (seeded randomness).\n2. **Real code, fake network.** Use the game's real simulation, netcode and server room logic; swap only the sockets for simulated links with latency, jitter, loss bursts, stalls and outages. If the netcode is tangled with rendering or UI, move it into the shared module first: that refactor is part of the work.\n3. **Give each client a drifting clock and a busy main thread** (frame rate, late sends, delayed message handling), so clock sync and send timing are tested too.\n4. **Bots or recorded inputs play.**\n5. **Compare against the truth.** Every frame, compare where each client drew the other player with where that player really was at that same true moment. Only one shared timeline makes this possible.\n6. **Output one table row per scenario** , plus an ablation mode that makes one estimator perfect at a time, to show what each costs.\n\nWhat to run and measure:\n\n- **Scenarios** (one-way latency) that people actually play on: 20/50/100 ms direct links, 1% loss, Wi-Fi stalls, long-tailed jitter, clock drift (50 ppm), direct-link outages, server path only, asymmetric links, main-thread hitches, a slow device at 30 fps with late sends, the start of a round, and contact between players.\n- **Metrics:** what each client drew for the other versus where the other truly was at the same true time. Error along the direction of travel and sideways (p50/p95/max), mean bias, wobble (RMS above 2 Hz), forward/back reversals per minute, big jumps per minute, share of frames past the prediction cap, how fast the timing error slides, the server-vs-direct path gap. Split the error by source: timing, predictor, stale data, smoothing.\n- **Ablations:** rerun with one estimator made perfect (exact RTT, true sender clock, true receiver clock) to see what each contributes. Export traces for plotting.\n- **CI:** a quick version of the table plus loose guard-rail tests on every change that touches netcode.\n- **In the real game:** a debug switch that adds latency, jitter and loss (reliable messages keep their order, states can drop), and a debug overlay with each remote player's correction, prediction and jitter.\n- **Load test:** a bot script that fills a room with headless clients that join, ready up and play.\n\n- **Rooms by code:** 6 characters from an alphabet without look-alikes (no 0/O, 1/I/L), and invite links that carry the code. Cap room size from bandwidth: N players each receive N-1 updates per tick.\n- **Identity:** anonymous accounts by default (or the game's existing auth), verified by the server, with a display name players can change from the room.\n- **Host:** the first player in, then whoever has been there longest. The host moves on when the host leaves (at once) or their held seat expires.\n- **Reconnects:** the server holds a dropped player's seat (about 20 s). The client keeps its own slot through the reconnect (do not clear it when the connection closes), shows \"Connection lost. Reconnecting...\" and disables host controls until it is back. Redraw on connection-status changes. Any auth/token refresh during a reconnect must time out and retry, never hang (phones waking from sleep). A second session of the same user replaces the first.\n- **Round flow, driven by the server:** lobby (ready up, host picks settings, host starts once everyone is ready) -> countdown -> go -> playing -> results -> back to the lobby.\n  - **Load in the background.** Join the room from the main menu while the game loads behind it; each client reports when it is loaded, and the Ready button shows progress. A round starts only once everyone can play.\n  - **Start handshake:** after Start, every client moves to its start position and reports \"in position\"; only then does the server start the countdown (with a timeout so one stuck player cannot block everyone). Countdown times come from the synced server clock, so everyone sees \"go\" at the same moment.\n  - **A time limit** per round, shown when it is near.\n  - **Restart vote:** all connected players must agree.\n  - **Recovery:** a reset or respawn puts the player back at the nearest good spot (not the start), at a safe speed, facing the right way, ghosted until clear. When a player is stuck (stopped after a crash, facing the wrong way, out of bounds, fallen off), show a big centre-screen hint with the reset key for their current device (keyboard, gamepad or touch).\n  - Results are saved server-side; leaving mid-round counts as a loss or did-not-finish.\n- **Room UI inside the main menu** , not a separate screen: a big copyable code, Share (native share sheet where available) and Copy link, the player list with each player's colour, ready/loading state and a host tag. Loading placeholders until the room answers, never guessed values. Game audio stays silent until the round starts.\n\n- On shutdown the server **drains** : rounds in progress finish (a persistent world hands players over instead), no new starts, room screens say the server is updating; then it closes every connection with a \"going away\" code and clients reconnect to the new instance. Configure the host's shutdown grace period to at least the longest possible round, and expose that number on a health endpoint.\n- Deploy the server automatically from the main branch once tests pass.\n- Ship multiplayer behind a feature flag until a real multi-player round has been played on a staging deploy.\n\nTrack the funnel with one event per player per step, each carrying room, mode, how they arrived (created / joined by code / invite link), player count and phase: room joined (join time), game loaded (load time), ready, start clicked, round started, start handshake done (wait time), round finished (placing or outcome), back in room, restart vote, invite shared (code / link / share sheet), left (button or closed app), reconnected (downtime), connection failed (error).\n\n1. The netcode module: wire format, clock sync, prediction and smoothing, with unit tests (deterministic randomness, fake timers).\n2. The server: rooms, relay with re-stamping, round control, with tests.\n3. The bench and its baseline table. Tune against it until 50 ms direct links keep the p95 error well under one entity length with no big jumps.\n4. The client: remote drawing, room UI, round flow.\n5. Direct peer links and the relay fallback.\n6. Draining deploys, the feature flag, analytics.\n\nAt every step: build and tests pass, then play it with bots against a local server, then with two real clients through the lag simulator (e.g. 150 ms and 5% loss) before calling it done. Report what you did not verify.", "url": "https://wpnews.pro/news/a-prompt-for-coding-agents-add-robust-near-zero-lag-online-multiplayer-to-any", "canonical_source": "https://gist.github.com/dim-pan/9a4a814d0b1b6c1827d624901be10cd0", "published_at": "2026-11-11 09:41:39+00:00", "updated_at": "2026-11-11 20:24:11.147008+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "generative-ai"], "entities": [], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/a-prompt-for-coding-agents-add-robust-near-zero-lag-online-multiplayer-to-any", "markdown": "https://wpnews.pro/news/a-prompt-for-coding-agents-add-robust-near-zero-lag-online-multiplayer-to-any.md", "text": "https://wpnews.pro/news/a-prompt-for-coding-agents-add-robust-near-zero-lag-online-multiplayer-to-any.txt", "jsonld": "https://wpnews.pro/news/a-prompt-for-coding-agents-add-robust-near-zero-lag-online-multiplayer-to-any.jsonld"}}