WebSocket protocol
Clients (player phones and the GM console) talk to the engine over a WebSocket,
joining a session by its code. This page documents the message types from
packages/wage-protocol/src/events.ts. You don’t need it to build a game — it’s
for anyone working on a client or the engine itself.
Connecting
Section titled “Connecting”A client opens a WebSocket to the engine’s /ws endpoint for a session
(addressed by join code) and then sends either a join (to play) or spectate
(read-only, used by the GM console) message.
Client → server
Section titled “Client → server”type ClientMessage = | { type: "join"; sessionId: string; name: string } | { type: "spectate" } | { type: "location"; position: Position } | { type: "action"; actionId: string; payload?: Record<string, unknown> } | { type: "chat"; text: string };join— enter the session as a player with a displayname. The server assigns a team and starting state and replies withwelcome.spectate— subscribe read-only; the server streams world snapshots but never expects location/action. The GM console uses this — spectators get the full, unfiltered world.location— a GPS update (sent ~every 2 s).Positionis{ lat, lng, ts, accuracyMeters?, headingDegrees? }.action— perform an interaction (or confirm/deny a mutual one), identified byactionIdwith an optionalpayload.chat— free text. The author is resolved server-side from the socket; a client-supplied name is never trusted.
Server → client
Section titled “Server → client”type ServerMessage = | { type: "welcome"; playerId: string; world: World } | { type: "world"; world: World } | { type: "event"; event: GameEvent } | { type: "error"; message: string } | { type: "chat"; message: ChatMessage } | { type: "chat_history"; messages: ChatMessage[] };welcome— sent once afterjoin, carrying the client’splayerIdand the currentworld.world— a full world snapshot, broadcast every tick (and on demand). The client renders from this. Position visibility is filtered per viewer by the ruleset’svisibilityrules (the GM is never filtered).event— aGameEvent({ type: string; … }) such as atoastorcountdown, produced bysend_eventactions.chat/chat_history— a new message, or recent history replayed once after join/spectate. The GM appears as Gamemaster.error— a problem with the last message.
The world snapshot
Section titled “The world snapshot”The World the server broadcasts is the whole renderable game state: session id,
status (stopped/started/ended), tick number, timers (startedAt,
pausedAt, countdownEndsAt), teams, the map plus placed zones/points/
annotations, all players (id, name, team, position, state, objective), all items,
and the mirrored presentation data (itemTypes, theme, bots, interactions).
Clients keep the latest snapshot and re-render on each one.
Full shapes live in packages/wage-protocol/src/world.ts.
Reconnection
Section titled “Reconnection”Because state is server-side, reconnecting is just opening a new socket and
re-join/spectate-ing: the client receives a fresh welcome/world and is
immediately back in sync. The GM console persists the active session and
reconnects automatically on reload.