# @ferret/net: API

Replication over pluggable transports, remote avatars, gateways to a game's backend, the Camerlengo link.

46 exports. Generated from the source (`packages/net/src`), so it matches the code.

## Classes

### CamerlengoClient

*class* · `packages/net/src/camerlengo.ts:23`

```ts
class CamerlengoClient
```

- `constructor(readonly opts: CamerlengoOptions)`
- `async call(command: string, params: Record<string, unknown> = {}): Promise<Record<string, any>>`
- `async login(path: string, user: string, password: string): Promise<string>` — Signs in; resolves with the session token.

### CamerlengoError

*class* · `packages/net/src/camerlengo.ts:19`

```ts
class CamerlengoError extends Error
```

- `constructor(readonly command: string, readonly code: string, reason: string)`

### CamerlengoOccupancy

*class* · `packages/net/src/camerlengo.ts:61`

```ts
class CamerlengoOccupancy implements OccupancyGateway
```

Zone occupancy for the backend's registry (`zone:setActive`): a zone with players is active, one that emptied is set inactive.
Uses a service key, so it belongs on the game server (or a trusted host); `level` is the registry's level id.

- `constructor(private client: CamerlengoClient, private opts: { key: string; level: string })`
- `async report(occupancy: Record<string, number>)` — zone hash -> number of players there

### CamerlengoVoice

*class* · `packages/net/src/camerlengo.ts:46`

```ts
class CamerlengoVoice implements VoiceGateway
```

The voice room of a zone for the signed-in player. `session` is read on every call, so it can change (sign-in, refresh).

