@jgengine/node 0.18.1 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,6 +12,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
12
12
 
13
13
  - `collectAuthoredTriggers` (function) · `import { collectAuthoredTriggers } from "@jgengine/shell/gameKit"`
14
14
 
15
+ ## chase-view-cycle — cycle a vehicle camera between chase, hood and cockpit views at runtime
16
+
17
+ - `nextChaseView` (function) · `import { nextChaseView } from "@jgengine/core/runtime/cameraDirector"`
18
+
15
19
  ## client-side — input prediction with replay reconciliation
16
20
 
17
21
  - `createPredictionBuffer` (function) · `import { createPredictionBuffer } from "@jgengine/core/runtime/prediction"`
@@ -20,6 +24,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
20
24
 
21
25
  - `GameContextModels` (interface) · `import { GameContextModels } from "@jgengine/core/runtime/gameContext"`
22
26
 
27
+ ## command-number — validate finite, bounded numbers and integers at command boundaries
28
+
29
+ - `readInputNumber` (function) · `import { readInputNumber } from "@jgengine/core/runtime/commandInput"`
30
+
31
+ ## command-parse — parse untrusted command input once so every command stage sees typed, bounded values
32
+
33
+ - `defineCommand` (function) · `import { defineCommand } from "@jgengine/core/runtime/commandRunner"`
34
+
23
35
  ## command-quantity — validate bounded whole-item counts at command boundaries
24
36
 
25
37
  - `readQuantity` (function) · `import { readQuantity } from "@jgengine/core/runtime/commandInput"`
@@ -40,6 +52,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
40
52
 
41
53
  - `EditorCatalogDefinition` (interface) · `import { EditorCatalogDefinition } from "@jgengine/shell/gameKit"`
42
54
 
55
+ ## fire-input — bind a key or mouse button to a named action that runs the same-named command with the aim; repeatMs auto-fires while held
56
+
57
+ - `ActionCodesMap` (type) · `import { ActionCodesMap } from "@jgengine/core/authoring"`
58
+
43
59
  ## game-system — declare a composable capability with its own schedule and lifecycle
44
60
 
45
61
  - `SystemDefinition` (interface) · `import { SystemDefinition } from "@jgengine/shell/gameKit"`
@@ -96,6 +112,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
96
112
 
97
113
  - `createInputRecorder` (function) · `import { createInputRecorder } from "@jgengine/core/runtime/inputRecorder"`
98
114
 
115
+ ## local-players — Couch co-op seats: devices hot-join into slots, each with its own user id and input snapshot.
116
+
117
+ - `createLocalPlayers` (function) · `import { createLocalPlayers } from "@jgengine/core/runtime/localPlayers"`
118
+
99
119
  ## loot-table — validate a loot table definition for use with the registry
100
120
 
101
121
  - `lootTable` (function) · `import { lootTable } from "@jgengine/core/authoring"`
@@ -178,6 +198,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
178
198
 
179
199
  - `defineStore` (function) · `import { defineStore } from "@jgengine/shell/gameKit"`
180
200
 
201
+ ## warn-once — emit a dev-only warning a single time per call site
202
+
203
+ - `warnOnce` (function) · `import { warnOnce } from "@jgengine/core/devtools/warnOnce"`
204
+
181
205
  ## world-chunk-key — map world positions to persisted chunk keys at a declared cell size
182
206
 
183
207
  - `chunkKeyOf` (function) · `import { chunkKeyOf } from "@jgengine/core/runtime/worldChunks"`
@@ -193,3 +217,7 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
193
217
  ## world-environment — compose editor/preset-derived outdoor dressing — terrain, weather, water, structures — into a legacy environment feature
194
218
 
195
219
  - `environment` (function) · `import { environment } from "@jgengine/shell/gameKit"`
220
+
221
+ ## world-item-pickup — pick up dropped items: ctx.scene.worldItem nearestInRadius → pickup into the player's inventory
222
+
223
+ - `SceneWorldItemContext` (interface) · `import { SceneWorldItemContext } from "@jgengine/core/runtime/gameContext"`
@@ -6,6 +6,11 @@ authored scene edited in the browser, one system, one store slot, an empty-canva
6
6
  and a win condition. Every new game starts here; add combat, multiplayer, or deeper
7
7
  world features only after this loop plays.
8
8
 
