games data flow

The relay mesh fans out a room's three channels: state, input, and event.

Build multiplayer rooms. Sync world state to every player. Collect inputs from each player. Send events that must not be lost. Create a Games client for a (room, player) and call publishState / publishInput / publishEvent. You do not wire up tracks, select priorities, or route a channel by hand. Each of the three channels uses the QUIC lane that matches its delivery semantics. The lane is pre-set, so you cannot route a channel incorrectly.

The three channels

state and input use QUIC datagrams. There is no retransmit, so the next tick replaces a dropped tick. This is correct for a position snapshot or a controller frame: a re-sent stale tick arrives too late to be useful. event uses a reliable, ordered MoQT subgroup stream. The stream does not drop frames, and it keeps their order. Use it for data that a player will miss: a chat line, a “you picked up the item” grant, or an end-of-match settlement.
Internally, all three channels are MoQT frame tracks: opaque binary with a per-frame priority. The relay mesh fans them out. state and input default to priority 100 on the datagram lane. event defaults to priority 50 on the reliable stream lane. You can override the priority on each write.

Authoritative server vs player roles

A room has exactly one authority (the server) and any number of players. One option sets the role: whether you pass a playerId.

Authority (server)

Construct Games without playerId. The authority publishes state to every player. It subscribes to inputs from all players on a single callback. It is the source of truth for the world.

Player (client)

Construct Games with a playerId. The player publishes input to the server. It subscribes to state from the server. It can also publish and subscribe to events.
The SDK enforces the roles. A call to publishInput or publishEvent without a playerId throws. Only players carry the from identity that the wire format needs. The authority never publishes input and never needs a playerId.

The from header — fan-in without N subscriptions

Every player publishes input to the same track (game/<room>/input). The relay fans every player’s frames to the authority. The SDK adds a small header to each input frame and each event frame. The header identifies the sender without a track for each player:
The authority’s subscribeInputs((playerId, bytes) => …) callback receives the decoded playerId. The SDK removes the header before the callback runs. state frames do not carry the header, because the source is always the room authority.
A playerId must encode to 255 bytes or fewer in UTF-8. The SDK throws if the id is longer. Keep player ids short and stable for the life of the room.

Tick rates

Both state and input are tick-driven. You run your own loop and call write once per tick. The modality does not run a clock for you. When you create the state publisher, you can pass a tickHz hint:
tickHz is a hint. The relay’s admission control and the recorder use it. It does not pace your writes. Typical rates:
Keep state snapshots small and self-contained. Delivery is latest-wins, so each snapshot must stand alone. Send deltas only if your client can recover from a dropped delta (for example, with periodic keyframes). For data that must not be lost, use an event, not state.

Latency budget

The player→relay RTT plus the relay→player RTT sets most of the round trip of player input → server tick → state broadcast. Each direction takes one MoQT group time. On the same continent as a relay POP, the round trip is typically 30–80 ms end to end. Across continents, the RTT sets the limit. Datagram lanes add no retransmit and no head-of-line blocking, so one lost tick costs one tick, not a stall.

When to use it

Use games when…

You have authoritative-server multiplayer: rooms, ticks, a server world, and players that send inputs and receive snapshots. Examples: real-time .io games, action games, board and card games, and co-op sessions.

Use Netcode (Unity) when…

You use Unity and want Netcode for GameObjects / Entities to operate over the same wire. Add the transport package instead of a direct call to this client. See Netcode (Unity).

Use data when…

You want hierarchical MQTT-style topics with + / # filters and retained messages rather than fixed room channels. See Data.

Drop to MoQT when…

Your wire model is not a room with three channels. Import @clutchcall/sdk/moqt and publish or subscribe to frames directly.

Wire convention

Capability tags: game.state, game.input, game.event. The session URL encodes the role. Players dial …/games/<room>/<player>. The authority dials …/games/<room>/_authority.

How it works

You do not manage these items, but they carry your room:
  • One connection. A Games client opens a single MoQT session per (room, player). It opens the session lazily on the first publish or subscribe. It reuses the session for every channel, so all three channels use one QUIC connection.
  • Publish once, reach every player. state, input, and event are MoQT frame tracks. The relay mesh fans them to every subscriber. The authority never counts subscribers. It publishes state once, and the mesh does the fan-out.
  • The correct lane, automatically. Lossy snapshots use the QUIC datagram lane. Reliable events use an ordered subgroup stream. One connection multiplexes both lanes, so there is no separate stack per channel.
  • Flat tick latency as rooms grow. Fan-out stays fast because the data plane is kernel-bypass. It uses a shard-per-core reactor with a zero-copy NIC fast path and lock-free mcache / dcache rings on io_uring.
  • One token to authorize. A single tenant token, scoped to (tenant, room, player?), authorizes the session. This is the same token model as every other modality.