# Games — Recipes

> End-to-end worked examples: an authoritative 1v1 duel server, a browser player client, and a reliable lobby + chat layer.

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

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

**TypeScript:**
```ts
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),
});
```

**Python:**
```python
from clutchcall.games import Games

auth = Games(
    relay_host="relay.clutchcall.dev",
    token=await mint_server_token("duel-42"),
    room_id="duel-42",
    on_state=lambda s, reason=None: print("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.

**TypeScript:**
```ts
const world = newWorld();
const latestInput = new Map<string, Input>();

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

**Python:**
```python
world = new_world()
latest_input: dict[str, Input] = {}

auth.subscribe_inputs(
    lambda pid, data: latest_input.__setitem__(pid, deserialize_input(data))
)
```

3. **Step + broadcast at a fixed tick**
Apply the buffered inputs, advance the world, and write a self-contained
snapshot every tick.

**TypeScript:**
```ts
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);
```

**Python:**
```python
import asyncio
TICK_HZ = 30
state = auth.publish_state(tick_hz=TICK_HZ)

async def game_loop():
    while not world.over:
        for pid, inp in list(latest_input.items()):
            apply_input(world, pid, inp)
        step_world(world, 1 / TICK_HZ)
        state.write(serialize_world(world))
        await asyncio.sleep(1 / 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.

**TypeScript:**
```ts
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
}
```

**Python:**
```python
results = auth.publish_event(channel="match.result")

async def end_match(winner: str):
    results.write(serialize({"winner": winner, "final_score": world.score}))
    await asyncio.sleep(1)   # let the reliable event flush
    auth.close()
```

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**
**TypeScript:**
```ts
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),
});
```

**Python:**
```python
from clutchcall.games import Games

me = Games(
    relay_host="relay.clutchcall.dev",
    token=await fetch_room_token("duel-42", "alice"),
    room_id="duel-42",
    player_id="alice",
    on_state=lambda s, reason=None: set_online_badge(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.

```ts
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.

```ts
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.

```ts
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.

**TypeScript:**
```ts
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" });
```

**Python:**
```python
lobby = Games(token=tok, room_id="duel-42", player_id="alice")

chat  = lobby.publish_event(channel="chat")
ready = lobby.publish_event(channel="ready")
```

2. **Track presence + ready state**
Subscribe to `ready` and keep a roster keyed by the decoded sender id.

**TypeScript:**
```ts
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
```

**Python:**
```python
ready_by: set[str] = set()

def on_ready(pid, data):
    if deserialize(data)["is_ready"]:
        ready_by.add(pid)
    else:
        ready_by.discard(pid)
    render_roster(ready_by)

lobby.subscribe_events(channel="ready", on_event=on_ready)
ready.write(serialize({"is_ready": True}))
```

3. **Wire chat**
Chat is just another reliable channel; the sender id comes decoded.

```ts
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.

```ts
await lobby.subscribeEvents({ channel: "start" }, async () => {
  await lobby.close();          // drop the lobby session
  startGameplay();              // construct the gameplay Games client
});
```

> **TIP:**
> 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](/modalities/games/sdk-methods)** — Every method, param, and handle in detail.
  - **[Cookbook](/modalities/games/cookbook)** — Short single-task snippets.
  - **[Netcode (Unity)](/modalities/netcode/details)** — Run Netcode for GameObjects / Entities over the same wire.
  - **[Realtime Tracks](/concepts/realtime-tracks)** — The MoQT primitive these channels are built on.
