
The relay mesh fans out a room's three channels: state, input, and event.
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 aplayerId.
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.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:
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.
Tick rates
Both state and input are tick-driven. You run your own loop and callwrite 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:
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
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
Gamesclient 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.
Related
- Games — SDK Methods — the typed surface
- Games — Cookbook — short task recipes
- Games — Recipes — full worked examples
- Netcode (Unity) — the UPM transport drop-in
- Realtime Tracks — the MoQT primitive underneath

