Protocol reference
Generated from packages/core/src/net/ by scripts/build-protocol-ref.mjs. Do not edit by hand —
regenerate. 05-multiplayer.md MP-180 requires this document to be public, versioned, and "sufficient
for third-party clients and self-hosted servers to interoperate", and a reference that drifts from
the code is worse for an implementer than no reference at all, because they will build against it.
Version
PROTOCOL_VERSION is 1. Every message carries it in its envelope (MP-260); a receiver
that does not speak the offered version refuses the connection rather than negotiating down
(MP-1220).
Framing and size
Messages are JSON objects. The transport is WebSocket over TLS, and RFC 6455 framing discharges the message-boundary obligation of MP-260 — the protocol adds no length prefix of its own. A stream-oriented transport added later must define its framing here.
The maximum accepted message size is 4,194,304 bytes, stated here because MP-260 requires it
"stated in that reference and derived from the largest legitimate payload class — an observation
stream (MP-240) or an AR-500 ViewCheckpoint — rather than left as an unstated implementation
constant". It is checked before the payload is parsed: an over-long message is refused without
being decoded, because a receiver that parses first to find out how big something was has already
done the work the limit exists to prevent.
A malformed, truncated, or over-long message is refused with a typed rejection (below) and never parsed partially.
Authority modes
relay-onlyauthoritative
What a client may receive depends on the mode. Authoritative-mode clients never receive the
canonical order log, RefereeView data, or full-state snapshots under any circumstance — including
fast start and reconnection, even on explicit request (MP-250). A RequestSnapshot from such a
client is answered with a ViewCheckpoint (MP-270).
Upstream — client to authority
| Message | Fields beyond kind |
|---|---|
Hello |
protocol, engine |
Auth |
token |
JoinGame |
(none) |
ClaimSeat |
seat |
LeaveGame |
(none) |
Resume |
lastMsgSeq |
SubmitOrders |
turn, seat, orders, clientBatchId |
CommitOrders |
turn, seat |
RevokeCommit |
turn, seat |
ActivationOrderHash |
turn, hash |
TurnHash |
turn, hash |
RequestCheckpoint |
(none) |
RequestSnapshot |
(none) |
Chat |
text, to |
LobbyChat |
text |
RequestLobbyInfo |
(none) |
Ping |
nonce |
Concede |
turn, seat |
No client may submit anything during the Cascade or the Reckoning; the authority refuses what arrives and does not queue it (MP-235). Session plumbing and the pre-Cascade digests cross that boundary; a submission does not.
Downstream — authority to client
| Message | Fields beyond kind |
|---|---|
GameInfo |
mode, protocol, engine, rulesVersion, viewSchema, initiativeTier, contentHashes, seats, aiSeats, game |
PhaseBegin |
turn, phase |
OrdersClosed |
turn, commitMarkers |
Observations |
turn, events |
ViewCheckpoint |
turn, viewHash, view |
ViewDelta |
turn, baseViewHash, viewHash, delta |
OrderResults |
clientBatchId, results |
ActivationOrderHash |
turn, hash |
TurnHash |
turn, hash |
Presence |
seats |
ChatRelay |
from, text, turn |
LobbyChatRelay |
from, text |
LobbyInfo |
listing |
Control |
notice |
LogTail |
records |
Snapshot |
stateHash, state |
Rejected |
reason |
The observation stream is one payload per seat per turn, dispatched for every seat together, and an empty stream is dispatched at the same moment as a full one (MP-1125) — a transport that treats "no observations" as "nothing to send" turns silence into a signal.
Typed rejections
MalformedTruncatedTooLargeUnknownKindProtocolMismatchWrongGameNotPermittedInMode
Bundle format (infrastructure-free play)
A turn bundle is a file, so bundle mode needs no network at all (MP-080). Phases:
sign-inturn-openturn-closed
A packet is refused for exactly these reasons, kept distinct because a caller that cannot tell them apart cannot tell a replay attempt from a version skew from a malformed file:
WrongGameStaleBundleSeqWrongTurnPacketCarriesStateSeatKeyAbsentRecordsFromAnotherSeatChainBrokenCommitmentBrokenCommitmentUnrevealedWrongCloser
The closer commits to its own packet in the turn-opening bundle and reveals the key only in the closing bundle (MP-845), which makes the closer's unavoidable early sight of every other packet worthless — their own orders were fixed before anyone else's existed.
Server API
Not yet specified: @everylastcity/server does not exist. 03-architecture.md AR-020 names it in the
dependency graph, and 08-services-platform.md owns its surface. This section is deliberately empty
rather than absent, so that an implementer reading MP-180's three obligations can see which of them
this document currently discharges: the wire protocol and the bundle format, and not the server API.