@jgengine/convex 0.50.0 → 0.52.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 +1363 -0
- package/dist/createConvexGameTransport.js +13 -0
- package/dist/hostedServer.d.ts +1 -1
- package/dist/hostedServer.js +3 -2
- package/dist/server.d.ts +23 -0
- package/dist/server.js +31 -8
- package/package.json +2 -2
- package/skills/jgengine/SKILL.md +43 -63
- package/skills/jgengine/api.md +26 -9
- package/skills/jgengine/capabilities.md +8 -0
- package/skills/jgengine/recipes/cookbook.md +752 -0
- package/skills/jgengine-multiplayer/SKILL.md +9 -0
- package/skills/jgengine-multiplayer/api.md +37 -20
- package/skills/jgengine-multiplayer/references/peer-worlds.md +1 -1
|
@@ -39,6 +39,15 @@ Use [capabilities.md](capabilities.md) for intent-to-import discovery, [api.md](
|
|
|
39
39
|
- Persistence adapters accept structural interfaces and keep backend dependencies out of core.
|
|
40
40
|
- Deterministic ids and commands make retries/reconnect safe.
|
|
41
41
|
|
|
42
|
+
## Rates and wire defaults
|
|
43
|
+
|
|
44
|
+
- Every host ticks at 20 Hz (`DEFAULT_HOST_TICK_MS`, 50 ms) unless the game passes `tickMs`; `resolveHostTickMs` rejects values above 1000 ms. The Convex hosted default (`JG_HOSTED_TICK_MS`) is the same constant, but a Convex world only advances as often as its cron fires.
|
|
45
|
+
- `createWsBackend` speaks the binary delta codec by default: per-entity field deltas against the previous frame, quantised poses, varint entity ids. Pass `codec: "json"` for readable frames while debugging. Precision is the host's: `createHostRouter({ precision: { position: 2, angle: 3 } })` (decimal places, 0 to 6).
|
|
46
|
+
- A viewer's interest is the game's `replication: { aoiRadius, aoiAlways }`. The host asks the scene spatial index for entities within `aoiRadius` of the viewer's own entity, adds `aoiAlways` ids (a list or a function), and sends each connection a diff of only what entered, changed or left its view. A viewer with no entity yet holds every entity.
|
|
47
|
+
- A hosted world auto-saves a changed world at most once per `saveIntervalMs` (the node server and the browser host default to `DEFAULT_HOST_SAVE_INTERVAL_MS`, 1000); `0` saves after every changed tick, which copies the whole world 20 times a second. `bun scripts/multiplayer-load.ts` reports host ms per tick, and bytes allocated per tick under `BUN_JSC_useGC=false`.
|
|
48
|
+
- A module that throttles itself with `priority` still gets a projected baseline each tick instead of diffs.
|
|
49
|
+
- Measure with `bun scripts/multiplayer-load.ts` (bytes and host ms per tick for N clients and E entities) and `bun scripts/multiplayer-two-clients.ts` (a real game, two clients).
|
|
50
|
+
|
|
42
51
|
## Traps
|
|
43
52
|
|
|
44
53
|
- Local loopback success does not prove authority or privacy.
|
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
- `DEFAULT_CONVEX_POSE_RULES` (const): const DEFAULT_CONVEX_POSE_RULES: PoseSyncRules — ⚠ undocumented
|
|
19
19
|
- `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.
|
|
20
20
|
- `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.
|
|
21
|
-
- `GameServerHelpers` (type): type GameServerHelpers = { ensureServer: (ctx: JGMutationCtx, gameId: string) => Promise<ServerDoc>;
|
|
21
|
+
- `GameServerHelpers` (type): type GameServerHelpers = { ensureServer: (ctx: JGMutationCtx, gameId: string) => Promise<ServerDoc>; /** The shared singleton world `ensureServer` would return, read without creating it, so a query can scope reads to it. `null` until a player or host mutation creates it. */ findServer: (ctx: { db: P… — 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.
|
|
22
22
|
- `HostedGameConfig` (interface): interface HostedGameConfig — One game the hosted path can serve, bound per `gameId`: its definition plus content lookup.
|
|
23
23
|
- `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.
|
|
24
24
|
- `HostedWorldOutcome` (interface): interface HostedWorldOutcome<T> — What one stateless invocation produced: the op's value, the resulting revision, whether the world changed (and was saved back), and the post-op roster.
|
|
25
25
|
- `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.
|
|
26
26
|
- `JGMutationCtx` (type): type JGMutationCtx = GenericMutationCtx<JGDataModel> — Mutation ctx accepted by {@link loadServerSnapshot} / {@link persistServerSnapshot}.
|
|
27
27
|
- `JGQueryCtx` (type): type JGQueryCtx = GenericQueryCtx<JGDataModel> — Read-only context accepted by engine host queries.
|
|
28
|
-
- `JG_HOSTED_TICK_MS` (const): const JG_HOSTED_TICK_MS:
|
|
28
|
+
- `JG_HOSTED_TICK_MS` (const): const JG_HOSTED_TICK_MS: 50 — Default minimum elapsed ms before `tickHostedWorlds` advances a world — override via the factory's `tickMs`.
|
|
29
29
|
- `JG_MAX_MEMBERS_PER_SERVER` (const): const JG_MAX_MEMBERS_PER_SERVER: 256 — Room-topology ceiling on `slotsPerServer`; shared membership uses indexed rows instead.
|
|
30
30
|
- `JG_RUNTIME_TICK_MS` (const): const JG_RUNTIME_TICK_MS: 1000 — ⚠ undocumented
|
|
31
31
|
- `JgAuthMode` (type): type JgAuthMode = "anonymous" | "required" — ⚠ undocumented
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
- `MAX_MEMBER_SESSIONS` (const): const MAX_MEMBER_SESSIONS: 16 — Most live client sessions one shared-topology membership remembers; the oldest drop first.
|
|
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
|
+
- `PresenceCheckpoint` (type): type PresenceCheckpoint = { userId: string; homeGameId?: string; position: { x: number; y: number; z: number }; rotationY: number; } — A live pose row handed to a `checkpoint` writer, so a host can persist its own last-position state.
|
|
39
40
|
- `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
41
|
- `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
42
|
- `REVISION_CONFLICT_REASON` (const): const REVISION_CONFLICT_REASON: "Revision conflict" — ⚠ undocumented
|
|
@@ -110,7 +111,7 @@
|
|
|
110
111
|
- `HostedGameConfig` (interface): interface HostedGameConfig — One game the hosted path can serve, bound per `gameId`: its definition plus content lookup.
|
|
111
112
|
- `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.
|
|
112
113
|
- `HostedWorldOutcome` (interface): interface HostedWorldOutcome<T> — What one stateless invocation produced: the op's value, the resulting revision, whether the world changed (and was saved back), and the post-op roster.
|
|
113
|
-
- `JG_HOSTED_TICK_MS` (const): const JG_HOSTED_TICK_MS:
|
|
114
|
+
- `JG_HOSTED_TICK_MS` (const): const JG_HOSTED_TICK_MS: 50 — Default minimum elapsed ms before `tickHostedWorlds` advances a world — override via the factory's `tickMs`.
|
|
114
115
|
|
|
115
116
|
## @jgengine/convex/occ
|
|
116
117
|
|
|
@@ -127,7 +128,7 @@
|
|
|
127
128
|
- `DEFAULT_CONVEX_POSE_RULES` (const): const DEFAULT_CONVEX_POSE_RULES: PoseSyncRules — ⚠ undocumented
|
|
128
129
|
- `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
130
|
- `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.
|
|
130
|
-
- `GameServerHelpers` (type): type GameServerHelpers = { ensureServer: (ctx: JGMutationCtx, gameId: string) => Promise<ServerDoc>;
|
|
131
|
+
- `GameServerHelpers` (type): type GameServerHelpers = { ensureServer: (ctx: JGMutationCtx, gameId: string) => Promise<ServerDoc>; /** The shared singleton world `ensureServer` would return, read without creating it, so a query can scope reads to it. `null` until a player or host mutation creates it. */ findServer: (ctx: { db: P… — 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.
|
|
131
132
|
- `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.
|
|
132
133
|
- `JGMutationCtx` (type): type JGMutationCtx = GenericMutationCtx<JGDataModel> — Mutation ctx accepted by {@link loadServerSnapshot} / {@link persistServerSnapshot}.
|
|
133
134
|
- `JGQueryCtx` (type): type JGQueryCtx = GenericQueryCtx<JGDataModel> — Read-only context accepted by engine host queries.
|
|
@@ -141,6 +142,7 @@
|
|
|
141
142
|
- `MAX_MEMBER_SESSIONS` (const): const MAX_MEMBER_SESSIONS: 16 — Most live client sessions one shared-topology membership remembers; the oldest drop first.
|
|
142
143
|
- `MatchmakingMode` (type): type MatchmakingMode = "auto" | "singleton" — How auto-match behaves when no `serverId` is supplied.
|
|
143
144
|
- `OnlinePlayer` (type): type OnlinePlayer = { serverId: string; userId: string; homeGameId?: string } — One active player delivered to an online-system batch.
|
|
145
|
+
- `PresenceCheckpoint` (type): type PresenceCheckpoint = { userId: string; homeGameId?: string; position: { x: number; y: number; z: number }; rotationY: number; } — A live pose row handed to a `checkpoint` writer, so a host can persist its own last-position state.
|
|
144
146
|
- `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.
|
|
145
147
|
- `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.
|
|
146
148
|
- `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).
|
|
@@ -378,7 +380,7 @@
|
|
|
378
380
|
- `RewoundPosition` (type): type RewoundPosition = { userId: string; x: number; y: number; z: number; } — A player's interpolated position sampled from history at a past timestamp.
|
|
379
381
|
- `ShutdownHook` (interface): interface ShutdownHook — A live signal listener installed by {@link installShutdownHook}; call `remove()` to uninstall it (tests, embedders opting out).
|
|
380
382
|
- `SocketIoLikeServer` (type): type SocketIoLikeServer = { on: (event: "connection", listener: (socket: SocketIoLikeServerSocket) => void) => unknown; } — ⚠ undocumented
|
|
381
|
-
- `SocketIoLikeServerSocket` (type): type SocketIoLikeServerSocket = { on: (event: string, listener: (payload: string) => void) => unknown; send: (data: string) => unknown; disconnect: (close?: boolean) => unknown; } — ⚠ undocumented
|
|
383
|
+
- `SocketIoLikeServerSocket` (type): type SocketIoLikeServerSocket = { on: (event: string, listener: (payload: string | Uint8Array) => void) => unknown; send: (data: string | Uint8Array) => unknown; disconnect: (close?: boolean) => unknown; } — ⚠ undocumented
|
|
382
384
|
- `WebHandler` (type): type WebHandler = (request: Request) => Promise<Response> — ⚠ undocumented
|
|
383
385
|
- `WorldGameServer` (interface): interface WorldGameServer — A runnable ws host for GameContext worlds: {@link createWorldGameHost} + {@link createGameWsServer} + a tick loop, with a manual `tick(dt)` seam so a fake clock can drive it in tests.
|
|
384
386
|
- `WorldGameServerOptions` (interface): interface WorldGameServerOptions extends Omit<GameWsServerOptions, "host"> — Config for {@link createWorldGameServer}: how to resolve a game by id, the tick cadence, and the underlying ws-server/router options (minus `host`, which the server builds).
|
|
@@ -449,7 +451,7 @@
|
|
|
449
451
|
- `GameSocketIoServerOptions` (type): type GameSocketIoServerOptions = HostRouterOptions & { io: SocketIoLikeServer } — ⚠ undocumented
|
|
450
452
|
- `RewoundPosition` (type): type RewoundPosition = { userId: string; x: number; y: number; z: number; } — A player's interpolated position sampled from history at a past timestamp.
|
|
451
453
|
- `SocketIoLikeServer` (type): type SocketIoLikeServer = { on: (event: "connection", listener: (socket: SocketIoLikeServerSocket) => void) => unknown; } — ⚠ undocumented
|
|
452
|
-
- `SocketIoLikeServerSocket` (type): type SocketIoLikeServerSocket = { on: (event: string, listener: (payload: string) => void) => unknown; send: (data: string) => unknown; disconnect: (close?: boolean) => unknown; } — ⚠ undocumented
|
|
454
|
+
- `SocketIoLikeServerSocket` (type): type SocketIoLikeServerSocket = { on: (event: string, listener: (payload: string | Uint8Array) => void) => unknown; send: (data: string | Uint8Array) => unknown; disconnect: (close?: boolean) => unknown; } — ⚠ undocumented
|
|
453
455
|
- `attachGameSocketIoServer` (function): function attachGameSocketIoServer(options: GameSocketIoServerOptions): GameSocketIoServer — ⚠ undocumented
|
|
454
456
|
|
|
455
457
|
## @jgengine/node/testFixtures
|
|
@@ -526,8 +528,8 @@
|
|
|
526
528
|
- `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
|
|
527
529
|
- `HostRouterAuthenticate` (type): type HostRouterAuthenticate = (args: { userId: string; token?: string | undefined; }) => Promise<string | null> | string | null — ⚠ undocumented
|
|
528
530
|
- `HostRouterConnection` (type): type HostRouterConnection = { handleRaw: (raw: unknown) => void; close: () => void; } — ⚠ undocumented
|
|
529
|
-
- `HostRouterOptions` (type): type HostRouterOptions = { host: GameServerHost; authenticate?: HostRouterAuthenticate; allowAnonymous?: boolean; singleSession?: boolean; poseRules?: PoseSyncRules; positionHistoryMs?: number; chatRateLimit?: ChatRateLimit; chatHistoryLimit?: number; chatMaxBodyLength?: number; /**
|
|
530
|
-
- `HostRouterTransport` (type): type HostRouterTransport = { send: (data:
|
|
531
|
+
- `HostRouterOptions` (type): type HostRouterOptions = { host: GameServerHost; authenticate?: HostRouterAuthenticate; allowAnonymous?: boolean; singleSession?: boolean; poseRules?: PoseSyncRules; positionHistoryMs?: number; chatRateLimit?: ChatRateLimit; chatHistoryLimit?: number; chatMaxBodyLength?: number; /** Quantisation of … — ⚠ undocumented
|
|
532
|
+
- `HostRouterTransport` (type): type HostRouterTransport = { send: (data: WsFrame) => void; close: () => void; } — ⚠ undocumented
|
|
531
533
|
- `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
|
|
532
534
|
- `HttpReadsOptions` (type): type HttpReadsOptions = { baseUrl: string; gameId: string; fetchImpl?: typeof fetch; } — ⚠ undocumented
|
|
533
535
|
- `MAX_APPEARANCE_ENTRIES` (const): const MAX_APPEARANCE_ENTRIES: 32 — Max number of keys in a pose `appearance` tag map.
|
|
@@ -550,10 +552,11 @@
|
|
|
550
552
|
- `ReadsPersistence` (type): type ReadsPersistence = Pick< HostPersistence, "listServers" | "loadProfile" | "getLeaderboardTop" | "getLeaderboardProfile" > — ⚠ undocumented
|
|
551
553
|
- `RewoundPosition` (type): type RewoundPosition = { userId: string; x: number; y: number; z: number; } — A player's interpolated position sampled from history at a past timestamp.
|
|
552
554
|
- `SocketIoBackendOptions` (type): type SocketIoBackendOptions = Omit<WsBackendOptions, "url" | "pipe" | "webSocketFactory"> & { socket: SocketIoLikeSocket; } — ⚠ undocumented
|
|
553
|
-
- `SocketIoLikeSocket` (type): type SocketIoLikeSocket = { connected: boolean; on: (event: string, listener: (payload: string) => void) => unknown; off: (event: string, listener: (payload: string) => void) => unknown; send: (data: string) => unknown; disconnect: () => unknown; } — ⚠ undocumented
|
|
554
|
-
- `
|
|
555
|
+
- `SocketIoLikeSocket` (type): type SocketIoLikeSocket = { connected: boolean; on: (event: string, listener: (payload: string | Uint8Array) => void) => unknown; off: (event: string, listener: (payload: string | Uint8Array) => void) => unknown; send: (data: string | Uint8Array<ArrayBuffer>) => unknown; disconnect: () => unknown; } — ⚠ undocumented
|
|
556
|
+
- `TransportData` (type): type TransportData = string | Uint8Array<ArrayBuffer> — One transport message: JSON text, or bytes from the binary codec.
|
|
557
|
+
- `TransportPipe` (type): type TransportPipe = { send: (data: TransportData) => void; close: () => void; } — ⚠ undocumented
|
|
555
558
|
- `TransportPipeFactory` (type): type TransportPipeFactory = (handlers: TransportPipeHandlers) => TransportPipe — ⚠ undocumented
|
|
556
|
-
- `TransportPipeHandlers` (type): type TransportPipeHandlers = { onOpen: () => void; onMessage: (data: string) => void; onClose: () => void; } — ⚠ undocumented
|
|
559
|
+
- `TransportPipeHandlers` (type): type TransportPipeHandlers = { onOpen: () => void; onMessage: (data: string | Uint8Array) => void; onClose: () => void; } — ⚠ undocumented
|
|
557
560
|
- `VoiceChannelDef` (interface): interface VoiceChannelDef { id: VoiceChannelId; positional: boolean; falloff?: AudioFalloffConfig; gain?: number } — ⚠ undocumented
|
|
558
561
|
- `VoiceChannelId` (type): type VoiceChannelId = string — ⚠ undocumented
|
|
559
562
|
- `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
|
|
@@ -564,13 +567,15 @@
|
|
|
564
567
|
- `WorldGameHostOptions` (interface): interface WorldGameHostOptions — Config for {@link createWorldGameHost}: how to resolve a hosted world's authoritative session per server.
|
|
565
568
|
- `WsAppearance` (type): type WsAppearance = Record<string, string | number | boolean> — Client-set cosmetic/state tags carried alongside a pose (skin, mount, emote, ...). Primitive values only.
|
|
566
569
|
- `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
|
|
567
|
-
- `WsBackendOptions` (type): type WsBackendOptions = { url?: string; pipe?: TransportPipeFactory; userId: string; token?: string | undefined;
|
|
570
|
+
- `WsBackendOptions` (type): type WsBackendOptions = { url?: string; pipe?: TransportPipeFactory; userId: string; token?: string | undefined; /** Wire codec: `binary` (default, per-entity deltas with quantised poses) or `json` text frames for debugging. */ codec?: WsCodecName; webSocketFactory?: (url: string) => WebSocket; reco… — ⚠ undocumented
|
|
568
571
|
- `WsBrowseResult` (type): type WsBrowseResult = SessionListing[] — ⚠ undocumented
|
|
569
572
|
- `WsChannel` (type): type WsChannel = "server" | "player" | "feed" | "presence" | "chat" | "voice" — ⚠ undocumented
|
|
570
573
|
- `WsChatMessage` (type): type WsChatMessage = { id: string; channelId: string; fromUserId: string; body: string; at: number; } — ⚠ undocumented
|
|
571
574
|
- `WsChatSync` (type): type WsChatSync = { subscribe: ( serverId: string, channelId: string, onChange: (messages: WsChatMessage[]) => void, ) => () => void; send: (serverId: string, channelId: string, body: string) => Promise<ChatSendOutcome>; } — ⚠ undocumented
|
|
572
|
-
- `WsClientMessage` (type): type WsClientMessage = | { v: 1; t: "ping"; id: number; at: number } | { v: 1; t: "hello"; id: number; userId: string; token?: string | undefined } | { v: 1; t: "join"; id: number; gameId: string; serverId?: string | undefined; attributes?: SessionAttributes |
|
|
575
|
+
- `WsClientMessage` (type): type WsClientMessage = | { v: 1; t: "ping"; id: number; at: number } | { v: 1; t: "hello"; id: number; userId: string; token?: string | undefined; codec?: WsCodecName | undefined } | { v: 1; t: "join"; id: number; gameId: string; serverId?: string | undefined; attributes?: SessionAttributes | undefi… — ⚠ undocumented
|
|
576
|
+
- `WsCodecName` (type): type WsCodecName = "json" | "binary" — Wire codec a client asks for in `hello`: `binary` (default for {@link createWsBackend}) or `json` for debugging.
|
|
573
577
|
- `WsDecodeFailure` (type): type WsDecodeFailure = { reason: string; id?: number | undefined; } — ⚠ undocumented
|
|
578
|
+
- `WsFrame` (type): type WsFrame = string | Uint8Array<ArrayBuffer> — One transport frame: JSON text, or a binary frame from {@link encodeWsFrame}.
|
|
574
579
|
- `WsJoinByCodeResult` (type): type WsJoinByCodeResult = JoinServerResult | null — ⚠ undocumented
|
|
575
580
|
- `WsJoinResult` (type): type WsJoinResult = JoinServerResult — ⚠ undocumented
|
|
576
581
|
- `WsPose` (type): type WsPose = PlayerPose & { appearance?: WsAppearance | undefined } — ⚠ undocumented
|
|
@@ -605,6 +610,15 @@
|
|
|
605
610
|
- `validateCommandInput` (function): function validateCommandInput(catalog: CommandCatalog | undefined, command: string, input: unknown): CommandRejection | null — Validates a `runCommand` input against a declared catalog. `undefined` catalog means "no declarations" — everything passes through unchanged.
|
|
606
611
|
- `webSocketPipe` (function): function webSocketPipe(url: string, webSocketFactory: (url: string) => WebSocket = (target) => new WebSocket(target)): TransportPipeFactory — ⚠ undocumented
|
|
607
612
|
|
|
613
|
+
## @jgengine/ws/binaryWire
|
|
614
|
+
|
|
615
|
+
- `DEFAULT_WIRE_PRECISION` (const): const DEFAULT_WIRE_PRECISION: { readonly position: 2; readonly angle: 3; } — Default {@link WirePrecision}.
|
|
616
|
+
- `WIRE_DESYNC` (const): const WIRE_DESYNC: "desync" — Marks a binary frame the decoder could not apply to its state.
|
|
617
|
+
- `WIRE_MAGIC` (const): const WIRE_MAGIC: 177 — Binary wire format for the ws protocol. Every message is a JSON-shaped value, so one tagged value codec carries all of them; world frames add an entity-delta section that sends only the fields that changed against the last frame on that connection, with quantised poses and varint entity ids.
|
|
618
|
+
- `WireDecoder` (type): type WireDecoder = { /** The decoded message, `null` for a malformed frame, or `"desync"` when a world delta does not follow the last frame it saw (the receiver must resubscribe for a baseline). */ decode(frame: Uint8Array): unknown; } — Stateful binary decoder for one connection's incoming messages.
|
|
619
|
+
- `WireEncoder` (type): type WireEncoder = { encode(message: unknown): Uint8Array<ArrayBuffer> } — Stateful binary encoder for one connection's outgoing messages.
|
|
620
|
+
- `WirePrecision` (type): type WirePrecision = { /** Decimal places kept for positions and velocities. Default 2 (centimetres). */ position?: number | undefined; /** Decimal places kept for rotations. Default 3 (about a milliradian). */ angle?: number | undefined; } — Quantisation applied to replicated poses on the binary wire, in decimal places.
|
|
621
|
+
|
|
608
622
|
## @jgengine/ws/commandMiddleware
|
|
609
623
|
|
|
610
624
|
- `CommandAuthorize` (type): type CommandAuthorize = (args: { userId: string; op: HostCommandOp; serverId?: string | undefined; command?: string | undefined; }) => boolean | Promise<boolean> — Per-command authorization hook: return `false` to reject. Defaults to allow-all when omitted.
|
|
@@ -626,7 +640,7 @@
|
|
|
626
640
|
## @jgengine/ws/createWsBackend
|
|
627
641
|
|
|
628
642
|
- `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
|
|
629
|
-
- `WsBackendOptions` (type): type WsBackendOptions = { url?: string; pipe?: TransportPipeFactory; userId: string; token?: string | undefined;
|
|
643
|
+
- `WsBackendOptions` (type): type WsBackendOptions = { url?: string; pipe?: TransportPipeFactory; userId: string; token?: string | undefined; /** Wire codec: `binary` (default, per-entity deltas with quantised poses) or `json` text frames for debugging. */ codec?: WsCodecName; webSocketFactory?: (url: string) => WebSocket; reco… — ⚠ undocumented
|
|
630
644
|
- `WsChatSync` (type): type WsChatSync = { subscribe: ( serverId: string, channelId: string, onChange: (messages: WsChatMessage[]) => void, ) => () => void; send: (serverId: string, channelId: string, body: string) => Promise<ChatSendOutcome>; } — ⚠ undocumented
|
|
631
645
|
- `WsPresenceSync` (type): type WsPresenceSync = { subscribe: (serverId: string, onChange: (rows: WsPresenceRow[]) => void) => () => void; /** `pose.appearance`, when provided, is forwarded to the host as-is and surfaces on every subscriber's presence row for that user. */ syncPose: (serverId: string, pose: WsPose) => void; } — ⚠ undocumented
|
|
632
646
|
- `WsVoiceSync` (type): type WsVoiceSync = { subscribe: ( serverId: string, channelId: string, onChange: (participants: WsVoiceParticipant[]) => void, ) => () => void; join: (serverId: string, channelId: string, streamId?: string) => Promise<void>; leave: (serverId: string, channelId: string) => Promise<void>; publish: (se… — ⚠ undocumented
|
|
@@ -648,8 +662,8 @@
|
|
|
648
662
|
- `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
|
|
649
663
|
- `HostRouterAuthenticate` (type): type HostRouterAuthenticate = (args: { userId: string; token?: string | undefined; }) => Promise<string | null> | string | null — ⚠ undocumented
|
|
650
664
|
- `HostRouterConnection` (type): type HostRouterConnection = { handleRaw: (raw: unknown) => void; close: () => void; } — ⚠ undocumented
|
|
651
|
-
- `HostRouterOptions` (type): type HostRouterOptions = { host: GameServerHost; authenticate?: HostRouterAuthenticate; allowAnonymous?: boolean; singleSession?: boolean; poseRules?: PoseSyncRules; positionHistoryMs?: number; chatRateLimit?: ChatRateLimit; chatHistoryLimit?: number; chatMaxBodyLength?: number; /**
|
|
652
|
-
- `HostRouterTransport` (type): type HostRouterTransport = { send: (data:
|
|
665
|
+
- `HostRouterOptions` (type): type HostRouterOptions = { host: GameServerHost; authenticate?: HostRouterAuthenticate; allowAnonymous?: boolean; singleSession?: boolean; poseRules?: PoseSyncRules; positionHistoryMs?: number; chatRateLimit?: ChatRateLimit; chatHistoryLimit?: number; chatMaxBodyLength?: number; /** Quantisation of … — ⚠ undocumented
|
|
666
|
+
- `HostRouterTransport` (type): type HostRouterTransport = { send: (data: WsFrame) => void; close: () => void; } — ⚠ undocumented
|
|
653
667
|
- `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.
|
|
654
668
|
- `RewoundPosition` (type): type RewoundPosition = { userId: string; x: number; y: number; z: number; } — A player's interpolated position sampled from history at a past timestamp.
|
|
655
669
|
- `createHostRouter` (function): function createHostRouter(options: HostRouterOptions): HostRouter — ⚠ undocumented
|
|
@@ -680,9 +694,10 @@
|
|
|
680
694
|
|
|
681
695
|
## @jgengine/ws/pipe
|
|
682
696
|
|
|
683
|
-
- `
|
|
697
|
+
- `TransportData` (type): type TransportData = string | Uint8Array<ArrayBuffer> — One transport message: JSON text, or bytes from the binary codec.
|
|
698
|
+
- `TransportPipe` (type): type TransportPipe = { send: (data: TransportData) => void; close: () => void; } — ⚠ undocumented
|
|
684
699
|
- `TransportPipeFactory` (type): type TransportPipeFactory = (handlers: TransportPipeHandlers) => TransportPipe — ⚠ undocumented
|
|
685
|
-
- `TransportPipeHandlers` (type): type TransportPipeHandlers = { onOpen: () => void; onMessage: (data: string) => void; onClose: () => void; } — ⚠ undocumented
|
|
700
|
+
- `TransportPipeHandlers` (type): type TransportPipeHandlers = { onOpen: () => void; onMessage: (data: string | Uint8Array) => void; onClose: () => void; } — ⚠ undocumented
|
|
686
701
|
- `webSocketPipe` (function): function webSocketPipe(url: string, webSocketFactory: (url: string) => WebSocket = (target) => new WebSocket(target)): TransportPipeFactory — ⚠ undocumented
|
|
687
702
|
|
|
688
703
|
## @jgengine/ws/protocol
|
|
@@ -697,8 +712,10 @@
|
|
|
697
712
|
- `WsBrowseResult` (type): type WsBrowseResult = SessionListing[] — ⚠ undocumented
|
|
698
713
|
- `WsChannel` (type): type WsChannel = "server" | "player" | "feed" | "presence" | "chat" | "voice" — ⚠ undocumented
|
|
699
714
|
- `WsChatMessage` (type): type WsChatMessage = { id: string; channelId: string; fromUserId: string; body: string; at: number; } — ⚠ undocumented
|
|
700
|
-
- `WsClientMessage` (type): type WsClientMessage = | { v: 1; t: "ping"; id: number; at: number } | { v: 1; t: "hello"; id: number; userId: string; token?: string | undefined } | { v: 1; t: "join"; id: number; gameId: string; serverId?: string | undefined; attributes?: SessionAttributes |
|
|
715
|
+
- `WsClientMessage` (type): type WsClientMessage = | { v: 1; t: "ping"; id: number; at: number } | { v: 1; t: "hello"; id: number; userId: string; token?: string | undefined; codec?: WsCodecName | undefined } | { v: 1; t: "join"; id: number; gameId: string; serverId?: string | undefined; attributes?: SessionAttributes | undefi… — ⚠ undocumented
|
|
716
|
+
- `WsCodecName` (type): type WsCodecName = "json" | "binary" — Wire codec a client asks for in `hello`: `binary` (default for {@link createWsBackend}) or `json` for debugging.
|
|
701
717
|
- `WsDecodeFailure` (type): type WsDecodeFailure = { reason: string; id?: number | undefined; } — ⚠ undocumented
|
|
718
|
+
- `WsFrame` (type): type WsFrame = string | Uint8Array<ArrayBuffer> — One transport frame: JSON text, or a binary frame from {@link encodeWsFrame}.
|
|
702
719
|
- `WsJoinByCodeResult` (type): type WsJoinByCodeResult = JoinServerResult | null — ⚠ undocumented
|
|
703
720
|
- `WsJoinResult` (type): type WsJoinResult = JoinServerResult — ⚠ undocumented
|
|
704
721
|
- `WsPose` (type): type WsPose = PlayerPose & { appearance?: WsAppearance | undefined } — ⚠ undocumented
|
|
@@ -719,7 +736,7 @@
|
|
|
719
736
|
## @jgengine/ws/socketIoPipe
|
|
720
737
|
|
|
721
738
|
- `SocketIoBackendOptions` (type): type SocketIoBackendOptions = Omit<WsBackendOptions, "url" | "pipe" | "webSocketFactory"> & { socket: SocketIoLikeSocket; } — ⚠ undocumented
|
|
722
|
-
- `SocketIoLikeSocket` (type): type SocketIoLikeSocket = { connected: boolean; on: (event: string, listener: (payload: string) => void) => unknown; off: (event: string, listener: (payload: string) => void) => unknown; send: (data: string) => unknown; disconnect: () => unknown; } — ⚠ undocumented
|
|
739
|
+
- `SocketIoLikeSocket` (type): type SocketIoLikeSocket = { connected: boolean; on: (event: string, listener: (payload: string | Uint8Array) => void) => unknown; off: (event: string, listener: (payload: string | Uint8Array) => void) => unknown; send: (data: string | Uint8Array<ArrayBuffer>) => unknown; disconnect: () => unknown; } — ⚠ undocumented
|
|
723
740
|
- `createSocketIoBackend` (function): function createSocketIoBackend(options: SocketIoBackendOptions): WsBackend — ⚠ undocumented
|
|
724
741
|
- `socketIoPipe` (function): function socketIoPipe(socket: SocketIoLikeSocket): TransportPipeFactory — ⚠ undocumented
|
|
725
742
|
|
|
@@ -18,7 +18,7 @@ await session.close();
|
|
|
18
18
|
|
|
19
19
|
The resolver owns one tick timer, signaling, peers and persistence teardown.
|
|
20
20
|
Ticks skip worlds with no active members; only the canonical game/server id is
|
|
21
|
-
admitted. `tickMs` defaults to
|
|
21
|
+
admitted. `tickMs` defaults to 50 (20 Hz, `DEFAULT_HOST_TICK_MS`). Automatic saves default to a 1000 ms interval (`saveIntervalMs`); commands and
|
|
22
22
|
close retain their immediate save boundaries. `store` accepts an asynchronous
|
|
23
23
|
`HostedWorldStore` for restart recovery; omitted storage is memory-only.
|
|
24
24
|
`slotsPerServer` caps players inside the serialized world join queue; known live
|