@jgengine/node 0.18.0 → 0.19.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 +265 -2
- package/dist/fileWorldStore.d.ts +5 -0
- package/dist/fileWorldStore.js +25 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/persistence.js +2 -2
- package/dist/worldServer.js +6 -7
- package/package.json +3 -3
- package/skills/ce-handoff/SKILL.md +2 -2
- package/skills/jgengine/SKILL.md +9 -5
- package/skills/jgengine/api.md +205 -101
- package/skills/jgengine/capabilities.md +56 -0
- package/skills/jgengine/recipes/minimal-game.md +3 -0
- package/skills/jgengine-multiplayer/SKILL.md +25 -0
- package/skills/jgengine-multiplayer/api.md +159 -109
- package/skills/jgengine-multiplayer/capabilities.md +37 -0
|
@@ -12,10 +12,30 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
12
12
|
|
|
13
13
|
- `collectAuthoredTriggers` (function) · `import { collectAuthoredTriggers } from "@jgengine/shell/gameKit"`
|
|
14
14
|
|
|
15
|
+
## chase-view-cycle — cycle a vehicle camera between chase, hood and cockpit views at runtime
|
|
16
|
+
|
|
17
|
+
- `nextChaseView` (function) · `import { nextChaseView } from "@jgengine/core/runtime/cameraDirector"`
|
|
18
|
+
|
|
19
|
+
## client-side — input prediction with replay reconciliation
|
|
20
|
+
|
|
21
|
+
- `createPredictionBuffer` (function) · `import { createPredictionBuffer } from "@jgengine/core/runtime/prediction"`
|
|
22
|
+
|
|
15
23
|
## collider-autofit — hitboxes and physical bodies fit each kind's rendered model bounds automatically — no hand-tuned sizes
|
|
16
24
|
|
|
17
25
|
- `GameContextModels` (interface) · `import { GameContextModels } from "@jgengine/core/runtime/gameContext"`
|
|
18
26
|
|
|
27
|
+
## command-number — validate finite, bounded numbers and integers at command boundaries
|
|
28
|
+
|
|
29
|
+
- `readInputNumber` (function) · `import { readInputNumber } from "@jgengine/core/runtime/commandInput"`
|
|
30
|
+
|
|
31
|
+
## command-parse — parse untrusted command input once so every command stage sees typed, bounded values
|
|
32
|
+
|
|
33
|
+
- `defineCommand` (function) · `import { defineCommand } from "@jgengine/core/runtime/commandRunner"`
|
|
34
|
+
|
|
35
|
+
## command-quantity — validate bounded whole-item counts at command boundaries
|
|
36
|
+
|
|
37
|
+
- `readQuantity` (function) · `import { readQuantity } from "@jgengine/core/runtime/commandInput"`
|
|
38
|
+
|
|
19
39
|
## default-walk-codes — stock WASD + jump key codes for the shell walk controller
|
|
20
40
|
|
|
21
41
|
- `DEFAULT_WALK_CODES` (const) · `import { DEFAULT_WALK_CODES } from "@jgengine/shell/gameKit"`
|
|
@@ -32,6 +52,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
32
52
|
|
|
33
53
|
- `EditorCatalogDefinition` (interface) · `import { EditorCatalogDefinition } from "@jgengine/shell/gameKit"`
|
|
34
54
|
|
|
55
|
+
## fire-input — bind a key or mouse button to a named action that runs the same-named command with the aim; repeatMs auto-fires while held
|
|
56
|
+
|
|
57
|
+
- `ActionCodesMap` (type) · `import { ActionCodesMap } from "@jgengine/core/authoring"`
|
|
58
|
+
|
|
35
59
|
## game-system — declare a composable capability with its own schedule and lifecycle
|
|
36
60
|
|
|
37
61
|
- `SystemDefinition` (interface) · `import { SystemDefinition } from "@jgengine/shell/gameKit"`
|
|
@@ -84,6 +108,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
84
108
|
|
|
85
109
|
- `StatBar` (function) · `import { StatBar } from "@jgengine/shell/gameKit"`
|
|
86
110
|
|
|
111
|
+
## input-recorder — tick-indexed input log for replay, rollback, and regression tests
|
|
112
|
+
|
|
113
|
+
- `createInputRecorder` (function) · `import { createInputRecorder } from "@jgengine/core/runtime/inputRecorder"`
|
|
114
|
+
|
|
115
|
+
## local-players — Couch co-op seats: devices hot-join into slots, each with its own user id and input snapshot.
|
|
116
|
+
|
|
117
|
+
- `createLocalPlayers` (function) · `import { createLocalPlayers } from "@jgengine/core/runtime/localPlayers"`
|
|
118
|
+
|
|
87
119
|
## loot-table — validate a loot table definition for use with the registry
|
|
88
120
|
|
|
89
121
|
- `lootTable` (function) · `import { lootTable } from "@jgengine/core/authoring"`
|
|
@@ -116,6 +148,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
116
148
|
|
|
117
149
|
- `toRuntimeObjectRow` (function) · `import { toRuntimeObjectRow } from "@jgengine/core/runtime/objectRows"`
|
|
118
150
|
|
|
151
|
+
## pose-interpolation — render entity poses between fixed simulation steps without stutter
|
|
152
|
+
|
|
153
|
+
- `createPoseHistory` (function) · `import { createPoseHistory } from "@jgengine/core/runtime/poseInterpolation"`
|
|
154
|
+
|
|
119
155
|
## render-fallback-diagnostics — Enable/disable the dev-only placeholder-fallback probe.
|
|
120
156
|
|
|
121
157
|
- `armFallbackSeams` (function) · `import { armFallbackSeams } from "@jgengine/core/devtools/fallbackSeams"`
|
|
@@ -126,6 +162,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
126
162
|
- `resetTextureErrors` (function) · `import { resetTextureErrors } from "@jgengine/core/devtools/textureErrors"`
|
|
127
163
|
- `textureErrorsSnapshot` (function) · `import { textureErrorsSnapshot } from "@jgengine/core/devtools/textureErrors"`
|
|
128
164
|
|
|
165
|
+
## runtime — snapshot interpolation
|
|
166
|
+
|
|
167
|
+
- `createSnapshotBuffer` (function) · `import { createSnapshotBuffer } from "@jgengine/core/runtime/snapshotBuffer"`
|
|
168
|
+
|
|
129
169
|
## runtime-save — save/load the whole game world through a pluggable backend, autosave or save points
|
|
130
170
|
|
|
131
171
|
- `createRuntimeSave` (function) · `import { createRuntimeSave } from "@jgengine/core/runtime/runtimeSave"`
|
|
@@ -142,6 +182,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
142
182
|
|
|
143
183
|
- `seededRng` (function) · `import { seededRng } from "@jgengine/shell/gameKit"`
|
|
144
184
|
|
|
185
|
+
## sim-loop — fixed-step or variable simulation stepping with tick count, catch-up cap, and interpolation alpha
|
|
186
|
+
|
|
187
|
+
- `createSimLoop` (function) · `import { createSimLoop } from "@jgengine/core/runtime/simLoop"`
|
|
188
|
+
|
|
189
|
+
## sim-snapshot — capture and restore whole-simulation state for replay, rollback, and regression tests
|
|
190
|
+
|
|
191
|
+
- `createSimSnapshotRegistry` (function) · `import { createSimSnapshotRegistry } from "@jgengine/core/runtime/simSnapshot"`
|
|
192
|
+
|
|
145
193
|
## typed-keyed-store — a cast-free typed handle onto a per-owner keyed family of reactive game-store slots, replayable and host-authoritative
|
|
146
194
|
|
|
147
195
|
- `defineKeyedStore` (function) · `import { defineKeyedStore } from "@jgengine/shell/gameKit"`
|
|
@@ -150,6 +198,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
150
198
|
|
|
151
199
|
- `defineStore` (function) · `import { defineStore } from "@jgengine/shell/gameKit"`
|
|
152
200
|
|
|
201
|
+
## warn-once — emit a dev-only warning a single time per call site
|
|
202
|
+
|
|
203
|
+
- `warnOnce` (function) · `import { warnOnce } from "@jgengine/core/devtools/warnOnce"`
|
|
204
|
+
|
|
153
205
|
## world-chunk-key — map world positions to persisted chunk keys at a declared cell size
|
|
154
206
|
|
|
155
207
|
- `chunkKeyOf` (function) · `import { chunkKeyOf } from "@jgengine/core/runtime/worldChunks"`
|
|
@@ -165,3 +217,7 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
165
217
|
## world-environment — compose editor/preset-derived outdoor dressing — terrain, weather, water, structures — into a legacy environment feature
|
|
166
218
|
|
|
167
219
|
- `environment` (function) · `import { environment } from "@jgengine/shell/gameKit"`
|
|
220
|
+
|
|
221
|
+
## world-item-pickup — pick up dropped items: ctx.scene.worldItem nearestInRadius → pickup into the player's inventory
|
|
222
|
+
|
|
223
|
+
- `SceneWorldItemContext` (interface) · `import { SceneWorldItemContext } from "@jgengine/core/runtime/gameContext"`
|
|
@@ -45,6 +45,9 @@ spatial place. When the game *is* a place, declare only substrate + laws with `w
|
|
|
45
45
|
`@jgengine/core/world/place` (e.g. `{ id, ground: { mode: "flat", size: { x: Infinity, z: Infinity } }, physics: { gravity: -24 } }`,
|
|
46
46
|
or `mode: "board"` for 2D). Never seed, sky, or vegetation in `world` — the engine renders its
|
|
47
47
|
default sky until you author one in the editor, and foliage is editor scatter over the ground.
|
|
48
|
+
A created 3D game instead ships hills as a terrain sculpt in `editor.scene.json` and a `world.ts`
|
|
49
|
+
that consumes it with `environment({ sculpt })` via `environmentContentFromDocument`; the file
|
|
50
|
+
holds only the ground palette. Reshape the hills in the editor, not in code.
|
|
48
51
|
|
|
49
52
|
|
|
50
53
|
```ts
|
|
@@ -45,3 +45,28 @@ Use [capabilities.md](capabilities.md) for intent-to-import discovery, [api.md](
|
|
|
45
45
|
- Presence/chat/voice are session channels, not game-state ownership.
|
|
46
46
|
- Do not couple a primitive to WebSocket, Convex, Postgres, or one deployment topology.
|
|
47
47
|
- Reward allocation, inventory mutation, and progression policy remain gameplay/combat concerns.
|
|
48
|
+
|
|
49
|
+
## Shared-world capacity and persistence
|
|
50
|
+
|
|
51
|
+
| Topology | Membership and session state | Command reads | Presence fan-out |
|
|
52
|
+
| --- | --- | --- | --- |
|
|
53
|
+
| Rooms | **256 members maximum** in the room document | Declared actor/player and chunk scope | Neighborhood index |
|
|
54
|
+
| Shared | Indexed `jgServerMembers`, separate `jgServerCapacity`, per-user profile session state; no 256-member engine cap | Declared actor/player and chunk scope; player-only writes avoid the world row | Neighborhood index, 10 Hz maximum default pose writes |
|
|
55
|
+
|
|
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
|
+
|
|
58
|
+
Every gameplay command declares `scope(input)`: use `{ players: "actor", chunkKeys: [] }` for player-only work and `chunkKeysAround(position, 1)` for spatial work. The omitted-command default is actor-only with no chunks; an explicit `{}` requests the whole world and needs a justified bounded workload. Hosts use `helpers.runCommand` and its `beforePersist` callback to pair game writes with membership and revision checks in one transaction. Never copy that check/apply/persist loop into a game. Declare commands with `defineCommand({ parse, scope, validate, apply })` and build `parse` from the `readInput*` readers in `@jgengine/core/runtime/commandInput` (finite, bounded numbers and points, capped strings and arrays): the runner refuses malformed input before `scope`, `validate` or `apply` run, so those stages never re-parse or trust raw values. Treat every field of a command's `input` as client-controlled: a command whose input carries facts only the host may decide (unlock state, prices, multipliers, whether a target exists) declares `access: "server"`, so the public `runCommand` and WebSocket clients are refused and only a host mutation calling `helpers.runCommand` (or `host.runCommand({ trusted: true })`) runs it. Read time from the `nowMs` argument, never from `input`.
|
|
59
|
+
|
|
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
|
+
|
|
62
|
+
Persist only dirty profiles/chunks. Join/leave are roster writes; they do not dirty or rewrite the world. Reset a player with `helpers.resetPlayerProfile`, which runs the same initializer used by joins, and keep the shared world intact.
|
|
63
|
+
|
|
64
|
+
## Scheduled work
|
|
65
|
+
|
|
66
|
+
A cron never collects a whole table. Use presence-gated `forEachOnlinePlayer` with a bounded `batchSize`, an internal batch handler, and a continuation reference: each page schedules its handler and the next page as separate transactions. `OnlinePlayer.homeGameId` carries optional application identity from its pose row. Persist elapsed-time anchors with effects; use `accrueSince` to cap offline catch-up. `allServers` is only for explicitly global systems with a bounded server budget.
|
|
67
|
+
|
|
68
|
+
Pass runtimes to `jgengineCronSpecs` so no tick cron registers when no runtime declares `onTick`; set per-spec `intervalSeconds` deliberately. Retention sweeps and one-shot backfills are bounded, report `remaining`, and stop at convergence. Delete completed migration crons instead of leaving hourly table scans installed forever.
|
|
69
|
+
|
|
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
|
+
|
|
72
|
+
For the explicit shared-builder composition, `jgengine create "World Name" --shape shared-world-builder` generates the connected Convex shell, runtime scope declarations, claim/accrual examples, chat, indexed presence, and an online batch pipeline. The generated README owns setup; replace the demonstration economy and author buildable world content in the editor.
|