@jgengine/convex 0.44.0 → 0.46.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.
- package/CHANGELOG.md +1024 -27
- package/README.md +1 -1
- package/dist/convexChatTransport.d.ts +2 -2
- package/dist/convexSaveBackend.d.ts +1 -1
- package/dist/convexSaveBackend.js +1 -1
- package/dist/createConvexGameTransport.d.ts +3 -3
- package/dist/server.d.ts +7 -11
- package/dist/server.js +3 -5
- package/package.json +5 -5
- package/skills/ce-handoff/SKILL.md +1 -1
- package/skills/jgengine/SKILL.md +5 -5
- package/skills/jgengine/api.md +171 -88
- package/skills/jgengine/capabilities.md +30 -10
- package/skills/jgengine/recipes/minimal-game.md +5 -5
- package/skills/jgengine-multiplayer/SKILL.md +2 -2
- package/skills/jgengine-multiplayer/api.md +81 -47
- package/skills/jgengine-multiplayer/capabilities.md +13 -1
- package/skills/jgengine-multiplayer/reference.md +12 -12
- package/skills/jgengine-multiplayer/references/peer-worlds.md +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- GENERATED by scripts/gen-capability-index.ts — do not edit. Regenerate: bun run gen
|
|
1
|
+
<!-- GENERATED by scripts/gen-capability-index.ts — do not edit. Regenerate: bun run gen capabilities -->
|
|
2
2
|
|
|
3
3
|
# jgengine — capability index
|
|
4
4
|
|
|
@@ -12,6 +12,14 @@ 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
|
+
## browser-suspension — observe blur, hidden-page and pointer-lock loss while the game owns pause and input policy
|
|
16
|
+
|
|
17
|
+
- `useBrowserSuspension` (function) · `import { useBrowserSuspension } from "@jgengine/core/runtime/browserLifecycle.view"`
|
|
18
|
+
|
|
19
|
+
## browser-suspension-observer — observe browser interruptions outside React with caller-owned pause policy and event sources
|
|
20
|
+
|
|
21
|
+
- `observeBrowserSuspension` (function) · `import { observeBrowserSuspension } from "@jgengine/core/runtime/browserLifecycle.view"`
|
|
22
|
+
|
|
15
23
|
## camera-director — drive the camera at runtime (follow, rig swap, cinematic, chase tuning, FOV kick, RTS pan-to jump)
|
|
16
24
|
|
|
17
25
|
- `CameraDirector` (interface) · `import { CameraDirector } from "@jgengine/core/runtime/cameraDirector"`
|
|
@@ -30,15 +38,19 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
30
38
|
|
|
31
39
|
## command-number — validate finite, bounded numbers and integers at command boundaries
|
|
32
40
|
|
|
33
|
-
- `readInputNumber` (function) · `import { readInputNumber } from "@jgengine/core/
|
|
41
|
+
- `readInputNumber` (function) · `import { readInputNumber } from "@jgengine/core/commands/commandInput"`
|
|
34
42
|
|
|
35
43
|
## command-parse — parse untrusted command input once so every command stage sees typed, bounded values
|
|
36
44
|
|
|
37
|
-
- `defineCommand` (function) · `import { defineCommand } from "@jgengine/core/
|
|
45
|
+
- `defineCommand` (function) · `import { defineCommand } from "@jgengine/core/commands/commandRunner"`
|
|
38
46
|
|
|
39
47
|
## command-quantity — validate bounded whole-item counts at command boundaries
|
|
40
48
|
|
|
41
|
-
- `readQuantity` (function) · `import { readQuantity } from "@jgengine/core/
|
|
49
|
+
- `readQuantity` (function) · `import { readQuantity } from "@jgengine/core/commands/commandInput"`
|
|
50
|
+
|
|
51
|
+
## default-drive-codes — stock WASD/arrow throttle, brake, steer and handbrake key codes
|
|
52
|
+
|
|
53
|
+
- `DEFAULT_DRIVE_CODES` (const) · `import { DEFAULT_DRIVE_CODES } from "@jgengine/shell/gameKit"`
|
|
42
54
|
|
|
43
55
|
## default-walk-codes — stock WASD + jump key codes for the shell walk controller
|
|
44
56
|
|
|
@@ -88,6 +100,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
88
100
|
|
|
89
101
|
- `isListablePublicly` (function) · `import { isListablePublicly } from "@jgengine/core/runtime/hostPolicy"`
|
|
90
102
|
|
|
103
|
+
## hotbar-slot-bindings — number-row key codes for a run of numbered actions (hotbar, build menu, tools)
|
|
104
|
+
|
|
105
|
+
- `hotbarSlotBindings` (function) · `import { hotbarSlotBindings } from "@jgengine/shell/gameKit"`
|
|
106
|
+
|
|
91
107
|
## hud-canvas — the HUD root surface — design-resolution scaling plus live panel-placement editing (F2+C)
|
|
92
108
|
|
|
93
109
|
- `HudCanvas` (function) · `import { HudCanvas } from "@jgengine/shell/gameKit"`
|
|
@@ -100,6 +116,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
100
116
|
|
|
101
117
|
- `Coins` (function) · `import { Coins } from "@jgengine/shell/gameKit"`
|
|
102
118
|
|
|
119
|
+
## hud-fade — HUD group that fades when idle and returns when its values change
|
|
120
|
+
|
|
121
|
+
- `HudFade` (function) · `import { HudFade } from "@jgengine/shell/gameKit"`
|
|
122
|
+
|
|
103
123
|
## hud-hotbar — optional inventory hotbar building block — games own slot chrome
|
|
104
124
|
|
|
105
125
|
- `Hotbar` (function) · `import { Hotbar } from "@jgengine/shell/gameKit"`
|
|
@@ -112,9 +132,9 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
112
132
|
|
|
113
133
|
- `HudPanel` (function) · `import { HudPanel } from "@jgengine/shell/gameKit"`
|
|
114
134
|
|
|
115
|
-
## hud-
|
|
135
|
+
## hud-slots — a play HUD of edge-pinned, player-movable panels in one component
|
|
116
136
|
|
|
117
|
-
- `
|
|
137
|
+
- `HudSlots` (function) · `import { HudSlots } from "@jgengine/shell/gameKit"`
|
|
118
138
|
|
|
119
139
|
## input-recorder — tick-indexed input log for replay, rollback, and regression tests
|
|
120
140
|
|
|
@@ -174,10 +194,6 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
174
194
|
|
|
175
195
|
- `createSnapshotBuffer` (function) · `import { createSnapshotBuffer } from "@jgengine/core/runtime/snapshotBuffer"`
|
|
176
196
|
|
|
177
|
-
## runtime-save — save/load the whole game world through a pluggable backend, autosave or save points
|
|
178
|
-
|
|
179
|
-
- `createRuntimeSave` (function) · `import { createRuntimeSave } from "@jgengine/core/runtime/runtimeSave"`
|
|
180
|
-
|
|
181
197
|
## scene-ownership — audit a whole ownership manifest for boundary violations
|
|
182
198
|
|
|
183
199
|
- `auditManifest` (function) · `import { auditManifest } from "@jgengine/core/authoring"`
|
|
@@ -198,6 +214,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
198
214
|
|
|
199
215
|
- `createSimSnapshotRegistry` (function) · `import { createSimSnapshotRegistry } from "@jgengine/core/runtime/simSnapshot"`
|
|
200
216
|
|
|
217
|
+
## tune-control-labels — find a Tune panel field by its control name (drive --fill, screen readers)
|
|
218
|
+
|
|
219
|
+
- `tuneControlLabels` (function) · `import { tuneControlLabels } from "@jgengine/core/devtools/TunePanel.view"`
|
|
220
|
+
|
|
201
221
|
## typed-keyed-store — a cast-free typed handle onto a per-owner keyed family of reactive game-store slots, replayable and host-authoritative
|
|
202
222
|
|
|
203
223
|
- `defineKeyedStore` (function) · `import { defineKeyedStore } from "@jgengine/shell/gameKit"`
|
|
@@ -13,7 +13,7 @@ it should not recreate input, model lifecycle, storage or UI accessibility machi
|
|
|
13
13
|
|
|
14
14
|
## 1. Scaffold
|
|
15
15
|
|
|
16
|
-
`npx jgengine create <name>` (inside the engine monorepo: `bun run
|
|
16
|
+
`npx jgengine create <name>` (inside the engine monorepo: `bun run jgengine create "<Title>"`). The game's `jgengine.json` says what it is and uses; edit it and run `jgengine sync` to update the game.
|
|
17
17
|
The file set is thin and already runs:
|
|
18
18
|
|
|
19
19
|
- `src/main.tsx` — mounts `GameHost` with `editor={() => import("@jgengine/editor")}`; the
|
|
@@ -47,7 +47,7 @@ export const game = defineGame({
|
|
|
47
47
|
|
|
48
48
|
**World is optional and thin.** Omitting `world` is valid — a pure UI/rules game is not a
|
|
49
49
|
spatial place. When the game *is* a place, declare only substrate + laws with `world()` from
|
|
50
|
-
`@jgengine/core/
|
|
50
|
+
`@jgengine/core/placement/place` (e.g. `{ id, ground: { mode: "flat", size: { x: Infinity, z: Infinity } }, physics: { gravity: -24 } }`,
|
|
51
51
|
or `mode: "board"` for 2D). Never seed, sky, or vegetation in `world` — the engine renders its
|
|
52
52
|
default sky until you author one in the editor, and foliage is editor scatter over the ground.
|
|
53
53
|
A created 3D game instead ships hills as a terrain sculpt in `editor.scene.json` and a `world.ts`
|
|
@@ -81,9 +81,9 @@ export function onNewPlayer(ctx: GameContext): void {
|
|
|
81
81
|
|
|
82
82
|
`GameUI` starts as an empty `HudCanvas` (`useHudLayout` + `HudCanvas` from the kit). That is
|
|
83
83
|
intentional: **every game owns its UI composition** — layout, terminology, placement, and art
|
|
84
|
-
direction. Compose the shipped building blocks the moment you need them (`
|
|
85
|
-
`Coins`, `InventoryGrid`, `CharacterSheet`, window shells from `jgengine-ui`), then reskin via
|
|
86
|
-
`
|
|
84
|
+
direction. Compose the shipped building blocks the moment you need them (`HudSlots`, `KitVitals`, `ActionBar look="kit"`,
|
|
85
|
+
`Hotbar`, `Coins`, `InventoryGrid`, `CharacterSheet`, window shells from `jgengine-ui`), then reskin via
|
|
86
|
+
the UI kit's tokens (`customizeKit`). Reaching for those is using the engine correctly, not incomplete work. What
|
|
87
87
|
the engine does not ship is a whole finished game *face* or genre theme preset — do not leave an
|
|
88
88
|
unarranged, unskinned generic dashboard as the product. Custom markup can use headless
|
|
89
89
|
SDK behavior; a missing reusable behavior is an upstream gap (see `jgengine-ui`).
|
|
@@ -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. Declare commands with `defineCommand({ parse, scope, validate, apply })` and build `parse` from the `readInput*` readers in `@jgengine/core/
|
|
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/commands/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
|
|
|
@@ -69,4 +69,4 @@ Pass runtimes to `jgengineCronSpecs` so no tick cron registers when no runtime d
|
|
|
69
69
|
|
|
70
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
|
-
For the explicit shared-builder composition, `jgengine create "World Name" --
|
|
72
|
+
For the explicit shared-builder composition, `jgengine create "World Name" --template 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.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- GENERATED by scripts/gen-skill-api.ts — do not edit. Regenerate: bun run gen
|
|
1
|
+
<!-- GENERATED by scripts/gen-skill-api.ts — do not edit. Regenerate: bun run gen skill-api -->
|
|
2
2
|
|
|
3
3
|
# jgengine-multiplayer — exported API surface
|
|
4
4
|
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
- `ConvexGameTransportConfig` (type): type ConvexGameTransportConfig = { gameId: string; userId?: string; } — ⚠ undocumented
|
|
13
13
|
- `ConvexLeaderboardReads` (type): type ConvexLeaderboardReads = ReturnType<typeof createConvexLeaderboardReads> — ⚠ undocumented
|
|
14
14
|
- `ConvexPresenceFunctions` (interface): interface ConvexPresenceFunctions — The Convex functions a **game** supplies for a world it owns: presence keyed by the game's own ids, including which residents exist while offline, which is game content and has no engine analogue. Not satisfied by `createPresenceFunctions` (`@jgengine/convex/server`) — that is the pose lane of a hosted `jgGameServers` server, backing `PresenceSync` rather than `PresenceTransport`.
|
|
15
|
+
- `ConvexRunCommandArgs` (type): type ConvexRunCommandArgs = RunCommandArgs & { externalId?: string; /** * How much of the server to hydrate before applying the command. Overrides whatever the * {@link CommandDef} declares through its own `scope`; omit both to hydrate the whole world. */ scope?: LoadSnapshotScope; /** * Trusted hos… — Arguments of the `runCommand` mutation, and of the equivalent {@link GameServerHelpers.runCommand} helper.
|
|
15
16
|
- `ConvexSaveBackendOptions` (interface): interface ConvexSaveBackendOptions — Config for {@link createConvexSaveBackend} — the Convex client, optional function refs (defaults to the `saves.*` convention), and an optional key `namespace`.
|
|
16
17
|
- `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
18
|
- `DEFAULT_CONVEX_POSE_RULES` (const): const DEFAULT_CONVEX_POSE_RULES: PoseSyncRules — ⚠ undocumented
|
|
@@ -33,13 +34,11 @@
|
|
|
33
34
|
- `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.
|
|
34
35
|
- `MAX_CHUNKS_PER_QUERY` (const): const MAX_CHUNKS_PER_QUERY: 64 — How many chunk rows one `getChunks` call may return.
|
|
35
36
|
- `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`.
|
|
37
37
|
- `MatchmakingMode` (type): type MatchmakingMode = "auto" | "singleton" — How auto-match behaves when no `serverId` is supplied.
|
|
38
38
|
- `OnlinePlayer` (type): type OnlinePlayer = { serverId: string; userId: string; homeGameId?: string } — One active player delivered to an online-system batch.
|
|
39
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.
|
|
40
40
|
- `PresenceSyncResult` (type): type PresenceSyncResult = { pose: { x: number; y: number; z: number; rotationY: number; rotationPitch: number }; lastSeenAt: number; displaced: boolean; } — What `sync` hands back: the pose the server holds after clamping, and whether this session had been displaced.
|
|
41
41
|
- `REVISION_CONFLICT_REASON` (const): const REVISION_CONFLICT_REASON: "Revision conflict" — ⚠ undocumented
|
|
42
|
-
- `RunCommandArgs` (type): type RunCommandArgs = { serverId: string; command: string; input: unknown; externalId?: string; /** * How much of the server to hydrate before applying the command. Overrides whatever the * {@link CommandDef} declares through its own `scope`; omit both to hydrate the whole world. */ scope?: LoadSnap… — Arguments of the `runCommand` mutation, and of the equivalent {@link GameServerHelpers.runCommand} helper.
|
|
43
42
|
- `RunCommandOutcome` (type): type RunCommandOutcome = { ok: true } | { ok: false; reason: string } — Outcome of a runtime command: applied, or refused with a reason (unknown server, non-member, validation, revision conflict).
|
|
44
43
|
- `ServerDoc` (type): type ServerDoc = DocumentByName<JGDataModel, "jgGameServers"> — A `jgGameServers` row — the server handle the persistence helpers load from and write back to.
|
|
45
44
|
- `canJoinPrivateServer` (function): function canJoinPrivateServer(args: { isMember: boolean; joinCode: string | undefined; suppliedCode: string | undefined; }): boolean — Private-server join-code gate. Existing members always pass; non-members must present a matching `joinCode` (loose-normalized via {@link normalizeJoinCode}). Callers still decide whether the server is private — this only answers the code/membership half.
|
|
@@ -124,6 +123,7 @@
|
|
|
124
123
|
|
|
125
124
|
## @jgengine/convex/server
|
|
126
125
|
|
|
126
|
+
- `ConvexRunCommandArgs` (type): type ConvexRunCommandArgs = RunCommandArgs & { externalId?: string; /** * How much of the server to hydrate before applying the command. Overrides whatever the * {@link CommandDef} declares through its own `scope`; omit both to hydrate the whole world. */ scope?: LoadSnapshotScope; /** * Trusted hos… — Arguments of the `runCommand` mutation, and of the equivalent {@link GameServerHelpers.runCommand} helper.
|
|
127
127
|
- `DEFAULT_CONVEX_POSE_RULES` (const): const DEFAULT_CONVEX_POSE_RULES: PoseSyncRules — ⚠ undocumented
|
|
128
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
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.
|
|
@@ -139,12 +139,10 @@
|
|
|
139
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.
|
|
140
140
|
- `MAX_CHUNKS_PER_QUERY` (const): const MAX_CHUNKS_PER_QUERY: 64 — How many chunk rows one `getChunks` call may return.
|
|
141
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`.
|
|
143
142
|
- `MatchmakingMode` (type): type MatchmakingMode = "auto" | "singleton" — How auto-match behaves when no `serverId` is supplied.
|
|
144
143
|
- `OnlinePlayer` (type): type OnlinePlayer = { serverId: string; userId: string; homeGameId?: string } — One active player delivered to an online-system batch.
|
|
145
144
|
- `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.
|
|
146
145
|
- `PresenceSyncResult` (type): type PresenceSyncResult = { pose: { x: number; y: number; z: number; rotationY: number; rotationPitch: number }; lastSeenAt: number; displaced: boolean; } — What `sync` hands back: the pose the server holds after clamping, and whether this session had been displaced.
|
|
147
|
-
- `RunCommandArgs` (type): type RunCommandArgs = { serverId: string; command: string; input: unknown; externalId?: string; /** * How much of the server to hydrate before applying the command. Overrides whatever the * {@link CommandDef} declares through its own `scope`; omit both to hydrate the whole world. */ scope?: LoadSnap… — Arguments of the `runCommand` mutation, and of the equivalent {@link GameServerHelpers.runCommand} helper.
|
|
148
146
|
- `RunCommandOutcome` (type): type RunCommandOutcome = { ok: true } | { ok: false; reason: string } — Outcome of a runtime command: applied, or refused with a reason (unknown server, non-member, validation, revision conflict).
|
|
149
147
|
- `ServerDoc` (type): type ServerDoc = DocumentByName<JGDataModel, "jgGameServers"> — A `jgGameServers` row — the server handle the persistence helpers load from and write back to.
|
|
150
148
|
- `canJoinPrivateServer` (function): function canJoinPrivateServer(args: { isMember: boolean; joinCode: string | undefined; suppliedCode: string | undefined; }): boolean — Private-server join-code gate. Existing members always pass; non-members must present a matching `joinCode` (loose-normalized via {@link normalizeJoinCode}). Callers still decide whether the server is private — this only answers the code/membership half.
|
|
@@ -195,7 +193,6 @@
|
|
|
195
193
|
- `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.
|
|
196
194
|
- `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
195
|
- `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
196
|
- `VoiceParticipant` (interface): interface VoiceParticipant { userId: string; streamId?: string } — ⚠ undocumented
|
|
200
197
|
- `VoiceRoute` (interface): interface VoiceRoute { fromUserId: string; channelId: string; gain: number } — ⚠ undocumented
|
|
201
198
|
- `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.
|
|
@@ -213,14 +210,13 @@
|
|
|
213
210
|
- `toRuntimeObjectRow` (function): function toRuntimeObjectRow(object: SceneObject): RuntimeObjectRow — Convert a live placed object into its persisted row — the bridge between `ctx.scene.object` and `GameRuntimeSnapshot.server.objects`/`chunks[].objects`. Per-instance `state` is carried as `flags`. Presentation overrides (`visual`, `animation`) are catalog- and authoring-resolved and deliberately stay off the persisted row — put anything a command must own in `state`.
|
|
214
211
|
- `validateFeedWrite` (function): function validateFeedWrite(gate: FeedWriteGate | undefined, action: string): { ok: true } | { ok: false; reason: string } — ⚠ undocumented
|
|
215
212
|
|
|
216
|
-
## @jgengine/core/multiplayer/
|
|
213
|
+
## @jgengine/core/multiplayer/JoinGate.view
|
|
217
214
|
|
|
218
|
-
- `
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
- `
|
|
223
|
-
- `createLocalChatTransport` (function): function createLocalChatTransport(options?: { userId?: string; historyLimit?: number; now?: () => number; }): { transport: ChatTransport; sync: ChatSync; actions: ChatActions; } — ⚠ undocumented
|
|
215
|
+
- `JoinGate` (function): function JoinGate({ status, failureReason, retry, joiningLabel = "Joining world…", failedLabel = "Unable to join", retryLabel = "Retry", className, children, renderJoining, renderFailure }: { status: "joining" | "joined" | "failed"; failureReason?: string | null; retry: () => void; joiningLabel?: Re… — Blocking join feedback; callers may supply their own copy and classes.
|
|
216
|
+
|
|
217
|
+
## @jgengine/core/multiplayer/authoritativeSync
|
|
218
|
+
|
|
219
|
+
- `AuthoritativeFrameHandler` (type): type AuthoritativeFrameHandler = (revision: number) => void — Receives the revision of an authoritative world frame after it is applied locally.
|
|
224
220
|
|
|
225
221
|
## @jgengine/core/multiplayer/combatSnapshot
|
|
226
222
|
|
|
@@ -245,14 +241,30 @@
|
|
|
245
241
|
- `resolveGuestSession` (function): function resolveGuestSession(seed?: string): AuthSession — ⚠ undocumented
|
|
246
242
|
- `sessionPlayer` (function): function sessionPlayer(session: AuthSession): PlayerIdentity — ⚠ undocumented
|
|
247
243
|
|
|
244
|
+
## @jgengine/core/multiplayer/identity.view
|
|
245
|
+
|
|
246
|
+
- `BetterAuthSessionState` (interface): interface BetterAuthSessionState { data: {user: BetterAuthUserShape} | null | undefined; isPending: boolean } — ⚠ undocumented
|
|
247
|
+
- `BetterAuthUserShape` (interface): interface BetterAuthUserShape { id: string; name?: string | null; email?: string | null; image?: string | null } — ⚠ undocumented
|
|
248
|
+
- `ClerkUserShape` (interface): interface ClerkUserShape { id: string; fullName?: string | null; username?: string | null; imageUrl?: string | null; primaryEmailAddress?: {emailAddress: string} | null; createdAt?: Date | null; lastSignInAt?: Date | null } — ⚠ undocumented
|
|
249
|
+
- `ClerkUserState` (interface): interface ClerkUserState { isLoaded: boolean; isSignedIn: boolean | undefined; user: ClerkUserShape | null | undefined } — ⚠ undocumented
|
|
250
|
+
- `GameIdentityProvider` (function): function GameIdentityProvider({ source, children, }: { source: IdentitySource; children?: ReactNode; }): React.JSX.Element — ⚠ undocumented
|
|
251
|
+
- `IdentitySource` (interface): interface IdentitySource { session: AuthSession | null; isLoading: boolean; signOut?: () => void } — ⚠ undocumented
|
|
252
|
+
- `RequireSession` (function): function RequireSession({ fallback, loading, children, }: { fallback?: ReactNode; loading?: ReactNode; children?: ReactNode; }): React.JSX.Element — ⚠ undocumented
|
|
253
|
+
- `SignOutButton` (function): function SignOutButton({ className, children, }: { className?: string; children?: ReactNode; }): React.JSX.Element | null — ⚠ undocumented
|
|
254
|
+
- `UserBadge` (function): function UserBadge({ className, avatarClassName, nameClassName, renderBadge, }: { className?: string; avatarClassName?: string; nameClassName?: string; renderBadge?: (session: AuthSession) => ReactNode; }): React.JSX.Element | null — ⚠ undocumented
|
|
255
|
+
- `betterAuthIdentity` (function): function betterAuthIdentity(state: BetterAuthSessionState, options?: { signOut?: () => void }): IdentitySource — ⚠ undocumented
|
|
256
|
+
- `clerkIdentity` (function): function clerkIdentity(state: ClerkUserState, options?: { signOut?: () => void }): IdentitySource — ⚠ undocumented
|
|
257
|
+
- `guestIdentity` (function): function guestIdentity(seed?: string): IdentitySource — ⚠ undocumented
|
|
258
|
+
- `useAuthedPlayer` (function): function useAuthedPlayer(options?: { guestSeed?: string }): PlayerIdentity | null — ⚠ undocumented
|
|
259
|
+
- `useSession` (function): function useSession(): IdentitySource — ⚠ undocumented
|
|
260
|
+
|
|
248
261
|
## @jgengine/core/multiplayer/lagCompensation
|
|
249
262
|
|
|
250
|
-
- `HitscanHit` (interface): interface HitscanHit { entityId: string; distance: number; point:
|
|
251
|
-
- `HitscanRay` (interface): interface HitscanRay { origin:
|
|
263
|
+
- `HitscanHit` (interface): interface HitscanHit { entityId: string; distance: number; point: Xyz } — ⚠ undocumented
|
|
264
|
+
- `HitscanRay` (interface): interface HitscanRay { origin: Xyz; direction: Xyz; maxDistance?: number } — ⚠ undocumented
|
|
252
265
|
- `HitscanTarget` (interface): interface HitscanTarget { entityId: string; radius: number } — ⚠ undocumented
|
|
253
266
|
- `PositionHistoryConfig` (interface): interface PositionHistoryConfig { historyMs: number; maxSamples?: number } — ⚠ undocumented
|
|
254
|
-
- `PositionSample` (interface): interface PositionSample extends
|
|
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.
|
|
267
|
+
- `PositionSample` (interface): interface PositionSample extends Xyz { t: number } — ⚠ undocumented
|
|
256
268
|
|
|
257
269
|
## @jgengine/core/multiplayer/matchmaking
|
|
258
270
|
|
|
@@ -269,6 +281,17 @@
|
|
|
269
281
|
- `normalizeJoinCode` (function): function normalizeJoinCode(code: string): string — ⚠ undocumented
|
|
270
282
|
- `quickMatch` (function): function quickMatch(listings: readonly SessionListing[], filter: MatchFilter = {}): SessionListing | null — ⚠ undocumented
|
|
271
283
|
|
|
284
|
+
## @jgengine/core/multiplayer/multiplayer.draw
|
|
285
|
+
|
|
286
|
+
- `DEFAULT_FEED_ACTIONS` (const): const DEFAULT_FEED_ACTIONS: string[] — ⚠ undocumented
|
|
287
|
+
- `PeerShellFactories` (interface): interface PeerShellFactories — Alternate peer factories for hosts that provide their own WebRTC implementation or transport pipe.
|
|
288
|
+
- `ResolvePeerShellMultiplayerArgs` (interface): interface ResolvePeerShellMultiplayerArgs — Peer bootstrap options; passing a playable hosts its authoritative GameContext world.
|
|
289
|
+
- `ResolveShellMultiplayerArgs` (type): type ResolveShellMultiplayerArgs = { game: GameDefinition; gameId: string; url?: string; userId?: string; force?: boolean; feedActions?: string[]; } — ⚠ undocumented
|
|
290
|
+
- `ShellMultiplayer` (type): type ShellMultiplayer = MultiplayerSession — ⚠ undocumented · used by `resolvePeerShellMultiplayer`: Opens a peer session.
|
|
291
|
+
- `randomPlayerId` (function): function randomPlayerId(): string — ⚠ undocumented
|
|
292
|
+
- `resolvePeerShellMultiplayer` (function): function resolvePeerShellMultiplayer(args: ResolvePeerShellMultiplayerArgs): Promise<ShellMultiplayer & { close: () => Promise<void> }> — Opens a peer session. With `playable`, one host runs the game loop and guests mirror its shared world. Await `close()` to finish authoritative persistence. Same-origin rooms use BroadcastChannel signaling.
|
|
293
|
+
- `resolveShellMultiplayer` (function): function resolveShellMultiplayer(args: ResolveShellMultiplayerArgs): ShellMultiplayer | null — ⚠ undocumented
|
|
294
|
+
|
|
272
295
|
## @jgengine/core/multiplayer/poseSyncGate
|
|
273
296
|
|
|
274
297
|
- `DEFAULT_POSE_SYNC_TUNING` (const): const DEFAULT_POSE_SYNC_TUNING: PoseSyncTuning — Default ten-hertz client pose gate with a five-second heartbeat.
|
|
@@ -308,6 +331,20 @@
|
|
|
308
331
|
- `SealResult` (type): type SealResult = | { ok: true; allSealed: boolean } | { ok: false; reason: "unknown_participant" | "already_sealed" | "already_revealed" } — ⚠ undocumented
|
|
309
332
|
- `SealedCommit` (interface): interface SealedCommit<TAction> { playerId: string; action: TAction; sealedAt: number } — ⚠ undocumented
|
|
310
333
|
|
|
334
|
+
## @jgengine/core/multiplayer/useShellMultiplayerSync.draw
|
|
335
|
+
|
|
336
|
+
- `useShellMultiplayerSync` (function): function useShellMultiplayerSync(ctx: GameContext | null, multiplayer: ShellMultiplayer | null, playable: PlayableGame, serverIdRef: { current: string | null }, setRemotePlayers: Dispatch<SetStateAction<PresencePoseRow[]>>, authoritativeFrameRef?: { current: AuthoritativeFrameHandler | null }): { st… — Joins the multiplayer server for the live context and wires presence, feed relay, and chat sync until teardown.
|
|
337
|
+
|
|
338
|
+
## @jgengine/core/multiplayer/voice.view
|
|
339
|
+
|
|
340
|
+
- `MicToggle` (function): function MicToggle({ voice, className, mutedLabel, unmutedLabel, }: { voice: VoiceState; className?: string; mutedLabel?: ReactNode; unmutedLabel?: ReactNode; }): React.JSX.Element — ⚠ undocumented
|
|
341
|
+
- `PushToTalkButton` (function): function PushToTalkButton({ voice, className, children, }: { voice: VoiceState; className?: string; children?: ReactNode; }): React.JSX.Element — ⚠ undocumented
|
|
342
|
+
- `SpeakingIndicator` (function): function SpeakingIndicator({ voice, userId, className, threshold = 0.01, children, }: { voice: VoiceState; userId: string; className?: string; threshold?: number; children?: ReactNode; }): React.JSX.Element — ⚠ undocumented
|
|
343
|
+
- `UseVoiceOptions` (interface): interface UseVoiceOptions { transport?: VoiceTransport; channelId?: string; mode?: PushToTalkMode; resolveRoutes?: () => readonly VoiceRoute[]; getUserMedia?: (constraints: MediaStreamConstraints) => Promise<MediaStream> } — ⚠ undocumented · used by `useVoice`: Mic capture + push-to-talk + channel roster over the VoiceTransport signaling seam.
|
|
344
|
+
- `VoiceRoster` (function): function VoiceRoster({ voice, className, participantClassName, renderParticipant, }: { voice: VoiceState; className?: string; participantClassName?: string; renderParticipant?: (participant: VoiceParticipant, gain: number) => ReactNode; }): React.JSX.Element — ⚠ undocumented
|
|
345
|
+
- `VoiceState` (interface): interface VoiceState { supported: boolean; micStream: MediaStream | null; micError: string | null; requestMic(): Promise<boolean>; transmitting: boolean; status: PushToTalkStatus; mode: PushToTalkMode; setMode(mode: PushToTalkMode): void; muted: boolean; setMut… — ⚠ undocumented · used by `useVoice`: Mic capture + push-to-talk + channel roster over the VoiceTransport signaling seam.
|
|
346
|
+
- `useVoice` (function): function useVoice(options?: UseVoiceOptions): VoiceState — Mic capture + push-to-talk + channel roster over the VoiceTransport signaling seam. Transmission gates the captured tracks' `enabled` flag; the media plane that actually moves audio bytes (WebRTC/SFU) stays behind the transport, host-supplied. Call once per voice channel and hand the returned state to the voice components.
|
|
347
|
+
|
|
311
348
|
## @jgengine/core/multiplayer/voiceContract
|
|
312
349
|
|
|
313
350
|
- `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
|
|
@@ -328,13 +365,13 @@
|
|
|
328
365
|
- `DEFAULT_HEARTBEAT_INTERVAL_MS` (const): const DEFAULT_HEARTBEAT_INTERVAL_MS: 30000 — Default ping/pong interval; a socket that misses one round-trip is terminated.
|
|
329
366
|
- `DEFAULT_MAX_CONNECTIONS` (const): const DEFAULT_MAX_CONNECTIONS: 10000 — Default max concurrent sockets this server accepts before rejecting new ones.
|
|
330
367
|
- `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.
|
|
331
|
-
- `
|
|
332
|
-
- `
|
|
368
|
+
- `GameServerHost` (type): type GameServerHost = { joinServer: (args: { userId: string; gameId: string; serverId?: string; attributes?: SessionAttributes; code?: string; role?: SnapshotViewer["role"]; }) => Promise<JoinServerResult>; browseServers: (args: { gameId: string; filter?: MatchFilter; limit?: number; }) => Promise<S… — A transport-agnostic authoritative game server host that manages sessions, ticking, and persistence.
|
|
369
|
+
- `GameServerHostOptions` (type): type GameServerHostOptions = { runtimes?: GameRuntime[]; persistence: HostPersistence; tickMs?: number; slotsPerServer?: number; now?: () => number; createServerId?: () => string; allowedFeedActions?: readonly string[]; } — Configuration for {@link createGameServerHost}, including persistence, tick rate, and game runtimes.
|
|
333
370
|
- `GameSocketIoServer` (type): type GameSocketIoServer = { rewind: (args: { serverId: string; atMs: number }) => RewoundPosition[]; close: () => void; } — ⚠ undocumented
|
|
334
371
|
- `GameSocketIoServerOptions` (type): type GameSocketIoServerOptions = HostRouterOptions & { io: SocketIoLikeServer } — ⚠ undocumented
|
|
335
372
|
- `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
|
|
336
373
|
- `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
|
|
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 `
|
|
374
|
+
- `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 `GameServerHost` for a server, player, or feed mutation.
|
|
338
375
|
- `HostedGameDefinition` (interface): interface HostedGameDefinition — A game the world server can host — its authoritative {@link GameDefinition} and the content lookup a `GameContext` reads.
|
|
339
376
|
- `InstallShutdownHookOptions` (interface): interface InstallShutdownHookOptions — Config for {@link installShutdownHook}.
|
|
340
377
|
- `NodeHandler` (type): type NodeHandler = (req: IncomingMessage, res: ServerResponse) => void — ⚠ undocumented
|
|
@@ -349,7 +386,7 @@
|
|
|
349
386
|
- `WorldPersistenceKey` (interface): interface WorldPersistenceKey — Per-world key a {@link WorldPersistence} resolves a {@link HostedWorldStore} for.
|
|
350
387
|
- `attachGameSocketIoServer` (function): function attachGameSocketIoServer(options: GameSocketIoServerOptions): GameSocketIoServer — ⚠ undocumented
|
|
351
388
|
- `clearFilePersistence` (function): function clearFilePersistence(dir: string): Promise<void> — ⚠ undocumented
|
|
352
|
-
- `
|
|
389
|
+
- `createGameServerHost` (function): function createGameServerHost(options: GameServerHostOptions): GameServerHost — Creates a `GameServerHost` that runs game servers over the given persistence and runtimes.
|
|
353
390
|
- `createGameWsServer` (function): function createGameWsServer(options: GameWsServerOptions): GameWsServer — ⚠ undocumented
|
|
354
391
|
- `createWorldGameServer` (function): function createWorldGameServer(options: WorldGameServerOptions): WorldGameServer — Build a {@link WorldGameServer} — one process hosting authoritative GameContext worlds over ws, ready for two-client play once {@link WorldGameServer.start} runs.
|
|
355
392
|
- `filePersistence` (function): function filePersistence(dir: string, now: () => number = Date.now): HostPersistence — ⚠ undocumented
|
|
@@ -368,6 +405,7 @@
|
|
|
368
405
|
|
|
369
406
|
- `EDITOR_SCENE_FILENAME` (const): const EDITOR_SCENE_FILENAME: "editor.scene.json" — Filename editor scene documents are saved under inside a game's `src/`.
|
|
370
407
|
- `SAVE_ENDPOINT_PATH` (const): const SAVE_ENDPOINT_PATH: "/__jgengine/save" — Route the dev save middleware answers on; matches `installSaveEndpoint` defaults.
|
|
408
|
+
- `SrcDirResolver` (type): type SrcDirResolver = (gameId: string) => string | null — Maps a save request's game id to the game `src/` directory it writes into, or `null` for an unknown game.
|
|
371
409
|
- `devSavePlugin` (function): function devSavePlugin(resolveSrcDir: SrcDirResolver): DevSavePluginShape — Dev-only Vite middleware that gives the F2 chord family its write path: editor mode's Save (Ctrl+S / `save_scene`) writes `editor.scene.json` and debug mode's Tune "Save to source" rewrites tunable literals, both into the directory `resolveSrcDir(gameId)` returns.
|
|
372
410
|
- `handleSaveRequest` (function): function handleSaveRequest(resolveSrcDir: SrcDirResolver, body: string): SaveEndpointResponse — Applies one parsed save request against a resolved game `src/` directory.
|
|
373
411
|
- `standaloneSavePlugin` (function): function standaloneSavePlugin(rootDir: string = process.cwd()): DevSavePluginShape — `devSavePlugin` preset for a standalone game project (one game, `src/` at the project root): every gameId maps to `<rootDir>/src`. Drop into the scaffolded `vite.config.ts` plugins array.
|
|
@@ -384,10 +422,10 @@
|
|
|
384
422
|
|
|
385
423
|
## @jgengine/node/host
|
|
386
424
|
|
|
387
|
-
- `
|
|
388
|
-
- `
|
|
389
|
-
- `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 `
|
|
390
|
-
- `
|
|
425
|
+
- `GameServerHost` (type): type GameServerHost = { joinServer: (args: { userId: string; gameId: string; serverId?: string; attributes?: SessionAttributes; code?: string; role?: SnapshotViewer["role"]; }) => Promise<JoinServerResult>; browseServers: (args: { gameId: string; filter?: MatchFilter; limit?: number; }) => Promise<S… — A transport-agnostic authoritative game server host that manages sessions, ticking, and persistence.
|
|
426
|
+
- `GameServerHostOptions` (type): type GameServerHostOptions = { runtimes?: GameRuntime[]; persistence: HostPersistence; tickMs?: number; slotsPerServer?: number; now?: () => number; createServerId?: () => string; allowedFeedActions?: readonly string[]; } — Configuration for {@link createGameServerHost}, including persistence, tick rate, and game runtimes.
|
|
427
|
+
- `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 `GameServerHost` for a server, player, or feed mutation.
|
|
428
|
+
- `createGameServerHost` (function): function createGameServerHost(options: GameServerHostOptions): GameServerHost — Creates a `GameServerHost` that runs game servers over the given persistence and runtimes.
|
|
391
429
|
- `memoryPersistence` (function): function memoryPersistence(now: () => number = Date.now): HostPersistence — Creates an in-memory `HostPersistence` implementation, useful for tests and ephemeral hosts.
|
|
392
430
|
|
|
393
431
|
## @jgengine/node/persistence
|
|
@@ -481,14 +519,14 @@
|
|
|
481
519
|
- `DEFAULT_COMMAND_LIMITS` (const): const DEFAULT_COMMAND_LIMITS: CommandLimits — Recommended per-op limits a host can opt into via `limits: DEFAULT_COMMAND_LIMITS`. Rate limiting is off unless `limits` is set.
|
|
482
520
|
- `DEFAULT_GRACE_MS` (const): const DEFAULT_GRACE_MS: 15000 — Default reconnect grace period for hosted socket sessions.
|
|
483
521
|
- `DEFAULT_POSE_RULES` (const): const DEFAULT_POSE_RULES: PoseSyncRules — ⚠ undocumented
|
|
484
|
-
- `
|
|
485
|
-
- `
|
|
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 `
|
|
522
|
+
- `GameServerHost` (type): type GameServerHost = { joinServer: (args: { userId: string; gameId: string; serverId?: string; attributes?: SessionAttributes; code?: string; role?: SnapshotViewer["role"]; }) => Promise<JoinServerResult>; browseServers: (args: { gameId: string; filter?: MatchFilter; limit?: number; }) => Promise<S… — A transport-agnostic authoritative game server host that manages sessions, ticking, and persistence.
|
|
523
|
+
- `GameServerHostOptions` (type): type GameServerHostOptions = { runtimes?: GameRuntime[]; persistence: HostPersistence; tickMs?: number; slotsPerServer?: number; now?: () => number; createServerId?: () => string; allowedFeedActions?: readonly string[]; } — Configuration for {@link createGameServerHost}, including persistence, tick rate, and game runtimes.
|
|
524
|
+
- `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 `GameServerHost` for a server, player, or feed mutation.
|
|
487
525
|
- `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.
|
|
488
526
|
- `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
|
|
489
527
|
- `HostRouterAuthenticate` (type): type HostRouterAuthenticate = (args: { userId: string; token?: string; }) => Promise<string | null> | string | null — ⚠ undocumented
|
|
490
528
|
- `HostRouterConnection` (type): type HostRouterConnection = { handleRaw: (raw: unknown) => void; close: () => void; } — ⚠ undocumented
|
|
491
|
-
- `HostRouterOptions` (type): type HostRouterOptions = { host:
|
|
529
|
+
- `HostRouterOptions` (type): type HostRouterOptions = { host: GameServerHost; authenticate?: HostRouterAuthenticate; allowAnonymous?: boolean; singleSession?: boolean; poseRules?: PoseSyncRules; positionHistoryMs?: number; chatRateLimit?: ChatRateLimit; chatHistoryLimit?: number; chatMaxBodyLength?: number; /** Per-op rate limi… — ⚠ undocumented
|
|
492
530
|
- `HostRouterTransport` (type): type HostRouterTransport = { send: (data: string) => void; close: () => void; } — ⚠ undocumented
|
|
493
531
|
- `HttpReads` (type): type HttpReads = { getTop: (args: { stat: string; scope: LeaderboardScope; serverId?: string; limit?: number; }) => Promise<LeaderboardEntry[]>; getLeaderboardProfile: (userId: string) => Promise<Record<string, number>>; getPlayerProfile: (userId: string) => Promise<GameRuntimePlayerView | null>; li… — ⚠ undocumented
|
|
494
532
|
- `HttpReadsOptions` (type): type HttpReadsOptions = { baseUrl: string; gameId: string; fetchImpl?: typeof fetch; } — ⚠ undocumented
|
|
@@ -498,12 +536,11 @@
|
|
|
498
536
|
- `MAX_FEED_ACTION_LENGTH` (const): const MAX_FEED_ACTION_LENGTH: 256 — Max length of a `pushFeed` action name, in UTF-16 code units.
|
|
499
537
|
- `MAX_FEED_ENTRY_BYTES` (const): const MAX_FEED_ENTRY_BYTES: 65536 — Max serialized size of a `pushFeed` entry payload, in bytes.
|
|
500
538
|
- `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.
|
|
502
539
|
- `OP_LEDGER_LIMIT` (const): const OP_LEDGER_LIMIT: 64 — Max recently-applied `runCommand` op IDs retained per (serverId, userId), oldest evicted first.
|
|
503
540
|
- `PeerGuest` (type): type PeerGuest = { backend: WsBackend; offer: () => Promise<string>; connect: (answerCode: string) => Promise<void>; close: () => void; } — ⚠ undocumented
|
|
504
541
|
- `PeerGuestOptions` (type): type PeerGuestOptions = { userId: string; token?: string; rtc?: PeerRtcOptions; } — ⚠ undocumented
|
|
505
|
-
- `PeerHost` (type): type PeerHost = { backend: WsBackend; host:
|
|
506
|
-
- `PeerHostOptions` (type): type PeerHostOptions = { userId: string; token?: string; host?:
|
|
542
|
+
- `PeerHost` (type): type PeerHost = { backend: WsBackend; host: GameServerHost; router: HostRouter; accept: (offerCode: string) => Promise<string>; close: () => void; } — ⚠ undocumented
|
|
543
|
+
- `PeerHostOptions` (type): type PeerHostOptions = { userId: string; token?: string; host?: GameServerHost; runtimes?: GameRuntime[]; persistence?: HostPersistence; tickMs?: number; router?: Omit<HostRouterOptions, "host">; rtc?: PeerRtcOptions; } — ⚠ undocumented
|
|
507
544
|
- `PeerRtcOptions` (type): type PeerRtcOptions = { configuration?: RTCConfiguration; iceGatherTimeoutMs?: number; } — ⚠ undocumented
|
|
508
545
|
- `PeerSignalPayload` (type): type PeerSignalPayload = { type: "offer" | "answer"; sdp: string } — ⚠ undocumented
|
|
509
546
|
- `PeerSignaling` (type): type PeerSignaling = { publishOffer: (offerCode: string) => Promise<string>; onOffer: (answer: (offerCode: string) => Promise<string>) => () => void; close: () => void; } — ⚠ undocumented
|
|
@@ -522,9 +559,8 @@
|
|
|
522
559
|
- `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
|
|
523
560
|
- `VoiceMemberId` (type): type VoiceMemberId = string — ⚠ undocumented
|
|
524
561
|
- `VoicePosition` (interface): interface VoicePosition { x: number; y: number; z: number } — ⚠ undocumented
|
|
525
|
-
- `VoiceRoute` (interface): interface VoiceRoute { fromUserId: VoiceMemberId; channelId: VoiceChannelId; gain: number } — ⚠ undocumented
|
|
526
562
|
- `WS_PROTOCOL_VERSION` (const): const WS_PROTOCOL_VERSION: 1 — ⚠ undocumented
|
|
527
|
-
- `WorldGameHost` (interface): interface WorldGameHost extends
|
|
563
|
+
- `WorldGameHost` (interface): interface WorldGameHost extends GameServerHost — A {@link GameServerHost} backed by `HostedWorldSession`s; `tick` advances worlds and `unload` explicitly saves and releases idle world references.
|
|
528
564
|
- `WorldGameHostOptions` (interface): interface WorldGameHostOptions — Config for {@link createWorldGameHost}: how to resolve a hosted world's authoritative session per server.
|
|
529
565
|
- `WsAppearance` (type): type WsAppearance = Record<string, string | number | boolean> — Client-set cosmetic/state tags carried alongside a pose (skin, mount, emote, ...). Primitive values only.
|
|
530
566
|
- `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
|
|
@@ -550,7 +586,7 @@
|
|
|
550
586
|
- `computeVoiceGain` (function): function computeVoiceGain(def: VoiceChannelDef, distance: number | null): number — ⚠ undocumented
|
|
551
587
|
- `createCommandMiddleware` (function): function createCommandMiddleware(options: CommandMiddlewareOptions): CommandMiddleware — Builds the composed command middleware pipeline from game-intent config: `limits`, `validate`, `authorize`.
|
|
552
588
|
- `createCommandRateLimiter` (function): function createCommandRateLimiter(limits: CommandLimits): CommandRateLimiter — Creates a sliding-window rate limiter keyed by connection identity and op; ops absent from `limits` are always allowed.
|
|
553
|
-
- `
|
|
589
|
+
- `createGameServerHost` (function): function createGameServerHost(options: GameServerHostOptions): GameServerHost — Creates a `GameServerHost` that runs game servers over the given persistence and runtimes.
|
|
554
590
|
- `createHostRouter` (function): function createHostRouter(options: HostRouterOptions): HostRouter — ⚠ undocumented
|
|
555
591
|
- `createHttpReads` (function): function createHttpReads(options: HttpReadsOptions): HttpReads — ⚠ undocumented
|
|
556
592
|
- `createPeerGuest` (function): function createPeerGuest(options: PeerGuestOptions): PeerGuest — ⚠ undocumented
|
|
@@ -558,7 +594,7 @@
|
|
|
558
594
|
- `createReadsHandler` (function): function createReadsHandler(options: ReadsHandlerOptions): ReadsHandler — ⚠ undocumented
|
|
559
595
|
- `createSocketIoBackend` (function): function createSocketIoBackend(options: SocketIoBackendOptions): WsBackend — ⚠ undocumented
|
|
560
596
|
- `createVoiceChannelRouter` (function): function createVoiceChannelRouter(channels?: readonly VoiceChannelDef[]): VoiceChannelRouter — ⚠ undocumented
|
|
561
|
-
- `createWorldGameHost` (function): function createWorldGameHost(options: WorldGameHostOptions): WorldGameHost — The GameContext-loop counterpart of the reducer `
|
|
597
|
+
- `createWorldGameHost` (function): function createWorldGameHost(options: WorldGameHostOptions): WorldGameHost — The GameContext-loop counterpart of the reducer `createGameServerHost`: a structural {@link GameServerHost} 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.
|
|
562
598
|
- `createWsBackend` (function): function createWsBackend(options: WsBackendOptions): WsBackend — ⚠ undocumented
|
|
563
599
|
- `decodePeerSignal` (function): function decodePeerSignal(code: string): PeerSignalPayload | null — ⚠ undocumented
|
|
564
600
|
- `encodePeerSignal` (function): function encodePeerSignal(payload: PeerSignalPayload): string — ⚠ undocumented
|
|
@@ -598,11 +634,11 @@
|
|
|
598
634
|
|
|
599
635
|
## @jgengine/ws/host
|
|
600
636
|
|
|
601
|
-
- `
|
|
602
|
-
- `
|
|
603
|
-
- `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 `
|
|
637
|
+
- `GameServerHost` (type): type GameServerHost = { joinServer: (args: { userId: string; gameId: string; serverId?: string; attributes?: SessionAttributes; code?: string; role?: SnapshotViewer["role"]; }) => Promise<JoinServerResult>; browseServers: (args: { gameId: string; filter?: MatchFilter; limit?: number; }) => Promise<S… — A transport-agnostic authoritative game server host that manages sessions, ticking, and persistence.
|
|
638
|
+
- `GameServerHostOptions` (type): type GameServerHostOptions = { runtimes?: GameRuntime[]; persistence: HostPersistence; tickMs?: number; slotsPerServer?: number; now?: () => number; createServerId?: () => string; allowedFeedActions?: readonly string[]; } — Configuration for {@link createGameServerHost}, including persistence, tick rate, and game runtimes.
|
|
639
|
+
- `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 `GameServerHost` for a server, player, or feed mutation.
|
|
604
640
|
- `OP_LEDGER_LIMIT` (const): const OP_LEDGER_LIMIT: 64 — Max recently-applied `runCommand` op IDs retained per (serverId, userId), oldest evicted first.
|
|
605
|
-
- `
|
|
641
|
+
- `createGameServerHost` (function): function createGameServerHost(options: GameServerHostOptions): GameServerHost — Creates a `GameServerHost` that runs game servers over the given persistence and runtimes.
|
|
606
642
|
- `memoryPersistence` (function): function memoryPersistence(now: () => number = Date.now): HostPersistence — Creates an in-memory `HostPersistence` implementation, useful for tests and ephemeral hosts.
|
|
607
643
|
|
|
608
644
|
## @jgengine/ws/hostRouter
|
|
@@ -612,7 +648,7 @@
|
|
|
612
648
|
- `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
|
|
613
649
|
- `HostRouterAuthenticate` (type): type HostRouterAuthenticate = (args: { userId: string; token?: string; }) => Promise<string | null> | string | null — ⚠ undocumented
|
|
614
650
|
- `HostRouterConnection` (type): type HostRouterConnection = { handleRaw: (raw: unknown) => void; close: () => void; } — ⚠ undocumented
|
|
615
|
-
- `HostRouterOptions` (type): type HostRouterOptions = { host:
|
|
651
|
+
- `HostRouterOptions` (type): type HostRouterOptions = { host: GameServerHost; authenticate?: HostRouterAuthenticate; allowAnonymous?: boolean; singleSession?: boolean; poseRules?: PoseSyncRules; positionHistoryMs?: number; chatRateLimit?: ChatRateLimit; chatHistoryLimit?: number; chatMaxBodyLength?: number; /** Per-op rate limi… — ⚠ undocumented
|
|
616
652
|
- `HostRouterTransport` (type): type HostRouterTransport = { send: (data: string) => void; close: () => void; } — ⚠ undocumented
|
|
617
653
|
- `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.
|
|
618
654
|
- `RewoundPosition` (type): type RewoundPosition = { userId: string; x: number; y: number; z: number; } — A player's interpolated position sampled from history at a past timestamp.
|
|
@@ -629,8 +665,8 @@
|
|
|
629
665
|
|
|
630
666
|
- `PeerGuest` (type): type PeerGuest = { backend: WsBackend; offer: () => Promise<string>; connect: (answerCode: string) => Promise<void>; close: () => void; } — ⚠ undocumented
|
|
631
667
|
- `PeerGuestOptions` (type): type PeerGuestOptions = { userId: string; token?: string; rtc?: PeerRtcOptions; } — ⚠ undocumented
|
|
632
|
-
- `PeerHost` (type): type PeerHost = { backend: WsBackend; host:
|
|
633
|
-
- `PeerHostOptions` (type): type PeerHostOptions = { userId: string; token?: string; host?:
|
|
668
|
+
- `PeerHost` (type): type PeerHost = { backend: WsBackend; host: GameServerHost; router: HostRouter; accept: (offerCode: string) => Promise<string>; close: () => void; } — ⚠ undocumented
|
|
669
|
+
- `PeerHostOptions` (type): type PeerHostOptions = { userId: string; token?: string; host?: GameServerHost; runtimes?: GameRuntime[]; persistence?: HostPersistence; tickMs?: number; router?: Omit<HostRouterOptions, "host">; rtc?: PeerRtcOptions; } — ⚠ undocumented
|
|
634
670
|
- `PeerRtcOptions` (type): type PeerRtcOptions = { configuration?: RTCConfiguration; iceGatherTimeoutMs?: number; } — ⚠ undocumented
|
|
635
671
|
- `PeerSignalPayload` (type): type PeerSignalPayload = { type: "offer" | "answer"; sdp: string } — ⚠ undocumented
|
|
636
672
|
- `PeerSignaling` (type): type PeerSignaling = { publishOffer: (offerCode: string) => Promise<string>; onOffer: (answer: (offerCode: string) => Promise<string>) => () => void; close: () => void; } — ⚠ undocumented
|
|
@@ -656,7 +692,6 @@
|
|
|
656
692
|
- `MAX_COMMAND_LENGTH` (const): const MAX_COMMAND_LENGTH: 4096 — Max length of a `runCommand` command name, in UTF-16 code units.
|
|
657
693
|
- `MAX_FEED_ACTION_LENGTH` (const): const MAX_FEED_ACTION_LENGTH: 256 — Max length of a `pushFeed` action name, in UTF-16 code units.
|
|
658
694
|
- `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.
|
|
660
695
|
- `WS_PROTOCOL_VERSION` (const): const WS_PROTOCOL_VERSION: 1 — ⚠ undocumented
|
|
661
696
|
- `WsAppearance` (type): type WsAppearance = Record<string, string | number | boolean> — Client-set cosmetic/state tags carried alongside a pose (skin, mount, emote, ...). Primitive values only.
|
|
662
697
|
- `WsBrowseResult` (type): type WsBrowseResult = SessionListing[] — ⚠ undocumented
|
|
@@ -695,12 +730,11 @@
|
|
|
695
730
|
- `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
|
|
696
731
|
- `VoiceMemberId` (type): type VoiceMemberId = string — ⚠ undocumented
|
|
697
732
|
- `VoicePosition` (interface): interface VoicePosition { x: number; y: number; z: number } — ⚠ undocumented
|
|
698
|
-
- `VoiceRoute` (interface): interface VoiceRoute { fromUserId: VoiceMemberId; channelId: VoiceChannelId; gain: number } — ⚠ undocumented
|
|
699
733
|
- `computeVoiceGain` (function): function computeVoiceGain(def: VoiceChannelDef, distance: number | null): number — ⚠ undocumented
|
|
700
734
|
- `createVoiceChannelRouter` (function): function createVoiceChannelRouter(channels?: readonly VoiceChannelDef[]): VoiceChannelRouter — ⚠ undocumented
|
|
701
735
|
|
|
702
736
|
## @jgengine/ws/worldHost
|
|
703
737
|
|
|
704
|
-
- `WorldGameHost` (interface): interface WorldGameHost extends
|
|
738
|
+
- `WorldGameHost` (interface): interface WorldGameHost extends GameServerHost — A {@link GameServerHost} backed by `HostedWorldSession`s; `tick` advances worlds and `unload` explicitly saves and releases idle world references.
|
|
705
739
|
- `WorldGameHostOptions` (interface): interface WorldGameHostOptions — Config for {@link createWorldGameHost}: how to resolve a hosted world's authoritative session per server.
|
|
706
|
-
- `createWorldGameHost` (function): function createWorldGameHost(options: WorldGameHostOptions): WorldGameHost — The GameContext-loop counterpart of the reducer `
|
|
740
|
+
- `createWorldGameHost` (function): function createWorldGameHost(options: WorldGameHostOptions): WorldGameHost — The GameContext-loop counterpart of the reducer `createGameServerHost`: a structural {@link GameServerHost} 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.
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
<!-- GENERATED by scripts/gen-capability-index.ts — do not edit. Regenerate: bun run gen
|
|
1
|
+
<!-- GENERATED by scripts/gen-capability-index.ts — do not edit. Regenerate: bun run gen capabilities -->
|
|
2
2
|
|
|
3
3
|
# jgengine-multiplayer — capability index
|
|
4
4
|
|
|
5
5
|
Reach for these before hand-rolling. Each row is *the thing you need* → *the primitive that already does it*.
|
|
6
6
|
|
|
7
|
+
## authoritative-host — run authoritative game servers with sessions, ticking and persistence behind any transport
|
|
8
|
+
|
|
9
|
+
- `createGameServerHost` (function) · `import { createGameServerHost } from "@jgengine/ws"`
|
|
10
|
+
|
|
7
11
|
## client-error-reporting — Accept authenticated bounded diagnostics with cooldown and retention sweeps.
|
|
8
12
|
|
|
9
13
|
- `createClientErrorFunctions` (function) · `import { createClientErrorFunctions } from "@jgengine/convex"`
|
|
@@ -45,6 +49,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
45
49
|
- `fileWorldStore` (function) · `import { fileWorldStore } from "@jgengine/node"`
|
|
46
50
|
- `sqlWorldStore` (function) · `import { sqlWorldStore } from "@jgengine/sql"`
|
|
47
51
|
|
|
52
|
+
## join-gate — block gameplay until joined and show failures with retry
|
|
53
|
+
|
|
54
|
+
- `JoinGate` (function) · `import { JoinGate } from "@jgengine/core/multiplayer/JoinGate.view"`
|
|
55
|
+
|
|
56
|
+
## multiplayer-p2p — bootstrap a playable authoritative world for browser peer co-op
|
|
57
|
+
|
|
58
|
+
- `resolvePeerShellMultiplayer` (function) · `import { resolvePeerShellMultiplayer } from "@jgengine/core/multiplayer/multiplayer.draw"`
|
|
59
|
+
|
|
48
60
|
## online-player-batches — Schedule bounded active-player or home-game work in separate transactions.
|
|
49
61
|
|
|
50
62
|
- `forEachOnlinePlayer` (function) · `import { forEachOnlinePlayer } from "@jgengine/convex"`
|