# Games — SDK Methods

> The typed Games surface: client, publishState/subscribeState, publishInput/subscribeInputs, publishEvent/subscribeEvents, handles.

The games modality lives in its own subpath. Import the `Games` client plus its
publisher/subscription handles from there.

**TypeScript:**
```ts
import {
  Games,
  StatePublisher,
  FromPublisher,
  GamesSubscription,
} from "@clutchcall/sdk/games";
```

**Python:**
```python
from clutchcall.games import (
    Games,
    StatePublisher,
    FromPublisher,
    GamesSubscription,
)
```

The TypeScript and Python surfaces mirror each other method-for-method, differing
only in idiomatic case (`publishState` ↔ `publish_state`) and keyword vs options
style. Both sit on the same MoQT substrate, so a TS player and a Python authority
interoperate on the wire.

## `Games` — the client

One `Games` instance per `(room, player)` — or `(room, server)` for the
authority. The session is opened lazily on the first publish or subscribe and
reused for every channel.

<Tab title="TypeScript">

```ts
const games = new Games({
  token:    await fetchRoomToken("duel-42", "alice"),
  roomId:   "duel-42",
  playerId: "alice",          // omit for the authoritative server
  relayHost: "relay.clutchcall.dev",
  onState:  (state, reason) => console.log("conn", state, reason),
});
```

<ParamField path="token" type="string" required>
  Bearer token for the relay session, scoped to `(tenant, room, player?)`.
</ParamField>
<ParamField path="roomId" type="string" required>
  The room every channel binds to. All three channels live under `game/<roomId>/…`.
</ParamField>
<ParamField path="playerId" type="string">
  Player identity. **Omit for the authoritative server.** Required to
  `publishInput` or `publishEvent` — it becomes the `from` header on every frame.
</ParamField>
<ParamField path="relayHost" type="string">
  Relay hostname. Defaults to the platform relay.
</ParamField>
<ParamField path="onState" type="(state, reason?) => void">
  Connection-state callback. `state` is a `ConnectionState`
  (`0` Connecting · `1` Connected · `2` Reconnecting · `3` Closed · `4` Failed).
</ParamField>
<ParamField path="webTransport" type="WebTransportFactory">
  Inject a custom WebTransport factory (for a Node polyfill or tests). Optional.
</ParamField>

</Tab>
<Tab title="Python">

```python
games = Games(
    token=await fetch_room_token("duel-42", "alice"),
    room_id="duel-42",
    player_id="alice",          # omit for the authoritative server
    relay_host="relay.clutchcall.dev",
    on_state=lambda state, reason=None: print("conn", state, reason),
)
```

<ParamField path="token" type="str" required>
  Bearer token, scoped to `(tenant, room, player?)`.
</ParamField>
<ParamField path="room_id" type="str" required>
  The room every channel binds to.
</ParamField>
<ParamField path="player_id" type="str">
  Player identity. Omit for the server. Required to `publish_input` /
  `publish_event`.
</ParamField>
<ParamField path="relay_host" type="str">
  Relay hostname. Defaults to the platform relay.
</ParamField>
<ParamField path="on_state" type="Callable[[int, Optional[str]], None]">
  Connection-state callback (same `ConnectionState` ints as TS).
</ParamField>

</Tab>

> **WARNING:**
> The constructor throws (`Error` / `GamesError`) if `token` or `roomId`/`room_id`
> is missing.

### Namespace accessors

Read-only helpers if you need to inspect or interop with the raw tracks:

**TypeScript:**
```ts
games.stateNs;            // "game/duel-42/state"
games.inputNs;           // "game/duel-42/input"
games.eventNs("chat");   // "game/duel-42/event/chat"
```

**Python:**
```python
games.state_ns           # "game/duel-42/state"
games.input_ns           # "game/duel-42/input"
games.event_ns("chat")   # "game/duel-42/event/chat"
```

## state — server → all

### `publishState(args?) → StatePublisher`

Authority-only. Opens the state track and returns a `StatePublisher`. State
frames carry **no** `from` header (the source is always the authority) and ride
the datagram lane at priority `100`.

<Tab title="TypeScript">

```ts
const state: StatePublisher = await games.publishState({ tickHz: 30 });
```

<ParamField path="tickHz" type="number">
  Tick-rate hint for the relay's admission control and the recorder. Does **not**
  pace your writes — you drive the loop. Optional.
</ParamField>

</Tab>
<Tab title="Python">

```python
state = games.publish_state(tick_hz=30)   # -> StatePublisher
```

<ParamField path="tick_hz" type="int">
  Tick-rate hint. Does not pace your writes. Optional.
</ParamField>

</Tab>

### `subscribeState(cb) → GamesSubscription`

Player-side. Subscribes to the authority's state and invokes your callback with
each snapshot's raw bytes. Returns a `GamesSubscription` you can `close()`.

**TypeScript:**
```ts
const sub = await games.subscribeState((bytes: Uint8Array) => {
  render(deserializeState(bytes));
});
```

**Python:**
```python
sub = games.subscribe_state(lambda data: render(deserialize_state(data)))
```

### `StatePublisher`

<ParamField path="write(stateBytes, priority?)" type="void">
  Publish one state snapshot. `priority` defaults to `100`. Timestamps are stamped
  for you from a monotonic clock.
</ParamField>
<ParamField path="close()" type="void">
  Close the state track.
</ParamField>

## input — each player → server

### `publishInput() → FromPublisher`

