# 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_connect/games";
```

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

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

## `Games` — the client

Use one `Games` instance per `(room, player)`, or per `(room, server)` for the
authority. The client opens the session lazily on the first publish or
subscribe. It reuses the session for every channel.

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

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

```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.firstbyte.fast",
    on_state=lambda state, reason=None: print("conn", state, reason),
)
```

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

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

### Namespace accessors

Use these read-only helpers to inspect the raw tracks or to interoperate with
them:

**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, because the source is always the authority.
They use the datagram lane at priority `100`.

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

- **`tickHz`** (`number`) — Tick-rate hint for the relay's admission control and the recorder. It does   **not** pace your writes. You drive the loop. Optional.

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

- **`tick_hz`** (`int`) — Tick-rate hint. Does not pace your writes. Optional.

### `subscribeState(cb) → GamesSubscription`

Player-side. Subscribes to the authority's state. It calls your callback with
each snapshot's raw bytes. Returns a `GamesSubscription` that 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`

- **`write(stateBytes, priority?)`** (`void`) — Publish one state snapshot. `priority` defaults to `100`. The SDK stamps   timestamps from a monotonic clock.
- **`close()`** (`void`) — Close the state track.

## input — each player → server

### `publishInput() → FromPublisher`

Player-only. Returns a `FromPublisher` that prefixes every frame with this
client's `playerId`. Input uses the datagram lane at priority `100`. The call
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)` for each frame. You
do not manage a subscription for each player.

**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:**
> The SDK silently drops malformed input frames (bad `from` header). It does not
> throw into your callback, so one bad packet cannot stall the tick loop. The
> relay logs the dropped frames as wire-format rejects.

## event — any → any (reliable)

### `publishEvent(args) → FromPublisher`

Either role. Opens a reliable, ordered event channel and returns a
`FromPublisher`. Events use a subgroup **stream** at priority `50`. Delivery
is guaranteed and in order. The SDK stamps the server's events with `from =
"_authority"`. A player's events carry the player's id.

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

- **`channel`** (`string`, required) — Event channel name, for example `chat`, `ready`, or `rpc.use_item`. Each   channel is its own track under `game/<room>/event/<channel>`.

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

- **`channel`** (`str`, required) — Event channel name. Each channel is its own track.

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

Both `publishInput` and `publishEvent` return this handle. It prefixes every
write with the caller's `from` id.

- **`write(payload, priority?)`** (`void`) — Publish one frame. `priority` defaults to the channel default (`100` for input,   `50` for events). The SDK adds the `from` header for you.
- **`close()`** (`void`) — Close the underlying track.

## Handles & lifecycle

### `GamesSubscription`

Every `subscribe*` method returns this handle. It has read-only `ns` / `name`
plus:

- **`close()`** (`void`) — Stop receiving on this channel.

### `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. This closes every publisher and
subscription on the session. After `close()`, the next publish or subscribe
opens a fresh session.

## Connection events

There is no separate event emitter. The `onState` / `on_state` callback that
you pass to the constructor delivers the connection lifecycle. The underlying
session **auto-reconnects** with capped backoff. It **replays every publish
and subscribe** on reconnect, so your channels reattach without 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)

The SDK exports the `from`-header codec. Use it for interop tests, or for
callers that bypass the high-level client but stay wire-compatible:

**TypeScript:**
```ts
import {
  encodeWithFrom,
  decodeWithFrom,
  FROM_HEADER_BYTES,   // 1
  MAX_FROM_LEN,        // 0xff
} from "clutchcall_connect/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`. Method names use 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). They
do not call this client directly.
