# Details

> Build multiplayer rooms: sync world state, collect player inputs, and send events. Each channel uses the correct lane, and you write no netcode plumbing.

<img src="/images/diagrams/games.png" alt="games data flow" />

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

| Channel | Direction       | Lane              | Delivery        | Use for                            |
| ------- | --------------- | ----------------- | --------------- | ---------------------------------- |
| state   | server → all    | datagram (lossy)  | latest-wins     | tick-rate world snapshots          |
| input   | player → server | datagram (lossy)  | latest-wins     | tick-rate player inputs            |
| event   | any → any       | stream (reliable) | ordered, no-drop | chat, lobby, item grants, settlement |

**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.

> **NOTE:**
> 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`.

```ts
// authority — no playerId
const auth = new Games({ relayHost, token, roomId: "duel-42" });

// player — playerId required
const me = new Games({ relayHost, token, roomId: "duel-42", playerId: "alice" });
```

## 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:

```
[u8 from_len][utf-8 player_id][payload]
```

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.

> **WARNING:**
> 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:

```ts
const state = await auth.publishState({ tickHz: 30 });
setInterval(() => state.write(serializeWorld(world)), 1000 / 30);
```

`tickHz` is a **hint**. The relay's admission control and the recorder use it.
It does not pace your writes. Typical rates:

| Game shape                    | state tick | input tick |
| ----------------------------- | ---------- | ---------- |
| Turn-based / card / board     | 1–5 Hz     | event-driven |
| Casual real-time / .io        | 10–20 Hz   | 20–30 Hz   |
| Action / shooter              | 30–60 Hz   | 60+ Hz     |

> **TIP:**
> 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)](/modalities/netcode/details).
  - **Use data when…** — You want hierarchical MQTT-style topics with `+` / `#` filters and retained
    messages rather than fixed room channels. See [Data](/modalities/data/details).
  - **Drop to MoQT when…** — Your wire model is not a room with three channels. Import
    `clutchcall_connect/moqt` and publish or subscribe to frames directly.

## Wire convention

```
game/<room>/state             server → all     datagram, no from-header
game/<room>/input             player → server  datagram, from-header
game/<room>/event/<channel>   any → any        reliable stream, from-header
```

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.

## Related

- [Games — SDK Methods](/modalities/games/sdk-methods) — the typed surface
- [Games — Cookbook](/modalities/games/cookbook) — short task recipes
- [Games — Recipes](/modalities/games/recipes) — full worked examples
- [Netcode (Unity)](/modalities/netcode/details) — the UPM transport drop-in
- [Realtime Tracks](/concepts/realtime-tracks) — the MoQT primitive underneath
