Longer, end-to-end examples that combine the games methods into realistic mini-apps. Each one is self-contained — bring your own serialization (the wire payloads are opaque bytes).
These examples use a tiny placeholder codec (serialize / deserialize). In production use a compact binary format — a fixed struct, FlatBuffers, or bit-packed snapshots — so snapshots stay small enough to fit a datagram.

Recipe 1 — An authoritative 1v1 duel server

A complete server loop: read every player’s input, step the world at a fixed tick, broadcast state, and emit a reliable settlement event when the match ends.
1

Create the authority client

Omit playerId to take the authority role.
import { Games } from "@clutchcall/sdk/games";

const auth = new Games({
  relayHost: "relay.clutchcall.dev",
  token:     await mintServerToken("duel-42"),
  roomId:    "duel-42",
  onState:   (s) => console.log("relay state", s),
});
2

Fan in every player's input

One callback collects all players. Stash the latest input per player; the tick loop reads it.
const world = newWorld();
const latestInput = new Map<string, Input>();

await auth.subscribeInputs((playerId, bytes) => {
  latestInput.set(playerId, deserializeInput(bytes));   // latest-wins
});
3

Step + broadcast at a fixed tick

Apply the buffered inputs, advance the world, and write a self-contained snapshot every tick.
const TICK_HZ = 30;
const state = await auth.publishState({ tickHz: TICK_HZ });

setInterval(() => {
  for (const [pid, input] of latestInput) applyInput(world, pid, input);
  stepWorld(world, 1 / TICK_HZ);
  state.write(serializeWorld(world));   // datagram, latest-wins
}, 1000 / TICK_HZ);
4

Settle the match with a reliable event

When the world ends, send the result on a reliable event channel — a dropped settlement would be a bug.
const results = await auth.publishEvent({ channel: "match.result" });

function endMatch(winner: string) {
  results.write(serialize({ winner, finalScore: world.score }));
  setTimeout(() => auth.close(), 1000);   // let the event flush, then leave
}
Because the server publishes the event without a playerId, every client sees from = "_authority" and can trust the result as server-authoritative.

Recipe 2 — A browser player client

The matching client: sample local input each frame, push it up, render incoming state, and react to the settlement event.
1

Join as a player

import { Games } from "@clutchcall/sdk/games";

const me = new Games({
  relayHost: "relay.clutchcall.dev",
  token:     await fetchRoomToken("duel-42", "alice"),
  roomId:    "duel-42",
  playerId:  "alice",
  onState:   (s) => setOnlineBadge(s === 1),
});
2

Push input on the client tick

publishInput binds to alice, so the server’s fan-in already knows who sent the frame.
const input = await me.publishInput();

function clientTick() {
  input.write(serializeInput(readGamepad()));   // datagram — drop is fine
  requestAnimationFrame(clientTick);
}
requestAnimationFrame(clientTick);
3

Render server state

Each state callback is one authoritative snapshot. Interpolate toward it for smoothness; never block waiting for a missed one.
let target = null;
await me.subscribeState((bytes) => { target = deserializeState(bytes); });

function renderLoop() {
  if (target) interpolateAndDraw(target);
  requestAnimationFrame(renderLoop);
}
requestAnimationFrame(renderLoop);
4

React to the settlement event

Subscribe to the same reliable channel the server publishes results on.
await me.subscribeEvents({ channel: "match.result" }, (from, bytes) => {
  if (from === "_authority") showResult(deserialize(bytes));
});
Reliable delivery means this fires exactly once even if a state datagram was dropped around the same moment.

Recipe 3 — A lobby with presence + chat

Before the match, a room often needs a reliable coordination layer: who’s ready, chat, and a host kicking off the game. This rides entirely on the event channels — no state/input loop yet.
1

Open the lobby channels

Use one channel per concern. Each is an isolated reliable, ordered track.
const lobby = new Games({ relayHost, token, roomId: "duel-42", playerId: "alice" });

const chat  = await lobby.publishEvent({ channel: "chat" });
const ready = await lobby.publishEvent({ channel: "ready" });
2

Track presence + ready state

Subscribe to ready and keep a roster keyed by the decoded sender id.
const readyBy = new Set<string>();

await lobby.subscribeEvents({ channel: "ready" }, (playerId, bytes) => {
  const { isReady } = deserialize(bytes);
  isReady ? readyBy.add(playerId) : readyBy.delete(playerId);
  renderRoster(readyBy);
});

ready.write(serialize({ isReady: true }));   // announce myself
3

Wire chat

Chat is just another reliable channel; the sender id comes decoded.
await lobby.subscribeEvents({ channel: "chat" }, (from, bytes) => {
  appendChatLine(from, deserialize(bytes).text);
});

sendButton.onclick = () => chat.write(serialize({ text: chatInput.value }));
4

Hand off to the match

When everyone is ready, the host (a player or your server authority) emits a start event. On receipt, every client tears down the lobby and constructs the gameplay client from Recipe 2.
await lobby.subscribeEvents({ channel: "start" }, async () => {
  await lobby.close();          // drop the lobby session
  startGameplay();              // construct the gameplay Games client
});
You can keep the lobby’s Games instance open through the match and just open the state/input channels on it — one session multiplexes all channels. Tearing it down here is only to keep the example’s two phases clearly separated.

Where to go next

SDK Methods

Every method, param, and handle in detail.

Cookbook

Short single-task snippets.

Netcode (Unity)

Run Netcode for GameObjects / Entities over the same wire.

Realtime Tracks

The MoQT primitive these channels are built on.