# Recipes

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

These longer, end-to-end examples combine the games methods into realistic
mini-apps. Each example is self-contained. Supply your own serialization,
because the wire payloads are opaque bytes.

> **NOTE:**
> These examples use a small placeholder codec (`serialize` / `deserialize`). In
> production, use a compact binary format: a fixed struct, FlatBuffers, or
> bit-packed snapshots. Then snapshots stay small enough to fit in a datagram.

## Recipe 1 — An authoritative 1v1 duel server

This is a complete server loop. It reads every player's input. It steps the
world at a fixed tick. It broadcasts state. When the match ends, it sends a
reliable settlement event.

1. **Create the authority client**
Omit `playerId` to take the authority role.

**TypeScript:**
```ts
import { Games } from "clutchcall_connect/games";

const auth = new Games({
  relayHost: "relay.firstbyte.fast",
  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.firstbyte.fast",
    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. Store the latest input for each 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**
On each tick, apply the buffered inputs. Advance the world. Write a
self-contained snapshot.

**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 match 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()
```

The server publishes the event without a `playerId`. Thus every client sees
`from = "_authority"` and knows that the result is server-authoritative.

## Recipe 2 — A browser player client

This is the matching client. It samples local input on each frame. It sends
the input to the server. It renders incoming state. It reacts to the
settlement event.

1. **Join as a player**
**TypeScript:**
```ts
import { Games } from "clutchcall_connect/games";

const me = new Games({
  relayHost: "relay.firstbyte.fast",
  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.firstbyte.fast",
    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 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
smooth motion. Do not block while you wait for a missed snapshot.

```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 on which the server publishes results.

```ts
await me.subscribeEvents({ channel: "match.result" }, (from, bytes) => {
  if (from === "_authority") showResult(deserialize(bytes));
});
```

Delivery is reliable, so this callback fires exactly once, even if a state
datagram was dropped at the same moment.

## Recipe 3 — A lobby with presence + chat

Before the match, a room often needs a **reliable** coordination layer: ready
status, chat, and a host that starts the game. This layer uses only the
**event** channels. There is no state/input loop yet.

1. **Open the lobby channels**
Use one channel for each concern. Each channel 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`. 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 another reliable channel. The SDK decodes the sender id for you.

```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 all players are ready, the host (a player or your server authority) sends
a `start` event. When a client receives the event, it closes 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 open the
> state/input channels on it. One session multiplexes all channels. This example
> closes the lobby only to keep its two phases separate.

## 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 that carries these channels.