9
+ Every extension follows the [framework-first decision](../SKILL.md#framework-first-decisions):
10
+ compose an installed SDK capability, improve its upstream contract if needed, then
11
+ remove superseded game glue after published adoption. The game owns its rules and art;
12
+ it should not recreate input, model lifecycle, storage or UI accessibility machinery.
13
+
9
14
  ## 1. Scaffold
10
15
 
11
16
  `npx jgengine create <name>` (inside the engine monorepo: `bun run new:game "<id-or-title>"`).
@@ -45,6 +50,9 @@ spatial place. When the game *is* a place, declare only substrate + laws with `w
45
50
  `@jgengine/core/world/place` (e.g. `{ id, ground: { mode: "flat", size: { x: Infinity, z: Infinity } }, physics: { gravity: -24 } }`,
46
51
  or `mode: "board"` for 2D). Never seed, sky, or vegetation in `world` — the engine renders its
47
52
  default sky until you author one in the editor, and foliage is editor scatter over the ground.
53
+ A created 3D game instead ships hills as a terrain sculpt in `editor.scene.json` and a `world.ts`
54
+ that consumes it with `environment({ sculpt })` via `environmentContentFromDocument`; the file
55
+ holds only the ground palette. Reshape the hills in the editor, not in code.
48
56
 
49
57
 
50
58
  ```ts
@@ -77,8 +85,8 @@ direction. Compose the shipped building blocks the moment you need them (`StatBa
77
85
  `Coins`, `InventoryGrid`, `CharacterSheet`, window shells from `jgengine-ui`), then reskin via
78
86
  `HudTheme` tokens. Reaching for those is using the engine correctly, not incomplete work. What
79
87
  the engine does not ship is a whole finished game *face* or genre theme preset — do not leave an
80
- unarranged, unskinned generic dashboard as the product. Hand-roll markup only where no building
81
- block fits (see `jgengine-ui`).
88
+ unarranged, unskinned generic dashboard as the product. Custom markup can use headless
89
+ SDK behavior; a missing reusable behavior is an upstream gap (see `jgengine-ui`).
82
90
 
83
91
  ## 3. Author, play, win
84
92
 
@@ -20,7 +20,7 @@ description: Design authority, transport, replication, sessions, and host persis
20
20
 
21
21
  This skill owns network topology, authority, transports, sessions/presence, replication/projection, reconnect, hosted runners, and persistence adapters. Serializable game state and save semantics stay in `jgengine-gameplay`.
22
22
 
23
- Use [capabilities.md](capabilities.md) for intent-to-import discovery, [api.md](api.md) for signatures, and [reference.md](reference.md) for transport, host, projection, persistence, and deployment recipes.
23
+ Use [capabilities.md](capabilities.md) for intent-to-import discovery, [api.md](api.md) for signatures, and [reference.md](reference.md) for transport, host, projection, persistence, and deployment recipes. See [playable peer worlds](references/peer-worlds.md) for authoritative browser co-op bootstrap and its release barrier.
24
24
 
25
25
  ## Canonical workflow
26
26
 
@@ -55,7 +55,7 @@ Use [capabilities.md](capabilities.md) for intent-to-import discovery, [api.md](
55
55
 
56
56
  Use `createGameServerFunctions({ topology: "shared", runtimes })` for one persistent world. Shared topology defaults to singleton matchmaking. Capacity is not a throughput guarantee; verify the command and subscription read sets under crowd load. Subscribe to `getServerCapacity` for shared membership/status without volatile world revision reads.
57
57
 
58
- Every gameplay command declares `scope(input)`: use `{ players: "actor", chunkKeys: [] }` for player-only work and `chunkKeysAround(position, 1)` for spatial work. The omitted-command default is actor-only with no chunks; an explicit `{}` requests the whole world and needs a justified bounded workload. Hosts use `helpers.runCommand` and its `beforePersist` callback to pair game writes with membership and revision checks in one transaction. Never copy that check/apply/persist loop into a game.
58
+ Every gameplay command declares `scope(input)`: use `{ players: "actor", chunkKeys: [] }` for player-only work and `chunkKeysAround(position, 1)` for spatial work. The omitted-command default is actor-only with no chunks; an explicit `{}` requests the whole world and needs a justified bounded workload. Hosts use `helpers.runCommand` and its `beforePersist` callback to pair game writes with membership and revision checks in one transaction. Never copy that check/apply/persist loop into a game. Declare commands with `defineCommand({ parse, scope, validate, apply })` and build `parse` from the `readInput*` readers in `@jgengine/core/runtime/commandInput` (finite, bounded numbers and points, capped strings and arrays): the runner refuses malformed input before `scope`, `validate` or `apply` run, so those stages never re-parse or trust raw values. Treat every field of a command's `input` as client-controlled: a command whose input carries facts only the host may decide (unlock state, prices, multipliers, whether a target exists) declares `access: "server"`, so the public `runCommand` and WebSocket clients are refused and only a host mutation calling `helpers.runCommand` (or `host.runCommand({ trusted: true })`) runs it. Read time from the `nowMs` argument, never from `input`.
59
59
 
60
60
  Every presence query is neighborhood-scoped. Pass `PresenceSession.viewerChunkKey` through `createConvexPresenceTransport`. Hosts with custom authentication, bot ownership, or spawn policy use `createWorldPresenceStore` from `@jgengine/convex/worldPresence`: resolve trusted identities and access first, then call `ensure`, `sync`, `revoke`, `active`, `nearby`, and `reap`. Store rows in `jgPoses`, never a game-owned presence table. Optional snapshot callbacks persist game-owned last-position fields at the store's cadence. Bot identity may differ from its authorized owner; preserve that mapping explicitly. Resident rows implement `PresenceResidentRow.actorExternalId` and `ownerActorId` exactly.
61
61
 
@@ -67,6 +67,6 @@ A cron never collects a whole table. Use presence-gated `forEachOnlinePlayer` wi
67
67
 
68
68
  Pass runtimes to `jgengineCronSpecs` so no tick cron registers when no runtime declares `onTick`; set per-spec `intervalSeconds` deliberately. Retention sweeps and one-shot backfills are bounded, report `remaining`, and stop at convergence. Delete completed migration crons instead of leaving hourly table scans installed forever.
69
69
 
70
- Join failures return typed `full`, `closed`, or `unauthorized` outcomes. Surface them through a retry gate; returning a rejection without visible UI is unfinished. Browser adapters leave sessions on `pagehide`.
70
+ Join failures return typed `full`, `closed`, or `unauthorized` outcomes. Surface them through a retry gate; returning a rejection without visible UI is unfinished. Browser adapters leave sessions on `pagehide`. Membership is per client session: pass a fresh `sessionId` (`createTransportSessionId`) to each `joinServer` and its matching `leaveServer`, so a closed tab or a disposed mount never evicts a live one. A host mutation acting for a user who may have no live session calls `helpers.ensureJoined` before `helpers.runCommand`.
71
71
 
72
72
  For the explicit shared-builder composition, `jgengine create "World Name" --shape shared-world-builder` generates the connected Convex shell, runtime scope declarations, claim/accrual examples, chat, indexed presence, and an online batch pipeline. The generated README owns setup; replace the demonstration economy and author buildable world content in the editor.
@@ -6,8 +6,8 @@
6
6
 
7
7
  - `ConvexBackend` (type): type ConvexBackend<TPresenceRow = unknown, TPresenceLocation = unknown, TGameId extends string = string> = LiveGameBackend<TPresenceRow, TPresenceLocation, TGameId> & { leaderboard: ConvexLeaderboardReads; } — ⚠ undocumented
8
8
  - `ConvexBackendOptions` (type): type ConvexBackendOptions<TRawPresenceRow extends { actorExternalId: string }, TPresenceRow> = { client: ConvexReactClient; gameId: string; userId: string; api?: ConvexGameApi; poseTuning?: PoseSyncTuning; presence?: { functions: ConvexPresenceFunctions; mapRow: (row: TRawPresenceRow) => TPresenceRo… — ⚠ undocumented
9
- - `ConvexChatFunctions` (interface): interface ConvexChatFunctions — ⚠ undocumented
10
- - `ConvexGameApi` (type): type ConvexGameApi = { runtime: { joinServer: FunctionReference< "mutation", "public", { gameId: string; serverId?: string; mode?: string; visibility?: "public" | "private"; joinCode?: string; externalId?: string; }, JoinServerOutcome >; leaveServer: FunctionReference<"mutation", "public", { serverI… — ⚠ undocumented
9
+ - `ConvexChatFunctions` (interface): interface ConvexChatFunctions { messages: FunctionReference<"query">; sendMessage: FunctionReference<"mutation"> } — ⚠ undocumented · used by `createConvexChatTransport`: Wires a game's Convex chat functions into the engine's ChatTransport contract: one live query per subscribed channel (the channel's recent h…
10
+ - `ConvexGameApi` (type): type ConvexGameApi = { runtime: { joinServer: FunctionReference< "mutation", "public", { gameId: string; serverId?: string; mode?: string; visibility?: "public" | "private"; joinCode?: string; externalId?: string; sessionId?: string; }, JoinServerOutcome >; leaveServer: FunctionReference<"mutation",… — ⚠ undocumented
11
11
  - `ConvexGameClient` (type): type ConvexGameClient = Pick<ConvexReactClient, "mutation" | "watchQuery"> — Structural client seam avoids requiring the engine and game to share a class instance type.
12
12
  - `ConvexGameTransportConfig` (type): type ConvexGameTransportConfig = { gameId: string; userId?: string; } — ⚠ undocumented
13
13
  - `ConvexLeaderboardReads` (type): type ConvexLeaderboardReads = ReturnType<typeof createConvexLeaderboardReads> — ⚠ undocumented
@@ -16,6 +16,7 @@
16
16
  - `ConvexSaveFunctions` (interface): interface ConvexSaveFunctions — The three Convex functions a save backend calls — a `query` that returns the stored string (or `null`) and two `mutation`s. Point them at your app's own module, or lean on the default `saves.*` convention.
17
17
  - `DEFAULT_CONVEX_POSE_RULES` (const): const DEFAULT_CONVEX_POSE_RULES: PoseSyncRules — ⚠ undocumented
18
18
  - `DEFAULT_MAX_SERVERS_PER_TICK` (const): const DEFAULT_MAX_SERVERS_PER_TICK: 32 — How many running servers one `tickActiveServers` transaction may hydrate, tick, and persist.
19
+ - `EnsureJoinedOutcome` (type): type EnsureJoinedOutcome = | { ok: true; serverId: GenericId<"jgGameServers">; isNew: boolean } | { ok: false; reason: "full" | "closed" | "unauthorized" } — Outcome of a join: the server joined and whether the player's profile was created, or why it was refused.
19
20
  - `GameServerHelpers` (type): type GameServerHelpers = { ensureServer: (ctx: JGMutationCtx, gameId: string) => Promise<ServerDoc>; loadSnapshot: ( ctx: JGMutationCtx, serverId: string, scope?: LoadSnapshotScope, ) => Promise<LoadedServerSnapshot | null>; applyCommand: (args: { gameId: string; loadedRevision: number; currentRevis… — The plain-function half of {@link createGameServerFunctions}, bound to the same runtime registry and auth mode as its mutations. Reach for it when a host mutation must pair snapshot work with its own table writes in one transaction, which a pre-registered mutation cannot do.
20
21
  - `HostedGameConfig` (interface): interface HostedGameConfig — One game the hosted path can serve, bound per `gameId`: its definition plus content lookup.
21
22
  - `HostedWorldInvocation` (interface): interface HostedWorldInvocation<T> — Everything one stateless invocation reconstructs a hosted world from: the persisted record via `store`, plus the member roster and held inputs the snapshot doesn't carry, and the op to apply to the fresh session.
@@ -31,6 +32,8 @@
31
32
  - `LoadSnapshotScope` (type): type LoadSnapshotScope = CommandScope — How much of a server to hydrate. Both fields default to "everything", which is a document read per member plus one per chunk — the cost that makes a large shared world unaffordable per mutation. Narrow them when the caller knows what it will touch: a command that only moves the actor's own state needs `players: [actorUserId]`, and a spatial edit needs only the chunk keys it writes (`chunkKeysInRadius` from `@jgengine/core/runtime/worldChunks`).
32
33
  - `LoadedServerSnapshot` (type): type LoadedServerSnapshot = { server: ServerDoc; runtime: GameRuntime; snapshot: GameRuntimeSnapshot; } — A server resolved for runtime work: its row, its registered runtime, and its hydrated snapshot.
33
34
  - `MAX_CHUNKS_PER_QUERY` (const): const MAX_CHUNKS_PER_QUERY: 64 — How many chunk rows one `getChunks` call may return.
35
+ - `MAX_MEMBER_SESSIONS` (const): const MAX_MEMBER_SESSIONS: 16 — Most live client sessions one shared-topology membership remembers; the oldest drop first.
36
+ - `MAX_SESSION_ID_LENGTH` (const): const MAX_SESSION_ID_LENGTH: 128 — Longest accepted client `sessionId`.
34
37
  - `MatchmakingMode` (type): type MatchmakingMode = "auto" | "singleton" — How auto-match behaves when no `serverId` is supplied.
35
38
  - `OnlinePlayer` (type): type OnlinePlayer = { serverId: string; userId: string; homeGameId?: string } — One active player delivered to an online-system batch.
36
39
  - `PresenceListRow` (type): type PresenceListRow = { userId: string; sessionId?: string; kind?: string; label?: string; position: { x: number; y: number; z: number }; rotationY: number; rotationPitch: number; lastSeenAt: number; } — One member's pose as `list` reports it.
@@ -67,7 +70,7 @@
67
70
 
68
71
  ## @jgengine/convex/convexChatTransport
69
72
 
70
- - `ConvexChatFunctions` (interface): interface ConvexChatFunctions — ⚠ undocumented
73
+ - `ConvexChatFunctions` (interface): interface ConvexChatFunctions { messages: FunctionReference<"query">; sendMessage: FunctionReference<"mutation"> } — ⚠ undocumented · used by `createConvexChatTransport`: Wires a game's Convex chat functions into the engine's ChatTransport contract: one live query per subscribed channel (the channel's recent h…
71
74
  - `createConvexChatTransport` (function): function createConvexChatTransport<TRawRow = ChatMessage>(functions: ConvexChatFunctions, options?: { mapRow?: (row: TRawRow) => ChatMessage; extraArgs?: Record<string, unknown>; }): ChatTransport — Wires a game's Convex chat functions into the engine's ChatTransport contract: one live query per subscribed channel (the channel's recent history, newest last) and one send mutation. mapRow converts backend rows into ChatMessage (defaults to structural passthrough); extraArgs is spread into both calls for games that scope chat by server or world.
72
75
 
73
76
  ## @jgengine/convex/convexPresenceTransport
@@ -91,7 +94,7 @@
91
94
 
92
95
  ## @jgengine/convex/createConvexGameTransport
93
96
 
94
- - `ConvexGameApi` (type): type ConvexGameApi = { runtime: { joinServer: FunctionReference< "mutation", "public", { gameId: string; serverId?: string; mode?: string; visibility?: "public" | "private"; joinCode?: string; externalId?: string; }, JoinServerOutcome >; leaveServer: FunctionReference<"mutation", "public", { serverI… — ⚠ undocumented
97
+ - `ConvexGameApi` (type): type ConvexGameApi = { runtime: { joinServer: FunctionReference< "mutation", "public", { gameId: string; serverId?: string; mode?: string; visibility?: "public" | "private"; joinCode?: string; externalId?: string; sessionId?: string; }, JoinServerOutcome >; leaveServer: FunctionReference<"mutation",… — ⚠ undocumented
95
98
  - `ConvexGameClient` (type): type ConvexGameClient = Pick<ConvexReactClient, "mutation" | "watchQuery"> — Structural client seam avoids requiring the engine and game to share a class instance type.
96
99
  - `ConvexGameTransportConfig` (type): type ConvexGameTransportConfig = { gameId: string; userId?: string; } — ⚠ undocumented
97
100
  - `createConvexChatSync` (function): function createConvexChatSync(client: ConvexGameClient, api: ConvexGameApi, config: ConvexGameTransportConfig, serverId: string): ChatSync — ⚠ undocumented
@@ -123,6 +126,7 @@
123
126
 
124
127
  - `DEFAULT_CONVEX_POSE_RULES` (const): const DEFAULT_CONVEX_POSE_RULES: PoseSyncRules — ⚠ undocumented
125
128
  - `DEFAULT_MAX_SERVERS_PER_TICK` (const): const DEFAULT_MAX_SERVERS_PER_TICK: 32 — How many running servers one `tickActiveServers` transaction may hydrate, tick, and persist.
129
+ - `EnsureJoinedOutcome` (type): type EnsureJoinedOutcome = | { ok: true; serverId: GenericId<"jgGameServers">; isNew: boolean } | { ok: false; reason: "full" | "closed" | "unauthorized" } — Outcome of a join: the server joined and whether the player's profile was created, or why it was refused.
126
130
  - `GameServerHelpers` (type): type GameServerHelpers = { ensureServer: (ctx: JGMutationCtx, gameId: string) => Promise<ServerDoc>; loadSnapshot: ( ctx: JGMutationCtx, serverId: string, scope?: LoadSnapshotScope, ) => Promise<LoadedServerSnapshot | null>; applyCommand: (args: { gameId: string; loadedRevision: number; currentRevis… — The plain-function half of {@link createGameServerFunctions}, bound to the same runtime registry and auth mode as its mutations. Reach for it when a host mutation must pair snapshot work with its own table writes in one transaction, which a pre-registered mutation cannot do.
127
131
  - `JGDataModel` (type): type JGDataModel = DataModelFromSchemaDefinition<typeof schemaForTypes> — Data model of the {@link jgengineTables} schema — the shape a host's own Convex ctx must satisfy to call the exported persistence helpers.
128
132
  - `JGMutationCtx` (type): type JGMutationCtx = GenericMutationCtx<JGDataModel> — Mutation ctx accepted by {@link loadServerSnapshot} / {@link persistServerSnapshot}.
@@ -134,6 +138,8 @@
134
138
  - `LoadSnapshotScope` (type): type LoadSnapshotScope = CommandScope — How much of a server to hydrate. Both fields default to "everything", which is a document read per member plus one per chunk — the cost that makes a large shared world unaffordable per mutation. Narrow them when the caller knows what it will touch: a command that only moves the actor's own state needs `players: [actorUserId]`, and a spatial edit needs only the chunk keys it writes (`chunkKeysInRadius` from `@jgengine/core/runtime/worldChunks`).
135
139
  - `LoadedServerSnapshot` (type): type LoadedServerSnapshot = { server: ServerDoc; runtime: GameRuntime; snapshot: GameRuntimeSnapshot; } — A server resolved for runtime work: its row, its registered runtime, and its hydrated snapshot.
136
140
  - `MAX_CHUNKS_PER_QUERY` (const): const MAX_CHUNKS_PER_QUERY: 64 — How many chunk rows one `getChunks` call may return.
141
+ - `MAX_MEMBER_SESSIONS` (const): const MAX_MEMBER_SESSIONS: 16 — Most live client sessions one shared-topology membership remembers; the oldest drop first.
142
+ - `MAX_SESSION_ID_LENGTH` (const): const MAX_SESSION_ID_LENGTH: 128 — Longest accepted client `sessionId`.
137
143
  - `MatchmakingMode` (type): type MatchmakingMode = "auto" | "singleton" — How auto-match behaves when no `serverId` is supplied.
138
144
  - `OnlinePlayer` (type): type OnlinePlayer = { serverId: string; userId: string; homeGameId?: string } — One active player delivered to an online-system batch.
139
145
  - `PresenceListRow` (type): type PresenceListRow = { userId: string; sessionId?: string; kind?: string; label?: string; position: { x: number; y: number; z: number }; rotationY: number; rotationPitch: number; lastSeenAt: number; } — One member's pose as `list` reports it.
@@ -166,32 +172,32 @@
166
172
 
167
173
  ## @jgengine/core/multiplayer
168
174
 
169
- - `AuthSession` (interface): interface AuthSession — ⚠ undocumented
170
- - `BoardSnapshot` (interface): interface BoardSnapshot — ⚠ undocumented
171
- - `ChatActions` (interface): interface ChatActions — ⚠ undocumented
172
- - `ChatSendOutcome` (interface): interface ChatSendOutcome — ⚠ undocumented
175
+ - `AuthSession` (interface): interface AuthSession { userId: string; displayName?: string; avatarUrl?: string; email?: string; isNew?: boolean } — ⚠ undocumented
176
+ - `BoardSnapshot` (interface): interface BoardSnapshot { ownerId: string; units: readonly SnapshotUnit[]; stats: Readonly<Record<string, number>>; seed: number; capturedAt: number } — ⚠ undocumented
177
+ - `ChatActions` (interface): interface ChatActions { sendMessage(args: ChatSendArgs): Promise<ChatSendOutcome> } — ⚠ undocumented
178
+ - `ChatSendOutcome` (interface): interface ChatSendOutcome { ok: boolean; reason?: string } — ⚠ undocumented · used by `sendChatMessage` (@jgengine/convex): Write validated chat in a host transaction after the caller resolves its actor and world.
173
179
  - `ChatSync` (interface): interface ChatSync — Callback seam for backends that cannot host React hooks (e.g. the ws client): subscribe delivers the channel's recent history on every change; send resolves with the host's verdict.
174
180
  - `ChatTransport` (interface): interface ChatTransport — Backend seam for remote text chat, mirroring PresenceTransport: the use* members are called as React hooks by consumers, so a mounted transport must never change identity — remount the subtree to switch backends. useMessages returns undefined while the subscription is loading and the channel's recent history once live.
175
- - `EnsurePresenceResult` (interface): interface EnsurePresenceResult — ⚠ undocumented
181
+ - `EnsurePresenceResult` (interface): interface EnsurePresenceResult { presenceId: string; position: PresencePosition; rotationY: number } — ⚠ undocumented
176
182
  - `FeedWriteGate` (type): type FeedWriteGate = { allowedActions: readonly string[]; } — ⚠ undocumented
177
- - `MatchFilter` (interface): interface MatchFilter — ⚠ undocumented
178
- - `PlayerIdentity` (interface): interface PlayerIdentity — ⚠ undocumented
179
- - `PlayerPose` (interface): interface PlayerPose — ⚠ undocumented
180
- - `PoseSyncRules` (interface): interface PoseSyncRules — ⚠ undocumented
181
- - `PoseSyncTuning` (interface): interface PoseSyncTuning — ⚠ undocumented
182
- - `PresenceActions` (interface): interface PresenceActions<TGameId extends string = string> — ⚠ undocumented
183
- - `PresenceFeeds` (interface): interface PresenceFeeds<TRow, TLocation> — ⚠ undocumented
184
- - `PresencePoseState` (interface): interface PresencePoseState — ⚠ undocumented
185
- - `PresenceSession` (interface): interface PresenceSession<TGameId extends string = string> — ⚠ undocumented
183
+ - `MatchFilter` (interface): interface MatchFilter { mode?: string; tags?: readonly string[]; query?: string; notFull?: boolean; status?: readonly SessionStatus[]; includePrivate?: boolean } — ⚠ undocumented · used by `useWorldBrowser` (@jgengine/react): Polls a host-supplied session fetcher (e.g.
184
+ - `PlayerIdentity` (interface): interface PlayerIdentity { userId: string; isNew: boolean } — ⚠ undocumented
185
+ - `PlayerPose` (interface): interface PlayerPose { x: number; y: number; z: number; rotationY: number; rotationPitch: number; appearance?: Record<string, string> } — ⚠ undocumented
186
+ - `PoseSyncRules` (interface): interface PoseSyncRules { maxSpeed: number; minIntervalMs?: number; maxVerticalOffset: number; floorY?: number; minElapsedSec: number; maxElapsedSec: number; keepAliveRefreshMs: number } — ⚠ undocumented · used by `DEFAULT_POSE_SYNC_RULES` (@jgengine/core/multiplayer/presenceModel): Canonical client-authoritative pose-sync tuning shared by every host transport (WS, Convex).
187
+ - `PoseSyncTuning` (interface): interface PoseSyncTuning { minIntervalMs: number; heartbeatMs: number; positionEpsilon: number; verticalEpsilon: number; rotationEpsilon: number } — ⚠ undocumented · used by `DEFAULT_POSE_SYNC_TUNING` (@jgengine/core/multiplayer/poseSyncGate): Default ten-hertz client pose gate with a five-second heartbeat.
188
+ - `PresenceActions` (interface): interface PresenceActions<TGameId extends string = string> { ensurePresence(args: EnsurePresenceArgs<TGameId>): Promise<EnsurePresenceResult | null>; leavePresence(args: LeavePresenceArgs<TGameId>): Promise<{left: boolean}>; syncPose(pose: PresencePose): void } — ⚠ undocumented
189
+ - `PresenceFeeds` (interface): interface PresenceFeeds<TRow, TLocation> { myPresenceLocation: TLocation | null | undefined; onlinePresences: readonly TRow[] | undefined; dormantPresences: readonly TRow[] | undefined } — ⚠ undocumented
190
+ - `PresencePoseState` (interface): interface PresencePoseState { position: {x: number; y: number; z: number}; rotationY: number; rotationPitch?: number; lastSeenAtMs?: number; appearance?: Record<string, string> } — ⚠ undocumented
191
+ - `PresenceSession` (interface): interface PresenceSession<TGameId extends string = string> { homeGameId: TGameId; externalId: string; viewerChunkKey?: string } — ⚠ undocumented
186
192
  - `PresenceTransport` (interface): interface PresenceTransport<TRow, TLocation, TGameId extends string = string> — Backend seam for multiplayer presence. Feeds are reactive data and change identity whenever any player's pose updates; actions MUST be identity-stable for the lifetime of a mounted session so join/leave lifecycle effects can depend on them without re-running per pose tick. The use* members are called as React hooks by consumers, so a mounted transport must never change identity — remount the subtree to switch backends.
187
193
  - `PushToTalkMode` (type): type PushToTalkMode = "hold" | "toggle" | "openMic" — ⚠ undocumented
188
194
  - `PushToTalkStatus` (type): type PushToTalkStatus = "idle" | "keyed" | "open" — ⚠ undocumented
189
195
  - `ReplicationPolicy` (interface): interface ReplicationPolicy — Host-side interest/privacy policy — how the authoritative world projects to each viewer over the wire. Unset (the default) means every client receives the whole world, exactly as before. Enabling a field changes only what each client *sees*, never how the host simulates: the game plays identically. The core replication modules read this to attach a {@link SnapshotModule.project} without the engine growing a per-feature branch.
190
- - `SessionListing` (interface): interface SessionListing — ⚠ undocumented
191
- - `SessionVisibility` (type): type SessionVisibility = "public" | "private" — ⚠ undocumented
192
- - `Vec3` (interface): interface Vec3 — ⚠ undocumented
193
- - `VoiceParticipant` (interface): interface VoiceParticipant — ⚠ undocumented
194
- - `VoiceRoute` (interface): interface VoiceRoute — ⚠ undocumented
196
+ - `SessionListing` (interface): interface SessionListing { serverId: string; gameId: string; status: SessionStatus; visibility: SessionVisibility; memberCount: number; slotsPerServer: number; label?: string; mode?: string; joinCode?: string; tags?: readonly string[]; updatedAt: number } — ⚠ undocumented · used by `useWorldBrowser` (@jgengine/react): Polls a host-supplied session fetcher (e.g.
197
+ - `SessionVisibility` (type): type SessionVisibility = "public" | "private" — ⚠ undocumented · used by `isAutoJoinCandidate` (@jgengine/core/runtime/hostPolicy): Auto-match candidate when no `serverId` is supplied: already a member, or a public room with free capacity.
198
+ - `Vec3` (interface): interface Vec3 { x: number; y: number; z: number } — ⚠ undocumented · used by `coverPoints` (@jgengine/core/ai/tacticalQueries): Returns boundary locations that are hidden from a threat.
199
+ - `VoiceParticipant` (interface): interface VoiceParticipant { userId: string; streamId?: string } — ⚠ undocumented
200
+ - `VoiceRoute` (interface): interface VoiceRoute { fromUserId: string; channelId: string; gain: number } — ⚠ undocumented
195
201
  - `VoiceTransport` (interface): interface VoiceTransport — Signaling seam for voice: who is in a channel and which media stream descriptor they published. The media plane (WebRTC, SFU, or anything else that moves audio bytes) stays behind this seam, host-supplied — the engine never touches it. subscribers delivers the channel roster on every change, starting with the current roster.
196
202
  - `browseSessions` (function): function browseSessions(listings: readonly SessionListing[], filter: MatchFilter = {}, options: BrowseOptions = {}): SessionListing[] — ⚠ undocumented
197
203
  - `createFeedWriteGate` (function): function createFeedWriteGate(allowedActions: readonly string[] = []): FeedWriteGate — ⚠ undocumented
@@ -209,22 +215,22 @@
209
215
 
210
216
  ## @jgengine/core/multiplayer/chatContract
211
217
 
212
- - `ChatActions` (interface): interface ChatActions — ⚠ undocumented
213
- - `ChatSendArgs` (interface): interface ChatSendArgs — ⚠ undocumented
214
- - `ChatSendOutcome` (interface): interface ChatSendOutcome — ⚠ undocumented
218
+ - `ChatActions` (interface): interface ChatActions { sendMessage(args: ChatSendArgs): Promise<ChatSendOutcome> } — ⚠ undocumented
219
+ - `ChatSendArgs` (interface): interface ChatSendArgs { channelId: string; body: string } — ⚠ undocumented
220
+ - `ChatSendOutcome` (interface): interface ChatSendOutcome { ok: boolean; reason?: string } — ⚠ undocumented · used by `sendChatMessage` (@jgengine/convex): Write validated chat in a host transaction after the caller resolves its actor and world.
215
221
  - `ChatSync` (interface): interface ChatSync — Callback seam for backends that cannot host React hooks (e.g. the ws client): subscribe delivers the channel's recent history on every change; send resolves with the host's verdict.
216
222
  - `ChatTransport` (interface): interface ChatTransport — Backend seam for remote text chat, mirroring PresenceTransport: the use* members are called as React hooks by consumers, so a mounted transport must never change identity — remount the subtree to switch backends. useMessages returns undefined while the subscription is loading and the channel's recent history once live.
217
223
  - `createLocalChatTransport` (function): function createLocalChatTransport(options?: { userId?: string; historyLimit?: number; now?: () => number; }): { transport: ChatTransport; sync: ChatSync; actions: ChatActions; } — ⚠ undocumented
218
224
 
219
225
  ## @jgengine/core/multiplayer/combatSnapshot
220
226
 
221
- - `BoardSnapshot` (interface): interface BoardSnapshot — ⚠ undocumented
222
- - `CombatRules` (interface): interface CombatRules — ⚠ undocumented
227
+ - `BoardSnapshot` (interface): interface BoardSnapshot { ownerId: string; units: readonly SnapshotUnit[]; stats: Readonly<Record<string, number>>; seed: number; capturedAt: number } — ⚠ undocumented
228
+ - `CombatRules` (interface): interface CombatRules { attackStat: string; healthStat: string; maxRounds?: number; critChance?: number; critMultiplier?: number } — ⚠ undocumented
223
229
  - `CombatSide` (type): type CombatSide = "a" | "b" | "draw" — ⚠ undocumented
224
- - `ReplayBlow` (interface): interface ReplayBlow — ⚠ undocumented
225
- - `ReplayResult` (interface): interface ReplayResult — ⚠ undocumented
226
- - `SerializeBoardArgs` (interface): interface SerializeBoardArgs — ⚠ undocumented
227
- - `SnapshotUnit` (interface): interface SnapshotUnit — ⚠ undocumented
230
+ - `ReplayBlow` (interface): interface ReplayBlow { round: number; attacker: CombatSide; attackerUnit: string; defenderUnit: string; damage: number; crit: boolean; defenderHealthAfter: number } — ⚠ undocumented
231
+ - `ReplayResult` (interface): interface ReplayResult { winner: CombatSide; rounds: number; blows: readonly ReplayBlow[]; survivorsA: readonly string[]; survivorsB: readonly string[] } — ⚠ undocumented
232
+ - `SerializeBoardArgs` (interface): interface SerializeBoardArgs { ownerId: string; units: readonly SnapshotUnit[]; stats?: Readonly<Record<string, number>>; seed?: number; capturedAt?: number } — ⚠ undocumented
233
+ - `SnapshotUnit` (interface): interface SnapshotUnit { id: string; stats: Readonly<Record<string, number>> } — ⚠ undocumented
228
234
 
229
235
  ## @jgengine/core/multiplayer/feedWriteGate
230
236
 
@@ -234,27 +240,27 @@
234
240
 
235
241
  ## @jgengine/core/multiplayer/identity
236
242
 
237
- - `AuthSession` (interface): interface AuthSession — ⚠ undocumented
238
- - `PlayerIdentity` (interface): interface PlayerIdentity — ⚠ undocumented
243
+ - `AuthSession` (interface): interface AuthSession { userId: string; displayName?: string; avatarUrl?: string; email?: string; isNew?: boolean } — ⚠ undocumented
244
+ - `PlayerIdentity` (interface): interface PlayerIdentity { userId: string; isNew: boolean } — ⚠ undocumented
239
245
  - `resolveGuestSession` (function): function resolveGuestSession(seed?: string): AuthSession — ⚠ undocumented
240
246
  - `sessionPlayer` (function): function sessionPlayer(session: AuthSession): PlayerIdentity — ⚠ undocumented
241
247
 
242
248
  ## @jgengine/core/multiplayer/lagCompensation
243
249
 
244
- - `HitscanHit` (interface): interface HitscanHit — ⚠ undocumented
245
- - `HitscanRay` (interface): interface HitscanRay — ⚠ undocumented
246
- - `HitscanTarget` (interface): interface HitscanTarget — ⚠ undocumented
247
- - `PositionHistoryConfig` (interface): interface PositionHistoryConfig — ⚠ undocumented
248
- - `PositionSample` (interface): interface PositionSample extends Vec3 — ⚠ undocumented
249
- - `Vec3` (interface): interface Vec3 — ⚠ undocumented
250
+ - `HitscanHit` (interface): interface HitscanHit { entityId: string; distance: number; point: Vec3 } — ⚠ undocumented
251
+ - `HitscanRay` (interface): interface HitscanRay { origin: Vec3; direction: Vec3; maxDistance?: number } — ⚠ undocumented
252
+ - `HitscanTarget` (interface): interface HitscanTarget { entityId: string; radius: number } — ⚠ undocumented
253
+ - `PositionHistoryConfig` (interface): interface PositionHistoryConfig { historyMs: number; maxSamples?: number } — ⚠ undocumented
254
+ - `PositionSample` (interface): interface PositionSample extends Vec3 { t: number } — ⚠ undocumented
255
+ - `Vec3` (interface): interface Vec3 { x: number; y: number; z: number } — ⚠ undocumented · used by `coverPoints` (@jgengine/core/ai/tacticalQueries): Returns boundary locations that are hidden from a threat.
250
256
 
251
257
  ## @jgengine/core/multiplayer/matchmaking
252
258
 
253
- - `BrowseOptions` (interface): interface BrowseOptions — ⚠ undocumented
254
- - `MatchFilter` (interface): interface MatchFilter — ⚠ undocumented
255
- - `SessionListing` (interface): interface SessionListing — ⚠ undocumented
259
+ - `BrowseOptions` (interface): interface BrowseOptions { limit?: number } — ⚠ undocumented
260
+ - `MatchFilter` (interface): interface MatchFilter { mode?: string; tags?: readonly string[]; query?: string; notFull?: boolean; status?: readonly SessionStatus[]; includePrivate?: boolean } — ⚠ undocumented · used by `useWorldBrowser` (@jgengine/react): Polls a host-supplied session fetcher (e.g.
261
+ - `SessionListing` (interface): interface SessionListing { serverId: string; gameId: string; status: SessionStatus; visibility: SessionVisibility; memberCount: number; slotsPerServer: number; label?: string; mode?: string; joinCode?: string; tags?: readonly string[]; updatedAt: number } — ⚠ undocumented · used by `useWorldBrowser` (@jgengine/react): Polls a host-supplied session fetcher (e.g.
256
262
  - `SessionStatus` (type): type SessionStatus = "open" | "running" | "closed" — ⚠ undocumented
257
- - `SessionVisibility` (type): type SessionVisibility = "public" | "private" — ⚠ undocumented
263
+ - `SessionVisibility` (type): type SessionVisibility = "public" | "private" — ⚠ undocumented · used by `isAutoJoinCandidate` (@jgengine/core/runtime/hostPolicy): Auto-match candidate when no `serverId` is supplied: already a member, or a public room with free capacity.
258
264
  - `browseSessions` (function): function browseSessions(listings: readonly SessionListing[], filter: MatchFilter = {}, options: BrowseOptions = {}): SessionListing[] — ⚠ undocumented
259
265
  - `findByJoinCode` (function): function findByJoinCode(listings: readonly SessionListing[], code: string): SessionListing | null — ⚠ undocumented
260
266
  - `generateJoinCode` (function): function generateJoinCode(random: () => number, length = 6): string — ⚠ undocumented
@@ -266,49 +272,49 @@
266
272
  ## @jgengine/core/multiplayer/poseSyncGate
267
273
 
268
274
  - `DEFAULT_POSE_SYNC_TUNING` (const): const DEFAULT_POSE_SYNC_TUNING: PoseSyncTuning — Default ten-hertz client pose gate with a five-second heartbeat.
269
- - `PlayerPose` (interface): interface PlayerPose — ⚠ undocumented
270
- - `PoseSyncGate` (interface): interface PoseSyncGate — ⚠ undocumented
271
- - `PoseSyncTuning` (interface): interface PoseSyncTuning — ⚠ undocumented
275
+ - `PlayerPose` (interface): interface PlayerPose { x: number; y: number; z: number; rotationY: number; rotationPitch: number; appearance?: Record<string, string> } — ⚠ undocumented
276
+ - `PoseSyncGate` (interface): interface PoseSyncGate { evaluate(pose: PlayerPose, nowMs: number): boolean } — ⚠ undocumented
277
+ - `PoseSyncTuning` (interface): interface PoseSyncTuning { minIntervalMs: number; heartbeatMs: number; positionEpsilon: number; verticalEpsilon: number; rotationEpsilon: number } — ⚠ undocumented · used by `DEFAULT_POSE_SYNC_TUNING`: Default ten-hertz client pose gate with a five-second heartbeat.
272
278
  - `createPoseSyncGate` (function): function createPoseSyncGate(tuning: PoseSyncTuning = DEFAULT_POSE_SYNC_TUNING): PoseSyncGate — ⚠ undocumented
273
279
 
274
280
  ## @jgengine/core/multiplayer/presenceContract
275
281
 
276
- - `EnsurePresenceArgs` (interface): interface EnsurePresenceArgs<TGameId extends string = string> — ⚠ undocumented
277
- - `EnsurePresenceResult` (interface): interface EnsurePresenceResult — ⚠ undocumented
278
- - `LeavePresenceArgs` (interface): interface LeavePresenceArgs<TGameId extends string = string> — ⚠ undocumented
279
- - `PresenceActions` (interface): interface PresenceActions<TGameId extends string = string> — ⚠ undocumented
280
- - `PresenceFeeds` (interface): interface PresenceFeeds<TRow, TLocation> — ⚠ undocumented
281
- - `PresencePose` (interface): interface PresencePose — ⚠ undocumented
282
- - `PresencePosition` (interface): interface PresencePosition — ⚠ undocumented
282
+ - `EnsurePresenceArgs` (interface): interface EnsurePresenceArgs<TGameId extends string = string> { gameId: TGameId; externalId: string } — ⚠ undocumented
283
+ - `EnsurePresenceResult` (interface): interface EnsurePresenceResult { presenceId: string; position: PresencePosition; rotationY: number } — ⚠ undocumented
284
+ - `LeavePresenceArgs` (interface): interface LeavePresenceArgs<TGameId extends string = string> { gameId: TGameId; externalId: string } — ⚠ undocumented
285
+ - `PresenceActions` (interface): interface PresenceActions<TGameId extends string = string> { ensurePresence(args: EnsurePresenceArgs<TGameId>): Promise<EnsurePresenceResult | null>; leavePresence(args: LeavePresenceArgs<TGameId>): Promise<{left: boolean}>; syncPose(pose: PresencePose): void } — ⚠ undocumented
286
+ - `PresenceFeeds` (interface): interface PresenceFeeds<TRow, TLocation> { myPresenceLocation: TLocation | null | undefined; onlinePresences: readonly TRow[] | undefined; dormantPresences: readonly TRow[] | undefined } — ⚠ undocumented
287
+ - `PresencePose` (interface): interface PresencePose { position: {x: number; y?: number; z: number}; rotationY?: number; rotationPitch?: number; externalId?: string } — ⚠ undocumented
288
+ - `PresencePosition` (interface): interface PresencePosition { x: number; y: number; z: number } — ⚠ undocumented
283
289
  - `PresenceResidentRow` (interface): interface PresenceResidentRow — Resident identity used to suppress offline actors while their owner is online.
284
- - `PresenceSession` (interface): interface PresenceSession<TGameId extends string = string> — ⚠ undocumented
290
+ - `PresenceSession` (interface): interface PresenceSession<TGameId extends string = string> { homeGameId: TGameId; externalId: string; viewerChunkKey?: string } — ⚠ undocumented
285
291
  - `PresenceTransport` (interface): interface PresenceTransport<TRow, TLocation, TGameId extends string = string> — Backend seam for multiplayer presence. Feeds are reactive data and change identity whenever any player's pose updates; actions MUST be identity-stable for the lifetime of a mounted session so join/leave lifecycle effects can depend on them without re-running per pose tick. The use* members are called as React hooks by consumers, so a mounted transport must never change identity — remount the subtree to switch backends.
286
292
  - `createLocalPresenceTransport` (function): function createLocalPresenceTransport<TRow, TLocation, TGameId extends string = string>(): { transport: PresenceTransport<TRow, TLocation, TGameId>; actions: PresenceActions<TGameId>; } — ⚠ undocumented
287
293
 
288
294
  ## @jgengine/core/multiplayer/presenceModel
289
295
 
290
- - `ActivePresenceResolution` (interface): interface ActivePresenceResolution<T> — ⚠ undocumented
296
+ - `ActivePresenceResolution` (interface): interface ActivePresenceResolution<T> { active: T | null; extras: T[] } — ⚠ undocumented
291
297
  - `DEFAULT_POSE_SYNC_RULES` (const): const DEFAULT_POSE_SYNC_RULES: PoseSyncRules — Canonical client-authoritative pose-sync tuning shared by every host transport (WS, Convex).
292
298
  - `DEFAULT_SPAWN_ORIGIN` (const): const DEFAULT_SPAWN_ORIGIN: { readonly x: 0; readonly y: 0; readonly z: 0; } — ⚠ undocumented
293
- - `IncomingPose` (interface): interface IncomingPose — ⚠ undocumented
294
- - `PoseSyncDecision` (interface): interface PoseSyncDecision — ⚠ undocumented
295
- - `PoseSyncRules` (interface): interface PoseSyncRules — ⚠ undocumented
296
- - `PresencePoseState` (interface): interface PresencePoseState — ⚠ undocumented
299
+ - `IncomingPose` (interface): interface IncomingPose { position: {x: number; z: number; y?: number}; rotationY?: number; rotationPitch?: number; appearance?: Record<string, string> } — ⚠ undocumented
300
+ - `PoseSyncDecision` (interface): interface PoseSyncDecision { position: {x: number; y: number; z: number}; rotationY: number; rotationPitch: number; appearance?: Record<string, string>; changed: boolean; refreshKeepAlive: boolean } — ⚠ undocumented
301
+ - `PoseSyncRules` (interface): interface PoseSyncRules { maxSpeed: number; minIntervalMs?: number; maxVerticalOffset: number; floorY?: number; minElapsedSec: number; maxElapsedSec: number; keepAliveRefreshMs: number } — ⚠ undocumented · used by `DEFAULT_POSE_SYNC_RULES`: Canonical client-authoritative pose-sync tuning shared by every host transport (WS, Convex).
302
+ - `PresencePoseState` (interface): interface PresencePoseState { position: {x: number; y: number; z: number}; rotationY: number; rotationPitch?: number; lastSeenAtMs?: number; appearance?: Record<string, string> } — ⚠ undocumented
297
303
 
298
304
  ## @jgengine/core/multiplayer/simultaneousCommit
299
305
 
300
306
  - `CommitPhase` (type): type CommitPhase = "collecting" | "revealed" — ⚠ undocumented
301
- - `CommitRoundConfig` (interface): interface CommitRoundConfig — ⚠ undocumented
307
+ - `CommitRoundConfig` (interface): interface CommitRoundConfig { participants: readonly string[]; allowReseal?: boolean } — ⚠ undocumented
302
308
  - `SealResult` (type): type SealResult = | { ok: true; allSealed: boolean } | { ok: false; reason: "unknown_participant" | "already_sealed" | "already_revealed" } — ⚠ undocumented
303
- - `SealedCommit` (interface): interface SealedCommit<TAction> — ⚠ undocumented
309
+ - `SealedCommit` (interface): interface SealedCommit<TAction> { playerId: string; action: TAction; sealedAt: number } — ⚠ undocumented
304
310
 
305
311
  ## @jgengine/core/multiplayer/voiceContract
306
312
 
307
- - `PushToTalk` (interface): interface PushToTalk — ⚠ undocumented
313
+ - `PushToTalk` (interface): interface PushToTalk { mode(): PushToTalkMode; setMode(mode: PushToTalkMode): void; keyDown(): void; keyUp(): void; muted(): boolean; setMuted(muted: boolean): void; status(): PushToTalkStatus; transmitting(): boolean } — ⚠ undocumented
308
314
  - `PushToTalkMode` (type): type PushToTalkMode = "hold" | "toggle" | "openMic" — ⚠ undocumented
309
315
  - `PushToTalkStatus` (type): type PushToTalkStatus = "idle" | "keyed" | "open" — ⚠ undocumented
310
- - `VoiceParticipant` (interface): interface VoiceParticipant — ⚠ undocumented
311
- - `VoiceRoute` (interface): interface VoiceRoute — ⚠ undocumented
316
+ - `VoiceParticipant` (interface): interface VoiceParticipant { userId: string; streamId?: string } — ⚠ undocumented
317
+ - `VoiceRoute` (interface): interface VoiceRoute { fromUserId: string; channelId: string; gain: number } — ⚠ undocumented
312
318
  - `VoiceTransport` (interface): interface VoiceTransport — Signaling seam for voice: who is in a channel and which media stream descriptor they published. The media plane (WebRTC, SFU, or anything else that moves audio bytes) stays behind this seam, host-supplied — the engine never touches it. subscribers delivers the channel roster on every change, starting with the current roster.
313
319
  - `createLocalVoiceTransport` (function): function createLocalVoiceTransport(options?: { userId?: string }): { transport: VoiceTransport; participants(channelId: string): readonly VoiceParticipant[]; } — ⚠ undocumented
314
320
  - `createPushToTalk` (function): function createPushToTalk(config?: { mode?: PushToTalkMode; onChange?: (transmitting: boolean) => void; }): PushToTalk — ⚠ undocumented
@@ -326,7 +332,7 @@
326
332
  - `GameHostOptions` (type): type GameHostOptions = { runtimes?: GameRuntime[]; persistence: HostPersistence; tickMs?: number; slotsPerServer?: number; now?: () => number; createServerId?: () => string; allowedFeedActions?: readonly string[]; } — Configuration for {@link createGameHost}, including persistence, tick rate, and game runtimes.
327
333
  - `GameSocketIoServer` (type): type GameSocketIoServer = { rewind: (args: { serverId: string; atMs: number }) => RewoundPosition[]; close: () => void; } — ⚠ undocumented
328
334
  - `GameSocketIoServerOptions` (type): type GameSocketIoServerOptions = HostRouterOptions & { io: SocketIoLikeServer } — ⚠ undocumented
329
- - `GameWsServer` (type): type GameWsServer = { wss: WebSocketServer; port: () => number; rewind: (args: { serverId: string; atMs: number }) => RewoundPosition[]; close: () => Promise<void>; } — ⚠ undocumented
335
+ - `GameWsServer` (type): type GameWsServer = { wss: WebSocketServer; port: () => number; rewind: (args: { serverId: string; atMs: number }) => RewoundPosition[]; /** Fence new messages, drain accepted router work, and close sockets. Repeated calls share completion; host persistence remains caller-owned. */ close: () => Prom… — ⚠ undocumented
330
336
  - `GameWsServerOptions` (type): type GameWsServerOptions = HostRouterOptions & { server?: HttpServer; port?: number; path?: string; /** Per-message payload cap in bytes. Defaults to {@link DEFAULT_MAX_PAYLOAD_BYTES}. */ maxPayloadBytes?: number; /** Max concurrent sockets accepted; connections beyond this are closed immediately. D… — ⚠ undocumented
331
337
  - `HostChangeEvent` (type): type HostChangeEvent = { type: "server"; serverId: string; } | { type: "player"; serverId: string; userId: string; } | { type: "feed"; serverId: string; action: string; } — A change notification emitted by a `GameHost` for a server, player, or feed mutation.
332
338
  - `HostedGameDefinition` (interface): interface HostedGameDefinition — A game the world server can host — its authoritative {@link GameDefinition} and the content lookup a `GameContext` reads.
@@ -432,7 +438,7 @@
432
438
  - `DEFAULT_HEARTBEAT_INTERVAL_MS` (const): const DEFAULT_HEARTBEAT_INTERVAL_MS: 30000 — Default ping/pong interval; a socket that misses one round-trip is terminated.
433
439
  - `DEFAULT_MAX_CONNECTIONS` (const): const DEFAULT_MAX_CONNECTIONS: 10000 — Default max concurrent sockets this server accepts before rejecting new ones.
434
440
  - `DEFAULT_MAX_PAYLOAD_BYTES` (const): const DEFAULT_MAX_PAYLOAD_BYTES: 1048576 — Default per-message payload cap (bytes) — `ws` closes the socket with 1009 past this.
435
- - `GameWsServer` (type): type GameWsServer = { wss: WebSocketServer; port: () => number; rewind: (args: { serverId: string; atMs: number }) => RewoundPosition[]; close: () => Promise<void>; } — ⚠ undocumented
441
+ - `GameWsServer` (type): type GameWsServer = { wss: WebSocketServer; port: () => number; rewind: (args: { serverId: string; atMs: number }) => RewoundPosition[]; /** Fence new messages, drain accepted router work, and close sockets. Repeated calls share completion; host persistence remains caller-owned. */ close: () => Prom… — ⚠ undocumented
436
442
  - `GameWsServerOptions` (type): type GameWsServerOptions = HostRouterOptions & { server?: HttpServer; port?: number; path?: string; /** Per-message payload cap in bytes. Defaults to {@link DEFAULT_MAX_PAYLOAD_BYTES}. */ maxPayloadBytes?: number; /** Max concurrent sockets accepted; connections beyond this are closed immediately. D… — ⚠ undocumented
437
443
  - `RewoundPosition` (type): type RewoundPosition = { userId: string; x: number; y: number; z: number; } — A player's interpolated position sampled from history at a past timestamp.
438
444
  - `createGameWsServer` (function): function createGameWsServer(options: GameWsServerOptions): GameWsServer — ⚠ undocumented
@@ -442,7 +448,7 @@
442
448
  - `SqlPool` (type): type SqlPool = SqlQueryable & { connect: () => Promise<SqlPoolClient>; } — ⚠ undocumented
443
449
  - `SqlPoolClient` (type): type SqlPoolClient = SqlQueryable & { release: () => void; } — ⚠ undocumented
444
450
  - `SqlQueryResult` (type): type SqlQueryResult = { rows: Record<string, unknown>[] } — ⚠ undocumented
445
- - `SqlQueryable` (type): type SqlQueryable = { query: (text: string, params?: unknown[]) => Promise<SqlQueryResult>; } — ⚠ undocumented
451
+ - `SqlQueryable` (type): type SqlQueryable = { query: (text: string, params?: unknown[]) => Promise<SqlQueryResult>; } — ⚠ undocumented · used by `sqlWorldStore`: Create an asynchronous hosted-world store backed by one JSONB row.
446
452
  - `ensureSchema` (function): function ensureSchema(pool: SqlQueryable): Promise<void> — ⚠ undocumented
447
453
  - `sqlPersistence` (function): function sqlPersistence(pool: SqlPool, now: () => number = Date.now): HostPersistence — ⚠ undocumented
448
454
  - `sqlWorldStore` (function): function sqlWorldStore(pool: SqlQueryable, worldId: string): HostedWorldStore — Create an asynchronous hosted-world store backed by one JSONB row.
@@ -452,7 +458,7 @@
452
458
  - `SqlPool` (type): type SqlPool = SqlQueryable & { connect: () => Promise<SqlPoolClient>; } — ⚠ undocumented
453
459
  - `SqlPoolClient` (type): type SqlPoolClient = SqlQueryable & { release: () => void; } — ⚠ undocumented
454
460
  - `SqlQueryResult` (type): type SqlQueryResult = { rows: Record<string, unknown>[] } — ⚠ undocumented
455
- - `SqlQueryable` (type): type SqlQueryable = { query: (text: string, params?: unknown[]) => Promise<SqlQueryResult>; } — ⚠ undocumented
461
+ - `SqlQueryable` (type): type SqlQueryable = { query: (text: string, params?: unknown[]) => Promise<SqlQueryResult>; } — ⚠ undocumented · used by `sqlWorldStore` (@jgengine/sql): Create an asynchronous hosted-world store backed by one JSONB row.
456
462
  - `ensureSchema` (function): function ensureSchema(pool: SqlQueryable): Promise<void> — ⚠ undocumented
457
463
  - `sqlPersistence` (function): function sqlPersistence(pool: SqlPool, now: () => number = Date.now): HostPersistence — ⚠ undocumented
458
464
 
@@ -479,7 +485,7 @@
479
485
  - `GameHostOptions` (type): type GameHostOptions = { runtimes?: GameRuntime[]; persistence: HostPersistence; tickMs?: number; slotsPerServer?: number; now?: () => number; createServerId?: () => string; allowedFeedActions?: readonly string[]; } — Configuration for {@link createGameHost}, including persistence, tick rate, and game runtimes.
480
486
  - `HostChangeEvent` (type): type HostChangeEvent = | { type: "server"; serverId: string } | { type: "player"; serverId: string; userId: string } | { type: "feed"; serverId: string; action: string } — A change notification emitted by a `GameHost` for a server, player, or feed mutation.
481
487
  - `HostCommandOp` (type): type HostCommandOp = "pose" | "runCommand" | "join" | "browse" | "voice" — A host-side op the command middleware pipeline can gate: pose sync, `runCommand`, join/joinByCode, browse, or voice join/leave/publish.
482
- - `HostRouter` (type): type HostRouter = { connect: (transport: HostRouterTransport) => HostRouterConnection; rewind: (args: { serverId: string; atMs: number }) => RewoundPosition[]; close: () => void; } — ⚠ undocumented
488
+ - `HostRouter` (type): type HostRouter = { connect: (transport: HostRouterTransport) => HostRouterConnection; rewind: (args: { serverId: string; atMs: number }) => RewoundPosition[]; close: () => void; /** Wait for accepted message handlers and subscription reads to settle. Close first to prevent new work. */ drain: () =>… — ⚠ undocumented
483
489
  - `HostRouterAuthenticate` (type): type HostRouterAuthenticate = (args: { userId: string; token?: string; }) => Promise<string | null> | string | null — ⚠ undocumented
484
490
  - `HostRouterConnection` (type): type HostRouterConnection = { handleRaw: (raw: unknown) => void; close: () => void; } — ⚠ undocumented
485
491
  - `HostRouterOptions` (type): type HostRouterOptions = { host: GameHost; authenticate?: HostRouterAuthenticate; allowAnonymous?: boolean; singleSession?: boolean; poseRules?: PoseSyncRules; positionHistoryMs?: number; chatRateLimit?: ChatRateLimit; chatHistoryLimit?: number; chatMaxBodyLength?: number; /** Per-op rate limits for… — ⚠ undocumented
@@ -492,6 +498,7 @@
492
498
  - `MAX_FEED_ACTION_LENGTH` (const): const MAX_FEED_ACTION_LENGTH: 256 — Max length of a `pushFeed` action name, in UTF-16 code units.
493
499
  - `MAX_FEED_ENTRY_BYTES` (const): const MAX_FEED_ENTRY_BYTES: 65536 — Max serialized size of a `pushFeed` entry payload, in bytes.
494
500
  - `MAX_QUEUED_MESSAGES` (const): const MAX_QUEUED_MESSAGES: 64 — Cap on frames queued behind a connection's in-flight message; beyond this a flood gets rejected instead of piling up unbounded promises.
501
+ - `MAX_SESSION_ID_LENGTH` (const): const MAX_SESSION_ID_LENGTH: 128 — Max length of a join/leave `sessionId`, in UTF-16 code units.
495
502
  - `OP_LEDGER_LIMIT` (const): const OP_LEDGER_LIMIT: 64 — Max recently-applied `runCommand` op IDs retained per (serverId, userId), oldest evicted first.
496
503
  - `PeerGuest` (type): type PeerGuest = { backend: WsBackend; offer: () => Promise<string>; connect: (answerCode: string) => Promise<void>; close: () => void; } — ⚠ undocumented
497
504
  - `PeerGuestOptions` (type): type PeerGuestOptions = { userId: string; token?: string; rtc?: PeerRtcOptions; } — ⚠ undocumented
@@ -510,14 +517,14 @@
510
517
  - `TransportPipe` (type): type TransportPipe = { send: (data: string) => void; close: () => void; } — ⚠ undocumented
511
518
  - `TransportPipeFactory` (type): type TransportPipeFactory = (handlers: TransportPipeHandlers) => TransportPipe — ⚠ undocumented
512
519
  - `TransportPipeHandlers` (type): type TransportPipeHandlers = { onOpen: () => void; onMessage: (data: string) => void; onClose: () => void; } — ⚠ undocumented
513
- - `VoiceChannelDef` (interface): interface VoiceChannelDef — ⚠ undocumented
520
+ - `VoiceChannelDef` (interface): interface VoiceChannelDef { id: VoiceChannelId; positional: boolean; falloff?: AudioFalloffConfig; gain?: number } — ⚠ undocumented
514
521
  - `VoiceChannelId` (type): type VoiceChannelId = string — ⚠ undocumented
515
- - `VoiceChannelRouter` (interface): interface VoiceChannelRouter — ⚠ undocumented
522
+ - `VoiceChannelRouter` (interface): interface VoiceChannelRouter { registerChannel(def: VoiceChannelDef): void; join(userId: VoiceMemberId, channelId: VoiceChannelId): void; leave(userId: VoiceMemberId, channelId: VoiceChannelId): void; leaveAll(userId: VoiceMemberId): void; updatePosition(userId: VoiceM… — ⚠ undocumented
516
523
  - `VoiceMemberId` (type): type VoiceMemberId = string — ⚠ undocumented
517
- - `VoicePosition` (interface): interface VoicePosition — ⚠ undocumented
518
- - `VoiceRoute` (interface): interface VoiceRoute — ⚠ undocumented
524
+ - `VoicePosition` (interface): interface VoicePosition { x: number; y: number; z: number } — ⚠ undocumented
525
+ - `VoiceRoute` (interface): interface VoiceRoute { fromUserId: VoiceMemberId; channelId: VoiceChannelId; gain: number } — ⚠ undocumented
519
526
  - `WS_PROTOCOL_VERSION` (const): const WS_PROTOCOL_VERSION: 1 — ⚠ undocumented
520
- - `WorldGameHost` (interface): interface WorldGameHost extends GameHost — A {@link GameHost} whose worlds run on `HostedWorldSession`s; `tick` advances them and re-broadcasts on change.
527
+ - `WorldGameHost` (interface): interface WorldGameHost extends GameHost — A {@link GameHost} backed by `HostedWorldSession`s; `tick` advances worlds and `unload` explicitly saves and releases idle world references.
521
528
  - `WorldGameHostOptions` (interface): interface WorldGameHostOptions — Config for {@link createWorldGameHost}: how to resolve a hosted world's authoritative session per server.
522
529
  - `WsAppearance` (type): type WsAppearance = Record<string, string | number | boolean> — Client-set cosmetic/state tags carried alongside a pose (skin, mount, emote, ...). Primitive values only.
523
530
  - `WsBackend` (type): type WsBackend = GameBackend & { rtt: { sampleMs: number; smoothedMs: number }; pushFeedEntry: (args: { serverId: string; action: string; entry: unknown }) => Promise<void>; browse: (args: { gameId: string; filter?: MatchFilter; limit?: number }) => Promise<SessionListing[]>; joinByCode: (args: { ga… — ⚠ undocumented
@@ -526,7 +533,7 @@
526
533
  - `WsChannel` (type): type WsChannel = "server" | "player" | "feed" | "presence" | "chat" | "voice" — ⚠ undocumented
527
534
  - `WsChatMessage` (type): type WsChatMessage = { id: string; channelId: string; fromUserId: string; body: string; at: number; } — ⚠ undocumented
528
535
  - `WsChatSync` (type): type WsChatSync = { subscribe: ( serverId: string, channelId: string, onChange: (messages: WsChatMessage[]) => void, ) => () => void; send: (serverId: string, channelId: string, body: string) => Promise<ChatSendOutcome>; } — ⚠ undocumented
529
- - `WsClientMessage` (type): type WsClientMessage = | { v: 1; t: "ping"; id: number; at: number } | { v: 1; t: "hello"; id: number; userId: string; token?: string } | { v: 1; t: "join"; id: number; gameId: string; serverId?: string; attributes?: SessionAttributes; code?: string; role?: "player" | "spectator"; } | { v: 1; t: "jo… — ⚠ undocumented
536
+ - `WsClientMessage` (type): type WsClientMessage = | { v: 1; t: "ping"; id: number; at: number } | { v: 1; t: "hello"; id: number; userId: string; token?: string } | { v: 1; t: "join"; id: number; gameId: string; serverId?: string; attributes?: SessionAttributes; code?: string; role?: "player" | "spectator"; /** Client session… — ⚠ undocumented
530
537
  - `WsDecodeFailure` (type): type WsDecodeFailure = { reason: string; id?: number; } — ⚠ undocumented
531
538
  - `WsJoinByCodeResult` (type): type WsJoinByCodeResult = JoinServerResult | null — ⚠ undocumented
532
539
  - `WsJoinResult` (type): type WsJoinResult = JoinServerResult — ⚠ undocumented
@@ -602,7 +609,7 @@
602
609
 
603
610
  - `DEFAULT_GRACE_MS` (const): const DEFAULT_GRACE_MS: 15000 — Default reconnect grace period for hosted socket sessions.
604
611
  - `DEFAULT_POSE_RULES` (const): const DEFAULT_POSE_RULES: PoseSyncRules — ⚠ undocumented
605
- - `HostRouter` (type): type HostRouter = { connect: (transport: HostRouterTransport) => HostRouterConnection; rewind: (args: { serverId: string; atMs: number }) => RewoundPosition[]; close: () => void; } — ⚠ undocumented
612
+ - `HostRouter` (type): type HostRouter = { connect: (transport: HostRouterTransport) => HostRouterConnection; rewind: (args: { serverId: string; atMs: number }) => RewoundPosition[]; close: () => void; /** Wait for accepted message handlers and subscription reads to settle. Close first to prevent new work. */ drain: () =>… — ⚠ undocumented
606
613
  - `HostRouterAuthenticate` (type): type HostRouterAuthenticate = (args: { userId: string; token?: string; }) => Promise<string | null> | string | null — ⚠ undocumented
607
614
  - `HostRouterConnection` (type): type HostRouterConnection = { handleRaw: (raw: unknown) => void; close: () => void; } — ⚠ undocumented
608
615
  - `HostRouterOptions` (type): type HostRouterOptions = { host: GameHost; authenticate?: HostRouterAuthenticate; allowAnonymous?: boolean; singleSession?: boolean; poseRules?: PoseSyncRules; positionHistoryMs?: number; chatRateLimit?: ChatRateLimit; chatHistoryLimit?: number; chatMaxBodyLength?: number; /** Per-op rate limits for… — ⚠ undocumented
@@ -649,12 +656,13 @@
649
656
  - `MAX_COMMAND_LENGTH` (const): const MAX_COMMAND_LENGTH: 4096 — Max length of a `runCommand` command name, in UTF-16 code units.
650
657
  - `MAX_FEED_ACTION_LENGTH` (const): const MAX_FEED_ACTION_LENGTH: 256 — Max length of a `pushFeed` action name, in UTF-16 code units.
651
658
  - `MAX_FEED_ENTRY_BYTES` (const): const MAX_FEED_ENTRY_BYTES: 65536 — Max serialized size of a `pushFeed` entry payload, in bytes.
659
+ - `MAX_SESSION_ID_LENGTH` (const): const MAX_SESSION_ID_LENGTH: 128 — Max length of a join/leave `sessionId`, in UTF-16 code units.
652
660
  - `WS_PROTOCOL_VERSION` (const): const WS_PROTOCOL_VERSION: 1 — ⚠ undocumented
653
661
  - `WsAppearance` (type): type WsAppearance = Record<string, string | number | boolean> — Client-set cosmetic/state tags carried alongside a pose (skin, mount, emote, ...). Primitive values only.
654
662
  - `WsBrowseResult` (type): type WsBrowseResult = SessionListing[] — ⚠ undocumented
655
663
  - `WsChannel` (type): type WsChannel = "server" | "player" | "feed" | "presence" | "chat" | "voice" — ⚠ undocumented
656
664
  - `WsChatMessage` (type): type WsChatMessage = { id: string; channelId: string; fromUserId: string; body: string; at: number; } — ⚠ undocumented
657
- - `WsClientMessage` (type): type WsClientMessage = | { v: 1; t: "ping"; id: number; at: number } | { v: 1; t: "hello"; id: number; userId: string; token?: string } | { v: 1; t: "join"; id: number; gameId: string; serverId?: string; attributes?: SessionAttributes; code?: string; role?: "player" | "spectator"; } | { v: 1; t: "jo… — ⚠ undocumented
665
+ - `WsClientMessage` (type): type WsClientMessage = | { v: 1; t: "ping"; id: number; at: number } | { v: 1; t: "hello"; id: number; userId: string; token?: string } | { v: 1; t: "join"; id: number; gameId: string; serverId?: string; attributes?: SessionAttributes; code?: string; role?: "player" | "spectator"; /** Client session… — ⚠ undocumented
658
666
  - `WsDecodeFailure` (type): type WsDecodeFailure = { reason: string; id?: number; } — ⚠ undocumented
659
667
  - `WsJoinByCodeResult` (type): type WsJoinByCodeResult = JoinServerResult | null — ⚠ undocumented
660
668
  - `WsJoinResult` (type): type WsJoinResult = JoinServerResult — ⚠ undocumented
@@ -682,17 +690,17 @@
682
690
 
683
691
  ## @jgengine/ws/voiceChannel
684
692
 
685
- - `VoiceChannelDef` (interface): interface VoiceChannelDef — ⚠ undocumented
693
+ - `VoiceChannelDef` (interface): interface VoiceChannelDef { id: VoiceChannelId; positional: boolean; falloff?: AudioFalloffConfig; gain?: number } — ⚠ undocumented
686
694
  - `VoiceChannelId` (type): type VoiceChannelId = string — ⚠ undocumented
687
- - `VoiceChannelRouter` (interface): interface VoiceChannelRouter — ⚠ undocumented
695
+ - `VoiceChannelRouter` (interface): interface VoiceChannelRouter { registerChannel(def: VoiceChannelDef): void; join(userId: VoiceMemberId, channelId: VoiceChannelId): void; leave(userId: VoiceMemberId, channelId: VoiceChannelId): void; leaveAll(userId: VoiceMemberId): void; updatePosition(userId: VoiceM… — ⚠ undocumented
688
696
  - `VoiceMemberId` (type): type VoiceMemberId = string — ⚠ undocumented
689
- - `VoicePosition` (interface): interface VoicePosition — ⚠ undocumented
690
- - `VoiceRoute` (interface): interface VoiceRoute — ⚠ undocumented
697
+ - `VoicePosition` (interface): interface VoicePosition { x: number; y: number; z: number } — ⚠ undocumented
698
+ - `VoiceRoute` (interface): interface VoiceRoute { fromUserId: VoiceMemberId; channelId: VoiceChannelId; gain: number } — ⚠ undocumented
691
699
  - `computeVoiceGain` (function): function computeVoiceGain(def: VoiceChannelDef, distance: number | null): number — ⚠ undocumented
692
700
  - `createVoiceChannelRouter` (function): function createVoiceChannelRouter(channels?: readonly VoiceChannelDef[]): VoiceChannelRouter — ⚠ undocumented
693
701
 
694
702
  ## @jgengine/ws/worldHost
695
703
 
696
- - `WorldGameHost` (interface): interface WorldGameHost extends GameHost — A {@link GameHost} whose worlds run on `HostedWorldSession`s; `tick` advances them and re-broadcasts on change.
704
+ - `WorldGameHost` (interface): interface WorldGameHost extends GameHost — A {@link GameHost} backed by `HostedWorldSession`s; `tick` advances worlds and `unload` explicitly saves and releases idle world references.
697
705
  - `WorldGameHostOptions` (interface): interface WorldGameHostOptions — Config for {@link createWorldGameHost}: how to resolve a hosted world's authoritative session per server.
698
706
  - `createWorldGameHost` (function): function createWorldGameHost(options: WorldGameHostOptions): WorldGameHost — The GameContext-loop counterpart of the reducer `createGameHost`: a structural {@link GameHost} that serves each world's full `WorldSnapshot` as `serverState`, so the existing ws router, `createWsBackend`, and the shell's `attachWorldSync` carry host-authoritative GameContext worlds with zero changes to any of them. The harness owns the tick cadence (call {@link WorldGameHost.tick} on an interval); commands and joins broadcast immediately.