Skip to content

packages/protocol ​

The wire contract (protocol 34: a run's run frame to its players, the spectate frame of a player out of it, and the input held while a run is not live; 33: an entity's life under its id in its record, gen, and every change of the delta snapshots carrying the entity's tier, a pose's age over a transport that loses frames, and a far entity's pose whenever its rest changes; 32: a dog is a character of the pedestrians' last role, its lead in the bytes a character had no use for; 31: the acked snapshots and the frames of inputs, for a transport that loses frames). Binary frames for the hot path (hand-written DataView codecs in codec.ts: the 60 Hz input, the 20 Hz snapshot) and JSON validated by zod for the rest (messages.ts: the welcome, with its level inline or named by hash (LevelRef, served at levelFilePath, asked for with LEVELS_BY_HASH), the room's items, the game's restricted zones and race gates, and whether the server takes the client's debug commands (debug, always sent; an older server's welcome without it is taken as unknown, never as off); city.ts: the room clock, its weather (weather) and time skipped (skip, "3 hours later"), and the junctions switched by hand; the mission tracker and notices; the Wanted level; each player's inventory, open container (or the pockets they search) and stored cars; the cars' key tags (keys); each reset of the level (reset, with its count, which the end-to-end tests wait for); each player's stealth, seen or hidden, and the room's alert; the debug commands and the AI readout, with the sentries and cameras). The input carries the item keys (the selected slot, drop, use, a transfer), the interaction key held (a lock picked) and a garage's service (a respray, storing); snapshots carry the doors (a key's or a keypad's lock), the items on the ground, the containers, the terminals, the security cameras (their state; their sweep is the level's plan) and a character's role (a contact), status and alert phase; a dog is a character whose cls is the pedestrians' role dog, and it and its walker carry their lead in ownerKind and ownerId (what the dog does, whether it is on the lead, the entity at the other end). Each player's phone (contacts, messages, apps, calls, the conversation and the screen open, the waypoint) goes to that player alone; the client's phone commands are inputs the server checks. The room's social frames (social.ts): the client's room commands (a line, a quick-chat phrase, an emote, a vote to kick) and the server's chat lines and roster; the welcome carries the game's safe zones and ghost time, the snapshot a player's ghost, a join token the player's party, and a public room's open request where its chat is moderated (version 10). transport.ts is the Transport interface every socket implementation adapts to (lossy when frames may be lost, duplicated or reordered: a WebRTC data channel without retransmits); memory-transport.ts is the in-process pair the tests and bots use, with a flush-driven clock, optional latency and, made lossy (createMemoryLink(latency, { loss, duplicate, reorderTicks, seed })), its unreliable frames lost, duplicated and held back from one seeded generator. rtc.ts is the WebRTC data channel's signalling over the WebSocket (rtc frames: the page's offer, the server's answer, both sides' candidates, a close with its reason; the channel both create, RTC_CHANNEL; the welcome's rtc says the server offers one).

Snapshots go as deltas (protocol 27: snapshot-v2.ts): the server keeps a SnapshotEncoder per client and quantizes every entity once a tick (EntityArena, quantize.ts), the client's SnapshotDecoder turns every frame back into a whole SnapshotMessage and says what the frame told of its entities (FrameNews: those changed, those whose pose it measured and when, those removed, gone or living again under their id, and the tier of each change), and a client that loses the thread sends RESYNC_REQUEST for a keyframe. A change's mask is its groups (bits 0 to 8), SMALL_STEPS (9), the acked snapshots' POSE_FULL (10), the entity's tier (11 and 12, TIER_EVERY: how often its pose comes) and POSE_AGE (13: the pose carried was measured that many ticks before the frame, a byte after the mask). Both encoders share one rule of what goes (changedGroups): what differs from the record the client holds, the poses on the entity's turn, in full for the client's own, and whenever the entity's rest changes (atRest, quantize.ts: a quantized velocity across the ground within REST_STEPS of zero), so a stop and a start are news at once; an entity whose generation changed under its id goes as removed and whole again. See Performance and Netcode.

Over a transport that loses frames the snapshots are acked (protocol 31: snapshot-acked.ts, kind 6): the server's AckedEncoder keeps computing what an ordered client would hold after each frame and sends what turns the newest frame the client acknowledged into it (a keyframe with nothing acknowledged), the events again until a frame holding them is acknowledged (twenty frames at most), a change's pose full or quantized in its own bit (POSE_FULL); the client's AckedDecoder keeps the states of its last 64 frames and drops a frame onto a base it does not hold, never asking for anything. Every frame the client shows is the ordered client's at that tick. snapshot-reader.ts' SnapshotReader is how the client reads every snapshot frame, whole, ordered or acked: stale frames dropped, a switch of kind on a keyframe, the events once, the tick to acknowledge. The client's inputs travel as frames of its last few (codec.ts' kind 7, encodeInputBatch: the ack, the newest number, the inputs newest first); the single input (kind 1) is still read. snapshot-acked.property.test.ts holds the acked snapshots to the ordered ones over two hundred seeded worlds sent through loss, duplicates and reordering.

Layouts are documented at the top of codec.ts, snapshot-v2.ts and snapshot-acked.ts. Inputs are quantized to 8 bits before they are applied locally, so the predicting client and the server simulate the same numbers.

Lobbies (lobby.ts): the platform API's lobby shapes, shared by the API and the web client and never sent on a room's socket (so PROTOCOL_VERSION does not count them): the requests (a lobby opened, a team, ready, a vote, a kick or an invitation, Quick play's playlist), a lobby as its page shows it (LobbyView: the mission's rules, the teams' members, the countdown, the results, the vote), a public lobby (LobbySummary), a mission of the index (MissionEntry), an invitation, and the feed's lobby and run events (LobbyFeedEvent: a change carries the lobby as the member sees it, view; a lobby closed or a member taken out of it says why, text). A reader not in a lobby gets no accounts nor its run (null). A run's RoomStatus lists its seats gone for good (gone), whose accounts the platform frees for another run.

Runs (TECHNICAL_SPECS §9.5): platform.ts has the run's contract between the platform and the game servers (RunSpec in a RoomOpenRequest, which may name a level that ships instead of an artifact, bundled; the join token's run, seat and team; the RunResult a game server reports), messages.ts the reliable run frame its players are sent (RunMessage: the mode, the phase, the countdown, the seats (who is out, each one's client), the teams' scores, the lives, the retries, the result with a versus run's winner, forfeit and places), the spectate frame a player out of the run sends to say whom they watch, and heldInput, what a run not live yet plays on. A RunResult is won (its winner, forfeit) or a draw in versus, with each seat's catches, takedowns and race place; a fleet server's heartbeat may say its maxRooms. A join token may carry debug (an account the platform's FOI_DEBUG_ACCOUNTS names: its debug commands honoured in any room), and a run where one was honoured says so on its RunMessage and its RunResult (debug: it pays nothing). These fields are optional, so neither they nor the welcome's debug change the protocol's version.