Player-only. Returns a `FromPublisher` that prefixes every frame with this
client's `playerId`. Rides the datagram lane at priority `100`. Throws if the
client has no `playerId`.

**TypeScript:**
```ts
const input: FromPublisher = await games.publishInput();
addEventListener("tick", () => input.write(serializeInput(localInput)));
```

**Python:**
```python
inp = games.publish_input()   # -> FromPublisher
inp.write(serialize_input(local_input))
```

### `subscribeInputs(cb) → GamesSubscription`

Authority-side. Receives **every** player's input on one callback. The SDK
decodes the `from` header, so you get `(playerId, bytes)` per frame — no
per-player subscription bookkeeping.

**TypeScript:**
```ts
await games.subscribeInputs((playerId: string, bytes: Uint8Array) => {
  applyInput(playerId, deserializeInput(bytes));
});
```

**Python:**
```python
games.subscribe_inputs(lambda pid, data: apply_input(pid, deserialize_input(data)))
```

> **NOTE:**
> Malformed input frames (bad `from` header) are dropped silently rather than
> throwing into your callback, so one bad packet can't stall the tick loop. The
> relay logs them as wire-format rejects.

## event — any → any (reliable)

### `publishEvent(args) → FromPublisher`

Either role. Opens a reliable, ordered event channel and returns a
`FromPublisher`. Events ride a subgroup **stream** at priority `50` —
guaranteed delivery, in order. The server's events are stamped `from =
"_authority"`; a player's events carry the player's id.

<Tab title="TypeScript">

```ts
const chat: FromPublisher = await games.publishEvent({ channel: "chat" });
chat.write(serialize({ kind: "say", text: "gg" }));
```

<ParamField path="channel" type="string" required>
  Event channel name — `chat`, `ready`, `rpc.use_item`, etc. Each channel is its
  own track under `game/<room>/event/<channel>`.
</ParamField>

</Tab>
<Tab title="Python">

```python
chat = games.publish_event(channel="chat")
chat.write(serialize({"kind": "say", "text": "gg"}))
```

<ParamField path="channel" type="str" required>
  Event channel name. Each channel is its own track.
</ParamField>

</Tab>

### `subscribeEvents(args, cb) → GamesSubscription`

Either role. Subscribes to one event channel; the callback gets the decoded
sender id and the raw event bytes.

**TypeScript:**
```ts
await games.subscribeEvents({ channel: "chat" }, (fromPlayerId, bytes) => {
  appendChat(fromPlayerId, deserialize(bytes));
});
```

**Python:**
```python
games.subscribe_events(
    channel="chat",
    on_event=lambda pid, data: append_chat(pid, deserialize(data)),
)
```

### `FromPublisher`

The handle returned by both `publishInput` and `publishEvent`. Prefixes every
write with the caller's `from` id.

<ParamField path="write(payload, priority?)" type="void">
  Publish one frame. `priority` defaults to the channel default (`100` for input,
  `50` for events). The `from` header is added for you.
</ParamField>
<ParamField path="close()" type="void">
  Close the underlying track.
</ParamField>

## Handles & lifecycle

### `GamesSubscription`

Returned by every `subscribe*` method. Read-only `ns` / `name` plus:

<ParamField path="close()" type="void">
  Stop receiving on this channel.
</ParamField>

### `Games.close()`

**TypeScript:**
```ts
await games.close();   // closes the shared MoQT session + all tracks
```

**Python:**
```python
games.close()          # closes the shared session + all tracks
```

Closes the single underlying session, which tears down every publisher and
subscription on it. After `close()` the next publish/subscribe re-opens a fresh
session.

## Connection events

There is no separate event emitter — connection lifecycle is delivered through
the `onState` / `on_state` callback you pass to the constructor. The underlying
session **auto-reconnects** with capped backoff and **replays every publish and
subscribe** on reconnect, so your channels reattach without any code on your
side.

| `ConnectionState` | Meaning                              |
| ----------------- | ------------------------------------ |
| `0` Connecting    | dialling the relay                   |
| `1` Connected     | session up; channels (re)attached    |
| `2` Reconnecting  | link dropped; retrying with backoff  |
| `3` Closed        | you called `close()`                 |
| `4` Failed        | unrecoverable (e.g. bad URL or auth) |

## Wire helpers (advanced)

For interop tests or callers that bypass the high-level client but stay
wire-compatible, the `from`-header codec is exported:

**TypeScript:**
```ts
import {
  encodeWithFrom,
  decodeWithFrom,
  FROM_HEADER_BYTES,   // 1
  MAX_FROM_LEN,        // 0xff
} from "@clutchcall/sdk/games";

const framed = encodeWithFrom("alice", payload);
const { fromPlayerId, payload: body } = decodeWithFrom(framed);
```

**Python:**
```python
from clutchcall.games import (
    encode_with_from,
    decode_with_from,
    FROM_HEADER_BYTES,   # 1
    MAX_FROM_LEN,        # 0xff
)

framed = encode_with_from("alice", payload)
decoded = decode_with_from(framed)   # .from_player_id, .payload
```

## Other languages

The Go and Rust SDKs expose the same `(room, player)` model and the same three
channels over the shared `MoqtClient`, with method names in each language's
idiomatic case. Package coordinates: `github.com/clutchcall/clutchcall-sdk/go`, `clutchcall`. Unity
games use the [Netcode transport drop-in](/modalities/netcode/details) rather
than calling this client directly.