- `constructor(private client: CamerlengoClient, private session: () => string | null)`
- `async roomFor(zoneHash: string)` — a token and URL for the LiveKit room of a zone (`zoneHash`, never the zone's name)

### Gateways

*class* · `packages/net/src/gateways.ts:55`

```ts
class Gateways implements Service
```

Holds the gateways and calls them safely. `call(name, fn, fallback)` runs `fn(gateway)` and returns `fallback` if the gateway
is absent, switched off, down (inside its cooldown) or the call fails or times out. Events: `gateway:down` { name, error },
`gateway:up` { name }.

- `readonly name: "gateways"`
- `constructor(readonly set: GatewaySet = {}, readonly opts: GatewayOptions = {})`
- `init(world: World)`
- `has(name: GatewayName): boolean` — Is there a gateway of that name, switched on? (Not whether it is reachable.)
- `isDown(name: GatewayName): boolean`
- `async call<K extends GatewayName, R>(name: K, fn: (g: NonNullable<GatewaySet[K]>) => Promise<R>, fallback: R): Promise<R>`
- `user()`
- `balance()`
- `change(amount: number, reason: string): Promise<number | null>` — Earn or spend. Resolves with the new balance, or null when it did not happen (no account, backend down, refused).
- `voiceRoom(zoneHash: string)`
- `async reportOccupancy(o: Record<string, number>)`
- `dialogue(npc: string, utterance: string, context?: Record<string, unknown>)` — The NPC's line from the backend, or null: the caller then says its local line.

### HttpPollTransport

*class* · `packages/net/src/transport.ts:162`

```ts
class HttpPollTransport implements Transport
```

Presence-server transport: every client `set`s one record (its entities' latest state and its last events) and `list`s the
records of all. The server needs no change, but this is polling: positions about twice a second, events as soon as the next
poll. Enough to see other players walk about; use `websocket` or `livekit` for anything quicker.
The record is `{ state, events: [{seq,name,data}], time }` under the key = the client's id.

- `readonly name: "http-poll"`
- `healthy: boolean` — false while the server cannot be reached
- `constructor(readonly opts: HttpPollOptions)`
- `connect(selfId: string)`
- `disconnect()`
- `send(msg: NetMessage, _reliable: boolean)`
- `onMessage(fn: (m: NetMessage) => void)`
- `update()` — Poll when it is time. Call every frame; it does nothing in between.

### LiveKitTransport

*class* · `packages/net/src/transport.ts:242`

```ts
class LiveKitTransport implements Transport
```

- `readonly name: "livekit"`
- `constructor(private room: () => DataRoom | null)`
- `connect()`
- `bind(room: DataRoom)` — Call when the room (re)joins: start listening on it.
- `disconnect()`
- `send(msg: NetMessage, reliable: boolean)`
- `onMessage(fn: (m: NetMessage) => void)`

### LiveKitVoice

*class* · `packages/net/src/camerlengo.ts:92`

```ts
class LiveKitVoice implements VoiceAdapter
```

Joins and leaves the voice room of a zone. `createRoom` makes a fresh Room each time (`() => new Room()` from livekit-client), so
the engine does not depend on that package. `room` is the connected Room, for `LiveKitTransport.bind`.

- `room: LiveKitRoomLike | null`
- `constructor(private createRoom: () => LiveKitRoomLike, private opts: { microphone?: boolean; onJoin?: (room: LiveKitRoomLike) => void; /** where the audio elements of other players go (default: document.body, when there is a document) */ container?: { appendChild(n: any): unknown } } = {})`
- `async join(r: { url: string; token: string; room: string })`
- `async leave()`

### MemoryHub

*class* · `packages/net/src/transport.ts:38`

```ts
class MemoryHub
```

- `latency: number` — artificial one-way delay in ms (0 = delivered at once)
- `loss: number` — drop this share of unreliable messages
- `join(t: MemoryTransport, id: string)`
- `leave(id: string)`
- `deliver(from: string, msg: NetMessage, reliable: boolean)`

### MemoryTransport

*class* · `packages/net/src/transport.ts:56`

```ts
class MemoryTransport implements Transport
```

- `readonly name: "memory"`
- `constructor(readonly hub: MemoryHub)`
- `connect(selfId: string)`
- `disconnect()`
- `send(msg: NetMessage, reliable: boolean)`
- `onMessage(fn: (m: NetMessage) => void)`
- `receive(m: NetMessage)`

### NetService

*class* · `packages/net/src/net.ts:67`

```ts
class NetService implements Service
```

Replication: publishes the state of the entities this machine owns, creates and moves copies of the others', carries
events (a door opened, an item taken), tells when players come and go. The transport underneath is a setting.
Game code does not know about it: it marks entities (`replicate`), says which events are shared (`replicateEvent`) and
how a copy is made (`spawnable`); with `NetService` left out the same game is a single-player game.
Events on the world: `net:join` { peer }, `net:leave` { peer }, `net:event` { from, name, data }.

- `readonly name: "net"`
- `readonly id: string`
- `readonly tickRate: number`
- `readonly delay: number`
- `constructor(readonly transport: Transport, readonly opts: NetOptions = {})`
- `init(world: World)`
- `isInTransit(e: Entity): boolean` — True while the entity is travelling and is not published.
- `async start(): Promise<void>` — Connect the transport. A transport that cannot connect leaves the game playing alone.
- `get peerIds(): string[]`
- `copies(): Entity[]` — the copies of other players' entities
- `spawnable(kind: string, factory: Factory): this` — How to make the copy of an entity of this kind. It returns the entity (node, look, animator); the service adds the rest.
- `replicate(e: Entity, params: ReplicatedParams): Replicated` — Publish an entity this machine owns.
- `replicateEvent(name: string, rule: EventRule): this` — Share a world event: when it happens here, others get it; when it comes from others, `apply` does it here.
- `send(name: string, data: unknown): void` — A message of your own to everyone else (reliable).
- `on(name: string, fn: (data: any, from: string) => void): () => void` — Listen for a message of that name from other players.
- `fixedUpdate(dt: number)` — Called before the components' fixed update, at the fixed rate.
- `update()` — Called once per rendered frame.
- `dispose()`

### OccupancyReporter

*class* · `packages/net/src/gateways.ts:153`

```ts
class OccupancyReporter implements Service
```

Reports the zone occupancy (hashed names) to the occupancy gateway every few seconds, when it changes.

- `readonly name: "occupancy-reporter"`
- `constructor(readonly opts: { interval?: number } = {})`
- `init(world: World)`
- `update(dt: number)` — Called once per rendered frame.

### PresenceRecordStore

*class* · `packages/net/src/records.ts:27`

```ts
class PresenceRecordStore
```

Named records on the presence server, speaking the original game's wire: `POST {op:"set", name, x, y, z, yaw, model}` to
assert one and `POST {op:"get", name}` to read it; a reply with an `error` field means missing or stale. The server forgets
a record that nobody posts for ~3 s, so "nobody is near" needs no message. `read` is synchronous: it returns the last
answer and starts the next poll in the background (one in flight per name), so a caller that ticks reads the state one
tick late, as the original does. When the server cannot be reached the client's own posts are kept for `stale` seconds so
a player alone, offline, still drives the state. Structurally a `RecordStore` of `@ferret/transform`.

- `offline: boolean` — true after a failed request, false after a successful one
- `constructor(readonly opts: PresenceRecordStoreOptions)`
- `post(name: string, record: NamedRecord)`
- `read(name: string): NamedRecord | null`

### RemoteMotion

*class* · `packages/net/src/net.ts:28`

```ts
class RemoteMotion extends Component
```

Gives a remote copy a motion for the Animator (walk/run by the speed the owner reported).

- `velocity: Vector3`
- `grounded: boolean`
- `motionState()`

### Replicated

*class* · `packages/net/src/net.ts:19`

```ts
class Replicated extends Component<ReplicatedParams>
```

Marks an entity whose state is published (when this machine owns it) or received (when it is somebody else's copy).

- `owner: string` — the owner's id: the player who publishes it
- `remote: boolean`
- `netId: string` — network id

### UnrealRecordStore

*class* · `packages/net/src/records.ts:86`

```ts
class UnrealRecordStore implements FerretRecordStore
```

Adapts a store that speaks the original game's units (Unreal axes: x mirrored, y = Ferret z, z up, centimetres, location = the
capsule's centre; yaw turned by 90 degrees as the level exporter turns it) to Ferret's, so Ferret and Unreal clients share records
such as an NPC's state. `half` is half the capsule's height in metres (default 0.44, the animals' 34 x 88 cm capsule).

- `constructor(private inner: FerretRecordStore, private half = 0.44)`
- `post(name: string, r: NamedRecord)`
- `read(name: string): NamedRecord | null`

### WebSocketTransport

*class* · `packages/net/src/transport.ts:91`

```ts
class WebSocketTransport implements Transport
```

- `readonly name: "websocket"`
- `connected: boolean` — false while the socket is down; messages sent then are dropped (state) or kept for the reconnect (events)
- `constructor(readonly opts: WebSocketOptions)`
- `connect(selfId: string): Promise<void>`
- `disconnect()`
- `send(msg: NetMessage, reliable: boolean)`
- `onMessage(fn: (m: NetMessage) => void)`

### ZoneVoice

*class* · `packages/net/src/gateways.ts:113`

```ts
class ZoneVoice implements Service
```

Zone voice: when the player enters a zone, join the voice room of that zone (through the gateway's token); leaving the zone
leaves the room. No gateway or no adapter: the zones stay silent.

- `readonly name: "zone-voice"`
- `current: string | null`
- `constructor(readonly adapter: VoiceAdapter, readonly opts: { tag?: string } = {})`
- `init(world: World)`
- `get settled(): Promise<void>` — Resolves when the joins and leaves asked for so far are done (tests).
- `enterZone(zone: string)` — Join the voice room of a zone now (also for a player who is already in it when the gateway comes online, e.g. after sign-in).
- `dispose()`

## Functions

### bodyField

*function* · `packages/net/src/bodies.ts:31`

```ts
function bodyField(): ReplicatedField
```

The replicated field for a body: add it to a `Replicated` entity's `fields` (`{ body: bodyField() }`). The owner publishes the
snapshot; a copy turns its node to it and, if the copy has a body, gives it the velocities (so a kinematic copy carries on in
the direction it was going between two updates). Position and the interpolation already come with the entity's state.

### replicateBreaks

*function* · `packages/net/src/bodies.ts:58`

```ts
function replicateBreaks(net: NetService, strengthOf: (world: World) => { breakNow(e: Entity, point?: [number, number, number], seed?: number): void } | undefined): void
```

Share breaks: when a replicated body breaks here, the others break its copy with the same seed, so the pieces and their scatter
are the same (the strength capability takes the seed, `breakNow(entity, point, seed)`). A break of an entity that is not
replicated stays on this machine.

### snapshotBody

*function* · `packages/net/src/bodies.ts:21`

```ts
function snapshotBody(b: BodyLike): BodySnapshot
```

Round and pack a body's state: millimetres, 1e-4 in a unit quaternion, one flag when it sleeps.

## Interfaces

### AccountGateway

*interface* · `packages/net/src/gateways.ts:8`

```ts
interface AccountGateway
```

The game's backends, each behind an interface the game implements (§4.7): account and currency, the voice room of a zone,
zone occupancy, NPC dialogue. All are optional. A missing or unreachable gateway never throws into the game: calls return
their fallback and the gateway is marked down until it works again.

- `user(): Promise<{ id: string; name: string } | null>` — the signed-in player, or null for a guest
- `balance(): Promise<number>`
- `change(amount: number, reason: string): Promise<number>` — earn (positive) or spend (negative); resolves with the new balance, rejects when the backend refuses

### BodySnapshot

*interface* · `packages/net/src/bodies.ts:14`

```ts
interface BodySnapshot
```

What a snapshot of a body holds. A body at rest sends its pose only (`z`): no velocities to apply, no change to send.

- `q: [number, number, number, number]`
- `v?: [number, number, number]`
- `w?: [number, number, number]`
- `z?: 1`

### CamerlengoOptions

*interface* · `packages/net/src/camerlengo.ts:11`

```ts
interface CamerlengoOptions
```

The Camerlengo backend (ReForce API v2): one endpoint, `POST /`, body `{ command, ...params }`, answer `{ ".status": "ok"|"error", ... }`.
Commands the game uses:
  user:verify          {key, path, user, password} -> {session}      sign a player in (`key`: the app's public key, scope user:verify only)
  livekit:userToken    {session, room: "zone:<hash>"} -> {token, url, room}   the voice room of a zone, for that player
  zone:setActive       {key, room, level, cellX, cellY, active}    zone occupancy (a service key: keep it on the game server, never in a page)
`fetch` is injected so tests and non-browser hosts can supply their own.

- `url: string` — the backend, e.g. https://backend.squirrelwisdom.com/
- `appKey?: string` — the app's public key for `user:verify` (a key with that one scope; it ships in the page, so it can do nothing else)
- `fetch?: typeof fetch`

### DataRoom

*interface* · `packages/net/src/transport.ts:237`

```ts
interface DataRoom
```

The part of a LiveKit Room this needs; pass the real one (`livekit-client`) or a fake.

- `publishData(data: Uint8Array, opts: { reliable: boolean }): Promise<void> | void`
- `onData(fn: (data: Uint8Array, from?: string) => void): () => void`

### DialogueGateway

*interface* · `packages/net/src/gateways.ts:26`

```ts
interface DialogueGateway
```

- `say(npc: string, utterance: string, context?: Record<string, unknown>): Promise<string | null>` — what an NPC says to the player; null when it has nothing (the game then uses its local lines)

### EntityState

*interface* · `packages/net/src/transport.ts:7`

```ts
interface EntityState
```

- `id: string` — network id, unique in the game (the owner's id + the entity's key)
- `kind: string` — which factory makes the remote copy
- `p: [number, number, number]` — position
- `r: number` — yaw, radians
- `v?: [number, number, number]` — velocity, for the remote copy's animation
- `g?: boolean`
- `f?: Record<string, unknown>` — custom replicated fields

### FerretRecordStore

*interface* · `packages/net/src/records.ts:76`

```ts
interface FerretRecordStore
```

A store of records in Ferret's units: metres, feet, yaw in degrees (`@ferret/transform`'s `SharedRecord`).

- `post(name: string, record: NamedRecord): void`
- `read(name: string): NamedRecord | null`

### GatewayOptions

*interface* · `packages/net/src/gateways.ts:40`

```ts
interface GatewayOptions
```

- `enabled?: Partial<Record<GatewayName, boolean>>` — each can be switched off whatever is supplied (configuration)
- `timeout?: number` — seconds before a call counts as failed (default 5)
- `cooldown?: number` — seconds a gateway that failed is left alone before the next try (default 15)
- `now?: () => number`

### GatewaySet

*interface* · `packages/net/src/gateways.ts:31`

```ts
interface GatewaySet
```

- `account?: AccountGateway`
- `voice?: VoiceGateway`
- `occupancy?: OccupancyGateway`
- `dialogue?: DialogueGateway`

### HttpPollOptions

*interface* · `packages/net/src/transport.ts:145`

```ts
interface HttpPollOptions
```

- `url: string` — the presence server's URL
- `interval?: number` — seconds between polls (default 0.5)
- `eventRing?: number` — how many recent events a client keeps in its record for others to read (default 20)
- `fetch?: (url: string, init: { method: string; headers: Record<string, string>; body: string }) => Promise<{ ok: boolean; json(): Promise<any> }>`
- `now?: () => number`

### LiveKitRoomLike

*interface* · `packages/net/src/camerlengo.ts:79`

```ts
interface LiveKitRoomLike
```

The part of a `livekit-client` Room that voice needs; pass the real class's instances or a fake.

- `connect(url: string, token: string): Promise<unknown>`
- `disconnect(): unknown`
- `localParticipant?: { setMicrophoneEnabled(on: boolean): Promise<unknown> }`
- `on?(event: string, fn: (...args: any[]) => void): unknown` — livekit-client's event emitter; used to play other players' audio
- `startAudio?(): Promise<unknown>`

### NamedRecord

*interface* · `packages/net/src/records.ts:2`

```ts
interface NamedRecord
```

A shared record about one named thing (an NPC): the fields of the original game's presence-server record (`FStudentData`).

- `location: [number, number, number]`
- `yaw: number` — degrees
- `model: string`

### NetOptions

*interface* · `packages/net/src/net.ts:34`

```ts
interface NetOptions
```

- `id?: string` — this player's id (default: a random one)
- `tickRate?: number` — state messages per second (default 10)
- `delay?: number` — remote copies are shown this far in the past, seconds: room to interpolate between two updates (default 0.15)
- `timeout?: number` — seconds without news before a player counts as gone (default 6)
- `now?: () => number` — replace the clock (tests); seconds

### OccupancyGateway

*interface* · `packages/net/src/gateways.ts:21`

```ts
interface OccupancyGateway
```

- `report(occupancy: Record<string, number>): Promise<void>` — zone hash -> number of players there

### PresenceRecordStoreOptions

*interface* · `packages/net/src/records.ts:9`

```ts
interface PresenceRecordStoreOptions
```

- `url: string` — the presence server's URL (POST, JSON)
- `stale?: number` — seconds a record counts as fresh without a new post or a successful read (default 3, the server's own expiry)
- `fetch?: (url: string, init: { method: string; headers: Record<string, string>; body: string }) => Promise<{ ok: boolean; json(): Promise<any> }>`
- `now?: () => number` — seconds, default `Date.now() / 1000`

### ReplicatedField

*interface* · `packages/net/src/net.ts:4`

```ts
interface ReplicatedField
```

- `get(e: Entity): unknown`
- `set(e: Entity, v: unknown): void`

### ReplicatedParams

*interface* · `packages/net/src/net.ts:9`

```ts
interface ReplicatedParams
```

- `kind: string` — which factory makes this entity's copy on other players' machines
- `key?: string` — a name that is the same on every machine for this entity (default: the entity's id)
- `fields?: Record<string, ReplicatedField>` — extra state to carry: a name -> getter and setter (a worn hat, a shown name, a held item)

### SocketLike

*interface* · `packages/net/src/transport.ts:69`

```ts
interface SocketLike
```

- `readyState: number`
- `send(data: string): void`
- `close(): void`
- `onopen: ((ev?: unknown) => void) | null`
- `onmessage: ((ev: { data: unknown }) => void) | null`
- `onclose: ((ev?: unknown) => void) | null`
- `onerror: ((ev?: unknown) => void) | null`

### Transport

*interface* · `packages/net/src/transport.ts:27`

```ts
interface Transport
```

A way to get messages to the other players. `send` goes to everyone else in the game; the transport never echoes a message
back to its sender. Reliable messages (events) must arrive; unreliable ones (state) may be dropped or replaced by newer ones.

- `readonly name: string`
- `connect(selfId: string): Promise<void> | void`
- `disconnect(): void`
- `send(msg: NetMessage, reliable: boolean): void`
- `onMessage(fn: (msg: NetMessage) => void): () => void`
- `update?(): void` — transports that poll or batch do their work here, called every frame

### VoiceAdapter

*interface* · `packages/net/src/gateways.ts:104`

```ts
interface VoiceAdapter
```

What joins a voice room. The LiveKit client is behind this so the engine does not depend on it.

- `join(room: { url: string; token: string; room: string }): Promise<void>`
- `leave(): Promise<void>`

### VoiceGateway

*interface* · `packages/net/src/gateways.ts:16`

```ts
interface VoiceGateway
```

- `roomFor(zoneHash: string): Promise<{ url: string; token: string; room: string }>` — a token and URL for the LiveKit room of a zone (`zoneHash`, never the zone's name)

### WebSocketOptions

*interface* · `packages/net/src/transport.ts:79`

```ts
interface WebSocketOptions
```

- `url: string` — wss://host/path ; the room is a query parameter the relay understands
- `room?: string`
- `create?: (url: string) => SocketLike` — replace `new WebSocket` (tests)
- `retry?: number` — seconds between reconnect attempts (default 2; 0 = no reconnect)
- `connectTimeout?: number` — seconds to wait for the connection before giving up on this attempt (default 5)

## Types

### EventRule

*type* · `packages/net/src/net.ts:47`

```ts
type EventRule = { /** event payload -> plain data for the wire (entities become ids); returning undefined keeps the event local */ encode?: (payload: any) => unknown; /** wire data -> the effect here. Must not emit the same event again; if it does, the echo is blocked anyway */ apply: (world: World, data: any, from: string) => void; }
```

### GatewayName

*type* · `packages/net/src/gateways.ts:38`

```ts
type GatewayName = keyof GatewaySet
```

### NetMessage

*type* · `packages/net/src/transport.ts:2`

```ts
type NetMessage = | { t: 'state'; from: string; time: number; ents: EntityState[] } | { t: 'event'; from: string; seq: number; name: string; data: unknown } | { t: 'bye'; from: string }
```

What goes over the wire. Transports carry these as JSON; they do not look inside.

