@jgengine/convex 0.48.0 → 0.49.1
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 +219 -57
- package/package.json +2 -2
- package/skills/ce-handoff/SKILL.md +2 -2
- package/skills/jgengine/SKILL.md +8 -8
- package/skills/jgengine/api.md +37 -17
- package/skills/jgengine-multiplayer/api.md +39 -39
package/CHANGELOG.md
CHANGED
|
@@ -26,6 +26,223 @@ At publish, rename this heading to the new version and mirror the entries into
|
|
|
26
26
|
`packages/core/src/meta/changelog.ts` (the typed `CHANGELOG` export).
|
|
27
27
|
-->
|
|
28
28
|
|
|
29
|
+
## 0.49.1
|
|
30
|
+
|
|
31
|
+
### Migrate
|
|
32
|
+
|
|
33
|
+
- **Bump lockstep SDK packages to `^0.49.1`:** `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.46.1`; `@jgengine/github` is `0.36.1`.
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- bun run check type-escapes fails a file that gains an any, a non-null !, an as cast or a @ts-ignore; the per-file count lives in scripts/type-escape-baseline.json, bun run gen lowers it, and an unavoidable escape is excused by a // type-escape: <why> comment.
|
|
38
|
+
- `ActionCodeModes` (`@jgengine/core/input/actionBindings`): The hold/toggle form of an {@link ActionCodes} entry, with an optional auto-repeat rate.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- core, shell and ws type-check with noUncheckedIndexedAccess, exactOptionalPropertyTypes, noImplicitOverride, noPropertyAccessFromIndexSignature and noImplicitReturns. All 1460 errors are fixed with guards, real types and small helpers, not casts: a missing array slot, a missing record key or an optional field that may be undefined is now a compile error.
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- spreading a game's place physics over the base no longer erases base gravity or jump velocity with an undefined.
|
|
47
|
+
- the four ActionCodes readers (camera, gamepad, touch, rebind) share one isCodeList / allActionCodes / primaryActionCodes instead of four copies with casts.
|
|
48
|
+
|
|
49
|
+
## 0.49.0
|
|
50
|
+
|
|
51
|
+
### Migrate
|
|
52
|
+
|
|
53
|
+
- **Bump lockstep SDK packages to `^0.49.0`:** `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.46.0`; `@jgengine/github` is `0.36.0`.
|
|
54
|
+
- rewrite each proofs/flows/<name>.json as <name>.flow.ts with defineFlow, add "proofs" to the game's tsconfig include, and name the capture state in the flow (state: "...") instead of passing --state.
|
|
55
|
+
- `HeadlessRunner.step(dt)` advances all of `dt` now, in frames of at most `maxStepSeconds` (default 0.05 s); it used to clamp the step to one frame and drop the rest. Code that stepped many small frames keeps working; code that relied on the clamp gets the time it asked for.
|
|
56
|
+
- `HeadlessRunner.snapshot()`/`restore()` capture `ctx.state()` (every system's save) instead of the replication view.
|
|
57
|
+
- `createHeadlessRunner` turns on the walk controller when the game's input binds a walk action (`moveForward`, `moveBack`, `moveLeft`, `moveRight`, `jump`), as the shell does; pass `playerMovement: false` to keep it off. With no `loop`, it runs the definition's own loop (and so its `systems`).
|
|
58
|
+
- `@jgengine/shell/TerritoryOverlay` → `@jgengine/core/placement/TerritoryOverlay.view`
|
|
59
|
+
- `@jgengine/shell/render/CharacterCrowd` → `@jgengine/core/anim/CharacterCrowd.draw`
|
|
60
|
+
- `@jgengine/shell/render/CorpseRagdolls` → `@jgengine/core/anim/CorpseRagdolls.draw`
|
|
61
|
+
- `@jgengine/shell/render/InteriorDressing` → `@jgengine/core/buildings/InteriorDressing.draw`
|
|
62
|
+
- `@jgengine/shell/render/LivingWorldLayer` → `@jgengine/core/weather/LivingWorldLayer.draw`
|
|
63
|
+
- `@jgengine/shell/render/PartMotion` → `@jgengine/core/anim/PartMotion.draw`
|
|
64
|
+
- `@jgengine/shell/render/SkinnedInstances` → `@jgengine/core/anim/SkinnedInstances.draw`
|
|
65
|
+
- `@jgengine/shell/render/characterGlance` → `@jgengine/core/anim/characterGlance.draw`
|
|
66
|
+
- `@jgengine/shell/render/crowdBatch` → `@jgengine/core/anim/crowdBatch.draw`
|
|
67
|
+
- `@jgengine/shell/render/crowdRegistry` → `@jgengine/core/anim/crowdRegistry.draw`
|
|
68
|
+
- `@jgengine/shell/render/entityMuzzle` → `@jgengine/core/anim/entityMuzzle.draw`
|
|
69
|
+
- `@jgengine/shell/render/livingWorld` → `@jgengine/core/weather/livingWorld.draw`
|
|
70
|
+
- `@jgengine/shell/render/testFixtures/proceduralSpriteAtlas` → `@jgengine/core/render/testFixtures/proceduralSpriteAtlas.view`
|
|
71
|
+
- `@jgengine/shell/render/testFixtures/proceduralTileLayer` → `@jgengine/core/render/testFixtures/proceduralTileLayer.view`
|
|
72
|
+
- `@jgengine/shell/render/useEntityRenderCues` → `@jgengine/core/anim/useEntityRenderCues.draw`
|
|
73
|
+
- `@jgengine/shell/render/useFootIk` → `@jgengine/core/anim/useFootIk.draw`
|
|
74
|
+
- `@jgengine/shell/render/useModelAnimation` → `@jgengine/core/anim/useModelAnimation.draw`
|
|
75
|
+
- `@jgengine/shell/render/useRagdoll` → `@jgengine/core/anim/useRagdoll.draw`
|
|
76
|
+
- `@jgengine/shell/world/AuthoredFlocks` → `@jgengine/core/ai/AuthoredFlocks.draw`
|
|
77
|
+
- `@jgengine/shell/world/DataObjects` → `@jgengine/core/data/DataObjects.draw`
|
|
78
|
+
- `@jgengine/shell/world/InstancedBodies` → `@jgengine/core/physics/InstancedBodies.draw`
|
|
79
|
+
- `@jgengine/shell/world/InstancedJoints` → `@jgengine/core/physics/InstancedJoints.draw`
|
|
80
|
+
- `@jgengine/shell/world/SpriteBatch` → `@jgengine/core/render/SpriteBatch.draw`
|
|
81
|
+
- `@jgengine/shell/world/StaticObjectModels` → `@jgengine/core/instancing/StaticObjectModels.draw`
|
|
82
|
+
- `@jgengine/shell/world/TileLayerRenderer` → `@jgengine/core/render/TileLayerRenderer.draw`
|
|
83
|
+
- `@jgengine/shell/world/WorldItems` → `@jgengine/core/item/WorldItems.draw`
|
|
84
|
+
- `@jgengine/shell/world/WorldTireMarks` → `@jgengine/core/vfx/tireMarks.draw`
|
|
85
|
+
- `@jgengine/shell/world/entityPose` → `@jgengine/core/anim/entityPose`
|
|
86
|
+
- `@jgengine/shell/world/staticObjectBatches` → `@jgengine/core/instancing/staticObjectBatches.draw`
|
|
87
|
+
- `@jgengine/shell/world/tileLayerInstances` → `@jgengine/core/render/tileLayerInstances`
|
|
88
|
+
- `@jgengine/shell/world/worldBarSamples` → `@jgengine/core/hud/barSamples.draw`
|
|
89
|
+
|
|
90
|
+
`verify:fast` boots only games the load check can open: a change touching llmville no longer fails the load guard with "unknown game". `siteGames` moved to `scripts/siteGames.ts`.
|
|
91
|
+
|
|
92
|
+
### Added
|
|
93
|
+
|
|
94
|
+
- `assets find <name>... [--list <file.json>] [--game <dir>] [--provision] [--limit N]` (also `jgengine assets find`): one JSON document per run. `--list` takes a JSON array of names or `{ "query", "pick" }`. `--provision` serves each confident top match, or the named `pick`, to the game: packs and library models through the machine's shared asset store (pulling only what no checkout has), another game's file copied to the same path. Pull output goes to stderr so stdout stays JSON.
|
|
95
|
+
- `findAssets(query, { has, gameFiles, game?, limit? })` (`@jgengine/assets/findAssets`) is the search behind it; `scoreAssetText` (`@jgengine/assets/rankAssets`) is the scorer every asset search ranks by.
|
|
96
|
+
- `@jgengine/assets/assetStore`: the shared asset store (`assetStoreRoot`, `linkFromStore`, `seedStore`, `provisionJobs`) moved here from the games folder, so the CLI and `games:provision` serve packs one way.
|
|
97
|
+
- `createBreedingField` in `@jgengine/core/breeding/breeding`: mating pairs in range, eggs incubating inside a temperature band or gestation, hatching or birth, baby stages, care requests and imprinting, multiples, cooldowns, speed multipliers and save/restore; the game supplies species times and how genes combine.
|
|
98
|
+
- `inheritStats` and `scatterLevels` in `@jgengine/core/breeding/inheritance`: per-stat inheritance from two parents with mutation rolls, and spreading levels over stats at random.
|
|
99
|
+
- `SceneEntity.size`: one entity's model drawn larger or smaller than its kind's scale, on its own rig and in a crowd batch, so a hatchling grows up without a new kind.
|
|
100
|
+
- `createBaseBuilding` (`@jgengine/core/basebuilding/baseBuilding`): snap base building from data. Shapes are boxes with sockets; pieces pair a shape with a tier and health. Plans a piece at a cursor (snapping to the nearest free socket, or standing on the ground within a step limit), places, rotates through snap variants, toggles doors, damages by damage class per tier, removes and collapses whatever loses its path to the ground, raycasts, and hands back world boxes and collision solids; snapshot/restore.
|
|
101
|
+
- `BaseStructures` (`@jgengine/core/basebuilding/BaseStructures.draw`): draws placed pieces by colour, darkened as they lose health, and the build ghost tinted valid or blocked.
|
|
102
|
+
- `EntityMovement.runMultiplier`: a body's own sprint ratio, so a mount sprints at its species' ratio instead of the rider's.
|
|
103
|
+
- `caveLayout`, `caveMesh` and `<Cave>` in `@jgengine/core/cave`: describe a cave by its path, width, height, chamber and cover and get its collision, inside/depth queries, rock and floor.
|
|
104
|
+
- `ctx.camera.turnTo(yaw)`: the orbit, first-person, chase and shoulder rigs turn to face that heading on their next frame, e.g. after a teleport or a respawn.
|
|
105
|
+
- `repeatSeconds` on authored weather: loop the schedule forever, each lap starting where the last ended.
|
|
106
|
+
- `precipitationAs: "rain" | "snow"` on a weather zone: whatever falls there falls as this, blending at the zone's edge.
|
|
107
|
+
- `solidOverhead(solids, at, { clearance?, reach? })` in `@jgengine/core/world/overhead`: whether a roof, ceiling or rock is over a point.
|
|
108
|
+
- `sky({ dayHold })`: how long the noon sky holds either side of noon under `timeOfDay`.
|
|
109
|
+
- `overcastDaylight(state, cover)` in `@jgengine/core/sky/daylightCycle`, and the `Sky` component's `weather` prop: falling rain or snow clouds the sky over. The shell passes the world's weather to the sky whenever the authored scene has weather.
|
|
110
|
+
- `createKnockoutTaming` (`@jgengine/core/taming/knockoutTaming`): ARK-style torpor knock-out and passive feeding taming. Per species: torpor by level, torpor drain, a food table (count and seconds at level 1 and 30, ineffectiveness). Tracks preferred food first, the meal clock, taming effectiveness, bonus levels, waking and starving; snapshot/restore.
|
|
111
|
+
- `createCreatureDriver` (`@jgengine/core/ai/creatureDriver`): moves and fights a creature from a mind (wild temperament, tamed companion intent, or still) and a body (walk, sprint, attack, wander). Wild creatures sense with their own perception and hold a grudge against whoever hurt them; they flee, stalk, bite or graze. The charge stops where the body's bite reaches, so a temperament tuned with a shorter range never stalls just out of reach, and steps veer around trunks and rocks instead of stopping behind them.
|
|
112
|
+
- `ridingOf` (`@jgengine/core/scene/riding`): ride a mount by seat offset. It hands controls to the mount through possession, freezes the rider and carries them on the seat, and sets them down beside the mount on dismount; snapshot/restore.
|
|
113
|
+
- `createRemainsField({ lastsSeconds })` in `@jgengine/core/remains/remains`: leave what someone carried where they fell, find the nearest, take back what fits, lose it when it expires, snapshot and restore.
|
|
114
|
+
- Orbit camera `snapDistance`: a follow target that jumps farther than this is cut to rather than glided to (default 30).
|
|
115
|
+
- `createAirdropField` in `@jgengine/core/airdrop/airdrop`: timed supply crates at authored sites, a tier picked per site by weight, falling from a height, rolled loot on landing, opened only at the tier's level, expiring when ignored, one per site, with `call` for an admin or scripted drop.
|
|
116
|
+
- `LightBeam` in `@jgengine/core/map/LightBeam.draw`: a tall glowing column marking a spot to find from far away.
|
|
117
|
+
- `createStatPoints` (`@jgengine/core/progression/statPoints`): level-up points spent into stats. Each stat has a base and what a point adds, either an amount (a survivor's +10 health) or a fraction of the base (a tame's +5.4% health), with a per-owner base override and an optional per-stat cap. Sheets are plain `{ stat: points }` records that save as they are.
|
|
118
|
+
- Fernwild tames level as in ARK: kills and a passive trickle give XP on the creature curve (10 x n per level, 88 levels past the tame), each level is a point for the tame's stat sheet, and mounts spend their own stamina on a sprint (raptor 6 a second, phiomia 20).
|
|
119
|
+
- `surveyFromDocument`, `surveyTerrain`, `surveyHeightField`, `surveyBiomeAt`, `surveyIsLand` (`@jgengine/core/terrain/surveyTerrain`): build a real-world-style landmass from scene shapes — `coast` outlines, `peak` markers (height, radius, crater), `ridge` paths (crest heights, width), `river` paths (width, bed) and `biome` outlines (ground material and colours) — into terrain config.
|
|
120
|
+
- `TerrainCoast.outlines`: a coast shaped by one or more closed shore outlines (islands, bays, capes) instead of a single wobbled ellipse.
|
|
121
|
+
- `TerrainPolygonRegion`: paint terrain ground inside any polygon, blended over its falloff.
|
|
122
|
+
- `PopulationTickContext.isActive` and `notifyRemoved(..., { delay })`: a region can sleep with its slots kept until it wakes, and a game can refill a culled slot at once.
|
|
123
|
+
- `sceneScatterBudget` (`defineGame`) and `AuthoredScene` `scatterBudget`: how many scatter instances a game's scene draws; default stays 20000.
|
|
124
|
+
- `capScatterInstances` (`@jgengine/core/scatter/InstancedScatter.draw`): thin a scatter list evenly to a budget.
|
|
125
|
+
- `readGlbModel(bytes)` in `@jgengine/core/creatureBody/glbRead`: a binary glTF's meshes baked into model space (positions, normals, UVs, colours, indices), its materials and embedded images.
|
|
126
|
+
- `autoRig(model, { shape, facing?, head?, tail?, ... })` in `@jgengine/core/creatureBody/autoRig`: give a static quadruped, biped, flyer, bug or serpent a skeleton, skin weights and gait clips, ready for `encodeGlb`.
|
|
127
|
+
- `gaitClips(rig)` in `@jgengine/core/creatureBody/gait`: the Idle, Walk, Run, Attack and Death clips for any limb-and-tail rig, shared by generated bodies and rigged models.
|
|
128
|
+
- `encodeGlb` writes UVs, vertex colours, skins (joints, weights, inverse bind matrices) and embedded base-colour images.
|
|
129
|
+
- `bodyModel`, `bodyGlbModel`, `BodyPlan` (`@jgengine/core/creatureBody/bodyPlan`): describe a creature's shape in a few numbers and get an animated `ModelConfig` (Idle, Walk, Run, Attack, Death clips), built lazily as a `data:` GLB the first time its url is read.
|
|
130
|
+
- `encodeGlb`, `glbDataUrl` (`@jgengine/core/creatureBody/glbWrite`): write a node-animated binary glTF from meshes, nodes and clips.
|
|
131
|
+
- `jgengine plan <game> [--brief "<text>"]`: writes or refreshes `<game>/PLAN.md` — features, the engine owner of each, the gaps, milestones.
|
|
132
|
+
- `jgengine has <feature> ...`: one JSON answer for many features from the capability index and code map `find` reads.
|
|
133
|
+
- `PLAN_GAPS_HEADING` (`jgengine/plan/plan`): the generated `## Gaps` heading of a game's `PLAN.md`.
|
|
134
|
+
- `HITCH_MS` (`@jgengine/core/devtools/frameBreakdown`): Where each frame goes: the record of one frame (CPU per system, GPU per render pass, draws, shader compiles, uploads, heap) and the summary of a window of them (percentiles, the costliest systems, passes, objects and materials, and every hitch with what caused it). Also `DrawCost`, `FrameBreakdownRecord`, `FrameBreakdownSummary`, `Hitch`, `NamedCost`, `Spread`.
|
|
135
|
+
- `measureComposerPasses` (`@jgengine/core/devtools/frameBreakdown.draw`): Time each post pass of a composer by name, CPU and GPU. Also `BreakdownRenderer`, `BreakdownStore`, `FrameBreakdown`.
|
|
136
|
+
- `LastFrameSample` (`@jgengine/core/devtools/types`): The last frame `devtools.frame.record` saw: its sim milliseconds and named phases.
|
|
137
|
+
- `pagePrefetch` (`@jgengine/core/boot/assetPrefetch`): The page's prefetch: one game per page, started when the game opens. Also `AssetPrefetch`, `PrefetchState`.
|
|
138
|
+
- `ENTER_QUIET_MS` (`@jgengine/core/boot/BootDirector.draw`): How long the model loader must stay idle after Play before the entering screen lifts. Also `BootSignals`.
|
|
139
|
+
- `bootLoadingAreas` (`@jgengine/core/boot/bootScreen.view`): How many world pieces are still loading; Play waits on the entering screen until none are. Also `useBootStep`.
|
|
140
|
+
- `BOOT_SEQUENCE_ATTRIBUTE` (`@jgengine/core/boot/bootSequence`): The page attribute a boot is stamped on, for a check that reads a running game. Also `BOOT_STEPS`, `BootSequence`, `BootSequenceState`, `BootStep`, `pageBoot`.
|
|
141
|
+
- `orderPrefetch` (`@jgengine/core/boot/prefetchTier`): Order a list near tier first, keeping each tier's own order and dropping repeats. Also `PrefetchItem`, `PrefetchTier`.
|
|
142
|
+
- `linkDrawsOffFrame` and `sceneRevealed` (`@jgengine/core/render/shaderWarmup.draw`): link the programs a set of draws would run without drawing them, and wait for a scene's loading cover to lift.
|
|
143
|
+
- `bun run shoot --load` reports the worst frame in the two seconds after the cover lifts, and each run's frame count, worst, median and frames over 33 ms in that window.
|
|
144
|
+
- the agent bridge answers camera_heading { yaw } and teleport { x, z, y?, yaw? }.
|
|
145
|
+
- `FLOW_PRESS_MS` (`@jgengine/core/play/flow`): How long {@link Flow.press} holds its keys. Also `BrowserFlow`, `BrowserFlowSpec`, `BrowserProofFlow`, `FLOW_WAIT_MS`, `Flow`, `FlowCommandResult`, `FlowHold`, `FlowKey` and 7 more.
|
|
146
|
+
- `FlowFailure` (`@jgengine/core/play/flowSteps`): The step a flow failed at and why, with the probe readings at that moment when the game has a probe. Also `FlowRunReport`, `FlowStepRecord`, `FlowStepper`.
|
|
147
|
+
- `runFlowHeadless` (`@jgengine/core/play/headlessFlow.draw`): Play a proof flow headless: the real game through {@link createPlaySession}, keys mapped to the game's own bindings, every wait in game time, so a flow that takes a minute in the browser takes seconds here. Also `HeadlessFlowOptions`.
|
|
148
|
+
- `createPlaySession` (`@jgengine/core/play/playSession.draw`): Boot a `PlayableGame` through the headless runner and tick it the way the shell does: movement from held actions, then the per-frame action phase (bound commands, prompts, hotbar). Also `PlaySession`, `PlaySessionOptions`.
|
|
149
|
+
- `createHeadlessRunner({ game })` boots a whole playable game (what `defineGame` returns): content, phase-synced loop, movement and collision config come from it, and passing them alongside `game` is a type error. `HeadlessRunner.look` reads the heading the walk steers by.
|
|
150
|
+
- `teleportPlayer(ctx, userId, { x, z, y?, yaw? })` (`@jgengine/core/movement/teleport`) moves the body a player controls (their mount while riding), drops carried velocity, snaps the render pose and turns the camera.
|
|
151
|
+
- `PlayableGame<TProbe>` and `GameCaptureConfig<TProbe>`: `defineGame` infers what `capture.probe` returns, so tools reading `typeof game` see each probe by name. `ProbeReadings` names the shape.
|
|
152
|
+
|
|
153
|
+
### Changed
|
|
154
|
+
|
|
155
|
+
- The dev build's missing-model banner names the command that fetches a model no checkout has: `jgengine assets find <name> --game <dir> --provision`.
|
|
156
|
+
- New: bun run data-table <url> --out <file.ts> fetches a web page's data table (a wiki's species stats) once into a typed file a game imports; the page is cached for every worktree and a second run fetches nothing
|
|
157
|
+
- Static batches take composed kit models (models built from `parts` with no roles, variants or motion): each part becomes its own instanced draw at the offset the single-model renderer gives it. Removing a batched prop no longer rebuilds its batch; regrouping one elsewhere still does.
|
|
158
|
+
- The `scenery` catalog flag now covers props a run clears away, such as a yard's clutter; Drift Foundry marks its zone clutter, yard lamps and towers, barricades and exit arch as scenery.
|
|
159
|
+
- Building chunks default to 256 m cells instead of 64 m (`partitionInstances` `cellSize`). Drift Foundry's beauty and shadow draws for buildings drop by about two thirds for about 2% more triangles; Harbor Heat's draws and triangles are unchanged.
|
|
160
|
+
- Box-building window and storefront glass no longer casts shadows when box walls are drawn: each pane sits inside its full wall panel, 2.5 cm proud, so the wall already casts that shadow.
|
|
161
|
+
- Building chunk distance tests reuse the chunk's bounds until its world matrix changes, and building chunks leave three's per-frame matrix pass (`freezeStaticTransforms`, which now also settles the node's parents first).
|
|
162
|
+
- The scene's matrix pass skips object and entity markers that culling hid, and rebuilds them in the pass that shows them again (`@jgengine/core/visibility/culledTransforms.draw`).
|
|
163
|
+
- Culling no longer re-places unchanged objects in its spatial index every frame, and the culling driver stops allocating a sphere and a lookup key per object per frame.
|
|
164
|
+
- After the reveal, each draw of the scene leaves out any drawable whose shader is not linked yet, starts its link off the frame, and draws it on the first frame it is ready (`deferUnlinkedDraws` in `@jgengine/core/render/shaderWarmup.draw`, installed by the scene warmup gate). A link that never reports completion stops hiding its drawable after 30 s.
|
|
165
|
+
- The reflection probe links, in the background, the programs a capture's environment map will need for every material reflecting for the first time, then hands the map over; the first capture included. `compileObjects` takes the material each drawable is about to draw with.
|
|
166
|
+
- The shared screen-space bump (`SURFACE_BUMP_NORMAL_GLSL`, `@jgengine/core/terrain/terrainSurfaceShader.draw`) keeps the incoming normal when the pixel frame is degenerate. A wet puddle's flat normal seen edge-on made the bump vector zero, normalizing it gave one NaN pixel, and bloom's blur spread that pixel over half the frame whenever TAA's jitter landed on it. Model surface detail now uses the same chunk instead of its own copy.
|
|
167
|
+
- Drift Foundry marks its five newer tracks' standing props (edge posts, lamps, clutter, set-pieces) as `scenery`, so they draw through shared instanced batches instead of about 2,000 separate props whose scene graph three walked every frame.
|
|
168
|
+
- `bun run shoot --load`: when a game's own full-screen menu covers the canvas at the reveal (Scrap Signal's broadcast menu stops the world drawing under it), the two-second frame window and the blank-scene read wait until the menu lifts after the Play press, and the run line says so.
|
|
169
|
+
- `buildPolylineIndex(points, reach?)` takes the bound most queries will pass and sizes its grid for it; `nearest` searches outward from the query instead of walking the whole bound, with the same answers.
|
|
170
|
+
- A scatter field over its instance budget thins evenly across the map instead of dropping every instance past the budget in document order (whole regions used to vanish).
|
|
171
|
+
- Scatter resolution about twice as fast: no surface-normal sample unless slope or alignment needs it, numeric spacing buckets.
|
|
172
|
+
- Asset provisioning links a pack a game names only through an asset alias (Fernwild's survivor model was a missing-model cube).
|
|
173
|
+
- Coast signed distance uses a polyline index and a coarse land grid, so a detailed outline costs about the same per sample as the old ellipse.
|
|
174
|
+
- `PopulationTickContext.isActive` is asked at most once per region per tick, and a tick with nothing due no longer copies the respawn queue.
|
|
175
|
+
- has: a feature worded differently from its capability now matches through the code's folder names and doc comments (supply drops, hunger thirst)
|
|
176
|
+
- Doc comments only finish a match the names started, and one comment must carry every word, so a stray word never covers a feature
|
|
177
|
+
- `prepareCollisionMeshSource` and `prepareCollisionMesh` split BVH nodes with a median select instead of a full sort (same tree contents, 4x faster on a 100k-triangle prop).
|
|
178
|
+
- The scene warmup reveals one empty round after the last link instead of three; `linkAhead` also waits for programs the compile reused while another caller was still linking them.
|
|
179
|
+
|
|
180
|
+
| game | scene ready before (ms) | after (ms) |
|
|
181
|
+
|---|---:|---:|
|
|
182
|
+
| harbor-heat | 8787 | 7378 |
|
|
183
|
+
| drift-foundry | 10029 | 8422 |
|
|
184
|
+
| scrap-signal | 8895 | 7233 |
|
|
185
|
+
|
|
186
|
+
Still above 5 s: what is left is the GPU linking 60-100 distinct shader variants one after another on a cold Direct3D cache (about 25-35 ms each, ~3 s per game).
|
|
187
|
+
- In-repo game `jgengine.json` `capabilities` holds only `debt` (`{ reason, issue }` per capability id) and `probes`; adoption is read from the game's imports.
|
|
188
|
+
- `jgengine sync` no longer writes capability answers.
|
|
189
|
+
- lane:close -m writes the change note, commit message and PR body
|
|
190
|
+
- The note lists the exported API the diff added or removed and, for a game change, its engine seam; Closes/Prevention/Root cause lines stay out of the changelog
|
|
191
|
+
- Skill routing for core feature folders is generated (`bun run gen skill-routing`); adding a folder needs no hand edit.
|
|
192
|
+
- Frame breakdown and perf command: where every frame goes
|
|
193
|
+
- Dev builds keep a per-frame breakdown (window.__jgFrameBreakdown, perf panel): CPU self time per engine and game system and per useFrame callback by file:line, GPU ms per pass (shadow, main, each post effect) via timer queries, draws, triangles, programs, uploads, top objects and materials, and hitches over 33 ms with their cause (compile, upload, gc, system, gpu).
|
|
194
|
+
- bun run perf <game> now defaults to 1080p, the uncapped clock and a walk path, writes .scratch/perf/<game>.json, prints a short text summary, and --compare before.json prints a before/after diff. The jgengine-verify skill has a 5-minute guide.
|
|
195
|
+
- Games on jgengine.com load every model, and every game opens through one loading sequence
|
|
196
|
+
- Cause: the production /play build never copied each game's public/ folder (only the dev server served it), so models 404'd on the live site and two scatter loaders crashed hold-the-line on a 404. Each game's files now ship under /play/games/<id>/, with a per-game file list the runner loads from; a .glb or .gltf's own textures resolve there too.
|
|
197
|
+
- Release gate: scripts/check-site-assets.ts runs before every site deploy and fails on any model, texture, sound, pack or model texture a game names that the build lacks.
|
|
198
|
+
- The dev missing-models overlay and placeholder labels never show in production.
|
|
199
|
+
- One boot sequence (open > loading > intro > menu > entering > playing) drives every loading surface: one jgengine loading screen, no second loading screen or flash. A game's files start downloading in the background the moment it opens, near files first and distance/LOD files after; Play enters at once when they are in, else shows the loading screen with real file progress. The page stamps the sequence as data-jg-boot, and bun run shoot --load fails a game whose sequence repeats or goes backwards or whose files 404.
|
|
200
|
+
- Migration: resolveAssetBaseUrl moved from @jgengine/core/assets/assetBase.draw to @jgengine/core/assets/assetBase; installAssetBase stays in assetBase.draw.
|
|
201
|
+
- `PostProcessing` (`@jgengine/core/postfx/PostProcessing.draw`) builds its effect passes switched off and turns them on after the scene's loading cover lifts and their programs have linked off the frame, fading them in over 0.3 s. A settings change that rebuilds the chain does the same. `immediate` draws every effect from the first frame (the shell passes it for posters).
|
|
202
|
+
- The beauty pass under temporal AA jitters only while the TAA pass is on.
|
|
203
|
+
- land: a re-close with only new -m wording rewrites the note and retitles the open PR
|
|
204
|
+
- Proof flows are TypeScript: <game>/proofs/flows/<name>.flow.ts exports defineFlow(game, { state?, browser?, run(f) }) with typed keys and chords, probe checks typed from the game's own capture.probe, f.heading, f.teleport, f.command, f.timeScale and f.waitFor. bun run drive <game> --flow <name> plays it headless by default (under a second where the browser took half a minute) or in the browser with browser: true or --browser. A pass prints one line; a failure names the step, the probe readings and the steps before it. Every game's flows are converted.
|
|
205
|
+
- Each core feature folder names its skill with one // @skill line in its own code; skill routing is generated from those lines, so all 106 folders route exactly as chosen
|
|
206
|
+
- A new folder with no tag gets a guess written into it (the skill whose folders its names and doc comments read most like), visible in the diff; the guess matches the chosen skill 72 times in 105, up from 64
|
|
207
|
+
- `bun run lane:close` is the one verification pass: it runs `gen` and `verify:fast` itself, so nothing runs by hand before it, and its PR body carries whole commit messages, not subjects alone.
|
|
208
|
+
- The Bends section lists only deleted assertions, skipped tests and disabled checks; a clean diff gets none, and `changes/*.md` folding keeps it out of the changelog.
|
|
209
|
+
- `bun run check games-capabilities` checks ids and issue numbers only: debt on an unknown, imported or probe-less capability, a debt entry with no issue number, a probe for a module the game does not adopt, a stale ledger. It no longer judges debt reasons, reads GitHub issues or keeps a debt budget.
|
|
210
|
+
- `bun run check games-framework` exempts a game file through a first-line `// framework-ok: <reason>`; the `--allow` flag CI never saw is gone, so local and CI run the same command.
|
|
211
|
+
- Root command guards are 600 s each, matching the 10-minute rule.
|
|
212
|
+
|
|
213
|
+
### Fixed
|
|
214
|
+
|
|
215
|
+
- A skinned character drawn on its own rig followed its entity again: the scene's matrix pass refreshes an attached skin's bind inverse the way three's own pass does.
|
|
216
|
+
- the boss arena's generated skill routing, regenerated on the current beta (it files under gameplay).
|
|
217
|
+
- Fernwild's number keys never equipped anything: the hotbar inventory was not marked as the HUD hotbar and items had no use handler, so Digit keys fell through to the bag.
|
|
218
|
+
- Distant skinned creatures drew at the wrong size: the character crowd measured a skinned mesh by its node transform instead of its bones, so a boar normalised to 1.9 m stood about 9 m tall past the crowd distance. The bake now measures exactly as the full model path does.
|
|
219
|
+
- A skinned model with no clips (a Rocketbox duck) baked into the crowd lying on its side at its raw quantized size; static crowd copies now bake each vertex at its skin-applied bind pose.
|
|
220
|
+
- The stray-browser sweep killed a dead run's Chrome and removed its profile at once; on Windows a folder whose files Chrome still held stays until Chrome has exited, so the sweep's test failed now and then under a loaded verify. The real kill now waits for the browser to exit before removing its profile. (Seen failing twice in `verify:fast`; not reproduced on demand, so the fix rests on the cause, not on a red-then-green run.)
|
|
221
|
+
- Shift never held its action: pressed keys fold ShiftLeft/ShiftRight to `Shift`, but bindings kept `ShiftLeft` as written, so `sprint: ["ShiftLeft"]` (the documented form, in ten games) never matched. Binding and pressed key now fold the same way.
|
|
222
|
+
- Drive proofs could not hold a modifier: the chord sender gave Shift the key name `ShiftLeft` and no modifier bit. One shared sender now sends a real Shift, Control or Alt, used by both drive paths.
|
|
223
|
+
- Bare fists did no torpor. A punch now deals ARK's 1.75x its damage as torpor (a club 3.75x).
|
|
224
|
+
- The HUD's health read the survivor's vitals copy, which catches up with the health pool only on the next tick, so it could show a stale value. It now reads the pool itself.
|
|
225
|
+
- Phiomia stats were guesses; they are now ARK's (stamina 300, food 3000, weight 200, melee 10, walk 1.5 m/s).
|
|
226
|
+
- a lane no longer hand-writes an Engine seam line, an API list or a changes/ file
|
|
227
|
+
- closing again with -m rewrites the open PR's body, not only the note
|
|
228
|
+
- headless runs start on the game's camera.initialYaw, so a walk faces the same way as in the page.
|
|
229
|
+
- bun run check games-design checked no games when run from the repository root, as CI runs it; it now checks all 18.
|
|
230
|
+
- The headless runner ignored the game's own `ctx.camera.turnTo`, so a teleport cheat that faced the camera left headless walks going the old way.
|
|
231
|
+
- A fixed-rate game stepped headlessly lost every tick past its catch-up cap on a long step.
|
|
232
|
+
- The dev server no longer watches `.jgengine/`, the folder `jgengine desktop` stages its build in, so desktop builds stop reloading or crashing a running `bun dev`. Root cause: Vite's default watch ignores cover only `.git`, `node_modules` and `test-results`, so every write into the staging folder reached the watcher. Generated `vite.config.ts` files set `server.watch.ignored` (`jgengine sync` carries it to existing games), and the standalone `.gitignore` lists `.jgengine/`.
|
|
233
|
+
|
|
234
|
+
### Removed
|
|
235
|
+
|
|
236
|
+
- The not-applicable decline and the per-game `adopts` list; `CapabilityDecline` is replaced by `CapabilityDebt`.
|
|
237
|
+
- `@jgengine/core/play/flow` no longer exports `BrowserFlow`, `BrowserFlowSpec`, `BrowserProofFlow`, `FLOW_PRESS_MS`, `FLOW_WAIT_MS`, `Flow`, `FlowCommandResult`, `FlowHold` and 8 more.
|
|
238
|
+
- `@jgengine/core/play/flowSteps` no longer exports `FlowFailure`, `FlowRunReport`, `FlowStepRecord`, `FlowStepper`.
|
|
239
|
+
- `@jgengine/core/play/headlessFlow.draw` no longer exports `HeadlessFlowOptions`, `runFlowHeadless`.
|
|
240
|
+
- `@jgengine/core/play/playSession.draw` no longer exports `PlaySession`, `PlaySessionOptions`, `createPlaySession`.
|
|
241
|
+
- the JSON flow format, and drive's --steps and --check flags with the look, aim-at and move-mouse steps; f.heading replaces them.
|
|
242
|
+
- `check fix-has-test`: it could not tell a bugfix from any other change, and `verify:fast` already exits 2 when a changed runtime file reaches no test.
|
|
243
|
+
- `scripts/debt-budget.json` and the `--write` / `--no-issues` flags of `check games-capabilities`; `land --watch`.
|
|
244
|
+
- `capabilities.json` `domain`: it only served the deleted reason check.
|
|
245
|
+
|
|
29
246
|
## 0.48.0
|
|
30
247
|
|
|
31
248
|
### Migrate
|
|
@@ -192,10 +409,6 @@ Prevention: none - the change is two redrawn PNG images with no code path a test
|
|
|
192
409
|
- `arrangeHudPanels` shrinks the HUD before lifting a panel off its edge into the play view: a bottom bar squeezed between the joystick and the action cluster (Fernwild's build bar) stays on the bottom edge at a smaller zoom, and lifts only when no zoom down to `minScale` keeps it there.
|
|
193
410
|
- Drift Foundry registers its steering and pedal pads as control regions; Apex Nights' garage panel grows to fit its price tags instead of spilling onto the action buttons.
|
|
194
411
|
|
|
195
|
-
### Bends
|
|
196
|
-
|
|
197
|
-
- Deleted the three `touchDockInsets` unit tests along with the function they tested; the new "touch controls are obstacles" browser test and the solver tests cover the behaviour that replaced it.
|
|
198
|
-
|
|
199
412
|
## 0.46.0
|
|
200
413
|
|
|
201
414
|
### Migrate
|
|
@@ -664,10 +877,6 @@ Verification:
|
|
|
664
877
|
- `bun scripts/check-types-all.ts` and the six games' `check-types`: pass
|
|
665
878
|
- fernwild in the browser: the build strip shows each structure with its cost and key, greyed when unaffordable, and clicking one starts building
|
|
666
879
|
|
|
667
|
-
## Bends
|
|
668
|
-
|
|
669
|
-
- `kitHud.test.tsx` lost its `KitActionBar` test with the component; the same behaviour (detail text, unaffordable as ghost, meters, prompt above) is asserted on `ActionBar look="kit"` in `actionHud.test.ts`.
|
|
670
|
-
- The `hudBlockOverlaps` tests in `hudLanes.test.ts` now run the same cases through `detectLayoutCollisions`; no case was dropped.
|
|
671
880
|
- City blocks are traced from the exact street graph instead of welding centerlines, so curved and filleted streets still close their blocks. Every city closes more blocks and places more buildings (the default district goes from 184 to 235 lots; Manhattan from 134 to 225). Apex Nights, Briarcrown, Ember Command, Hold the Line and Saltwick place city volumes and get the fuller fabric.
|
|
672
881
|
- Playground: "Lot frontage", "Lot depth" and "Plot variety" become one "Lot scale" dial (`?lotScale=`); "Landmarks" goes up to 0.5.
|
|
673
882
|
|
|
@@ -677,10 +886,6 @@ Verification:
|
|
|
677
886
|
- Playground in the browser: city and street race grow with buildings, an arena landmark, and the lap with kerbs and barriers
|
|
678
887
|
- `shoot apex-nights` and `shoot hold-the-line`: load and render normally
|
|
679
888
|
|
|
680
|
-
## Bends
|
|
681
|
-
|
|
682
|
-
- `cityKind.test.ts` pins the default district's lot counts; the pins move from 184/189 to 235/252 because the fabric now closes more blocks, which is the intended change.
|
|
683
|
-
- `cityLayout.ts` stays: it is Harbor Heat's authored road classes over the shared lot deriver, not a generator.
|
|
684
889
|
- Drop the i-have-adhd plugin from the repo settings; nothing on this PC uses it
|
|
685
890
|
- `check concept-homes` scans `games/*/src`, `games/*/packages/*/src` and `games/shared`. Each game is its own namespace (two games may each have a `Hud`); a clash with an engine export fails. The names a game's entry files hand the engine (`game`, `world`, `editorLayers`, `editorCatalogs`, `uiScenario` in top-level `src/` files) are the contract and pass.
|
|
686
891
|
- Games use the engine where theirs was a copy: `UiKitName` (`@jgengine/react/uiKits`) in the nine idea games, `FogBounds` in ember-command, `Vec2` in beacon-bastion, `clamp` in odd-orbit (`clampNeed` wraps it), and `openTitle` (`@jgengine/core/game/gameFlow`) inside beacon-bastion's `openKeepTitle` and deepward's `openMarrowTitle`.
|
|
@@ -692,9 +897,6 @@ Verification:
|
|
|
692
897
|
- every game's `check-types`: pass
|
|
693
898
|
- `bun test` on the touched games, `games/shared` and `scripts/check-concept-homes.test.ts`: pass
|
|
694
899
|
|
|
695
|
-
## Bends
|
|
696
|
-
|
|
697
|
-
- None. The entry-export allowance covers only the five names the template writes into top-level `src/` files; the same name anywhere else in a game still fails (tested).
|
|
698
900
|
- A ground over 66k vertices splits into 128-cell tiles, each a `THREE.LOD` at strides 1/2/4/8/16. Scene load builds only the coarsest level of every tile plus what the camera needs nearby; finer levels build in the background as the camera reaches them. Each level drops a skirt below its edges, so neighbouring tiles at different detail show no crack. Normals come from the sampled heights, so a tile edge lights the same from both sides.
|
|
699
901
|
- A pulled terrain scan that may be missing no longer meshes the ground twice (once for the procedural stand-in, again when its maps load): the same mesh swaps material.
|
|
700
902
|
- A compatible prop alone in its 128 m cell joins the nearest batch of its model, and a model with no cell batch gathers its lone props into one. Drawn as a plain mesh it linked a second, non-instanced program for every material the batched copies already had (harbor-heat start: 112 programs, now 107).
|
|
@@ -716,9 +918,6 @@ Verification:
|
|
|
716
918
|
- `bun scripts/check-types-all.ts`: 23 dirs, 0 failing; every game's `check-types`: pass
|
|
717
919
|
- `bun test` on threshold, stats, vec2/vec3, waypoints, ui, movement, combat, nav, ws, node, convex, shell audio: 1693 pass
|
|
718
920
|
|
|
719
|
-
## Bends
|
|
720
|
-
|
|
721
|
-
- None. The concept-homes template-literal skip removes false positives only (text a generator prints is not a definition); it has its own test.
|
|
722
921
|
- `HudCanvas` places every panel with `arrangeHudPanels` instead of nine independent anchor regions, so panels in different anchors no longer run into each other on phone and tablet screens.
|
|
723
922
|
- Beacon Bastion, Deepward, Harbor Heat, Odd Orbit and Scrap Signal declare one anchor and a priority per panel; their per-size placements are gone.
|
|
724
923
|
- Ember Command draws the ledger, orders note, pause seal, survey map, Bram's card, command board, order hints and level-up writ as `HudCanvas` panels with one anchor and a priority each, instead of hand-placed CSS. The map folds into a toggle when there is no room for it rather than being hidden below 1180px, and the hand-registered layout regions are gone because every panel registers its own.
|
|
@@ -855,12 +1054,6 @@ Verification:
|
|
|
855
1054
|
- `bun test scripts/load-check.test.ts scripts/verify-fast.test.ts packages/shell/src/terrain/terrainDetailMaterial.test.ts`: pass
|
|
856
1055
|
- `bun run shoot --load deepward --guard` with a planted stall: exit 1 (above)
|
|
857
1056
|
|
|
858
|
-
## Bends
|
|
859
|
-
|
|
860
|
-
- None.
|
|
861
|
-
|
|
862
|
-
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
|
863
|
-
|
|
864
1057
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
865
1058
|
- `Ocean` (`@jgengine/shell/water`) animates its waves on the game clock (`ctx.time.advancedSeconds()`), so the sea stops when the game pauses, follows slow motion and lines up with the water surface buoyancy samples. Before any game time has passed (a title screen), or with no game (an editor preview), it runs on real time.
|
|
866
1059
|
- Ocean glints follow the sun the mounted `Sky` placed, through the day-night cycle, and fade out once the sun sets; without a sky they keep the fixed day heading. `syncOceanMaterial` takes the sun direction as an optional fourth argument.
|
|
@@ -889,9 +1082,6 @@ Verification:
|
|
|
889
1082
|
- `bun test packages/jgengine scripts/scaffold-editor.test.ts`: 387 pass
|
|
890
1083
|
- fernwild and odd-orbit run from their own folder (`bun run dev`): styled title menu, fernwild's HUD and build strip in play
|
|
891
1084
|
|
|
892
|
-
## Bends
|
|
893
|
-
|
|
894
|
-
- `templates.test.ts`: the "in-repo tsconfig paths match the exact map doctor requires" test went with the map; it is replaced by a test that the in-repo wiring extends the base tsconfig, runs on `games/shared` and names the game by id. The AGENTS.md test now runs on the standalone template only, since the in-repo template no longer writes one.
|
|
895
1085
|
- Moved to `@jgengine/core/terrain/`: `terrain` (was `world/terrain`), `terraform` (was `world/terraform`), `pathTerrain` (was `world/pathTerrain`), `terrainGuides` (was `world/terrainGuides`), `terrainGrade` (was `movement/terrainGrade`), and `terrainPaint` (was `editor/terrainMaterials`).
|
|
896
1086
|
- `terrainGroundColorSampler` moved to `@jgengine/shell/terrain/terrainGroundColor` (was `environment/terrainGroundColor`).
|
|
897
1087
|
- The editor's paint palette is `TerrainPaintPreset` / `TERRAIN_PAINT_PRESETS` / `TERRAIN_PAINT_COLORS` (was `TerrainMaterial` / `TERRAIN_MATERIALS` / `TERRAIN_MATERIAL_COLORS`), imported from `@jgengine/core/terrain/terrainPaint`. `@jgengine/core/editor` and `@jgengine/editor` no longer re-export it.
|
|
@@ -914,10 +1104,6 @@ Root cause: gameplay keys are read on the shell's focusable wrapper. Closing the
|
|
|
914
1104
|
Verification:
|
|
915
1105
|
- `bun test packages/shell/src/playSurfaceFocus.test.ts`: pass (new test fails without the fix)
|
|
916
1106
|
- fernwild in the browser: New Game, Escape opens pause, Escape closes it, focus is back on the play surface, Escape opens pause again (before: focus stayed on `<body>` and pause did not reopen).
|
|
917
|
-
|
|
918
|
-
## Bends
|
|
919
|
-
|
|
920
|
-
- None.
|
|
921
1107
|
- `jgengine find <feature>` answers by folder and file names once a folder is named for the query: an export that only mentions the word (`MAX_OCEAN_WAVES` for "waves") no longer shows `shell/water` as a home for waves.
|
|
922
1108
|
- `shoot` and `drive` `--size half` keep the page's full CSS viewport and halve only the pixel ratio, so a half-size shot lays out exactly like a full one. Before, the 800x450 viewport switched games into their compact HUD and the layout-collision check failed the run (Scrap Signal, Beacon Bastion).
|
|
923
1109
|
- `EntityPreview` no longer throws "Cannot read properties of null (reading 'addEventListener')" when it unmounts while react-three-fiber is still creating its canvas (a dialogue or menu that re-mounts a portrait in one beat, e.g. Harbor Heat's Marco dialogue).
|
|
@@ -930,9 +1116,6 @@ Verification:
|
|
|
930
1116
|
- the new "hovering the menu while a field has focus" test fails without the fix (focus lands on the body) and passes with it
|
|
931
1117
|
- waits are `requestAnimationFrame` frames, not `waitForTimeout`
|
|
932
1118
|
|
|
933
|
-
## Bends
|
|
934
|
-
|
|
935
|
-
- None. No assertion was relaxed; the timer waits became frame waits and one regression test was added.
|
|
936
1119
|
- A shadow-casting light whose shadow map no frame had drawn yet left every lit draw failing (WebGL: "Mismatch between texture format and sampler type"), so the scene showed only its clear colour. The shader warmup holds the frame's draws behind the loading cover, and cascaded shadows took the first held frame as their one refresh, so far cascades never got a map. A cascade now refreshes until it has a map, or when something asks for a redraw.
|
|
937
1120
|
- The warmup's own draws ran before any shadow map existed, so every lit warmup draw failed and finished nothing. The first warmup draw now makes the missing maps, and the next real frame redraws them with every caster. WebGL errors on a cold load drop from 230-250 per game to under 50.
|
|
938
1121
|
- Ocean and authored water drew a different wave set from the CPU `waterSurface`, so floating bodies bobbed on waves that were not on screen. Both now come from `synthesizeWaves`.
|
|
@@ -942,8 +1125,6 @@ Verification:
|
|
|
942
1125
|
|
|
943
1126
|
- `buildingSpatialBatch` (replaced by `partitionInstances`), `chunkScatterInstances` / `ScatterChunk` (replaced by `groupInstancesByCell`).
|
|
944
1127
|
|
|
945
|
-
Bends:
|
|
946
|
-
- Left on their own writers: `InstancedBodies` and `SpriteBatch` (per-frame packed-array hot loops over thousands of bodies), `staticObjectBatches` (skips the upload when nothing moved), `ImpostorCardPool` (packs many slices into one shared mesh), `crowdBatch` / `SkinnedInstances` (the crowd concept) and `ImpactFeedback` (combat feedback, another builder's area).
|
|
947
1128
|
- `GridWorldScene` now computes a culling sphere where it had none.
|
|
948
1129
|
|
|
949
1130
|
Verification:
|
|
@@ -953,17 +1134,11 @@ Verification:
|
|
|
953
1134
|
- Unverified: LLMVille's endless datacenter on screen (it needs its Convex backend to run); it type-checks.
|
|
954
1135
|
- `jgengine upgrade` (with `--plan`, `--apply`, `--to`, `--json`), with no alias.
|
|
955
1136
|
|
|
956
|
-
## Bends
|
|
957
|
-
|
|
958
|
-
- `upgrade.test.ts` and `upgradeCatalog.test.ts` are deleted. Their changelog tests moved to `releaseNotes.test.ts`; the catalog cases (bump, `--check`, `catalog:` kept, downgrade refused) are now sync tests.
|
|
959
1137
|
- Sync always targets the CLI's own version (`npx jgengine@<version> sync` picks a version). The registry-publication check and the `--to` and `--json` flags from upgrade are dropped.
|
|
960
|
-
- The harness no longer runs a project's own `scripts/shoot.mjs`/`drive.mjs` copy
|
|
961
|
-
- The harness sources moved from `templates/gameFiles.ts` to `templates/harnessScripts.ts`; `check-scaffold-launch.ts` reads them there.
|
|
1138
|
+
- The harness no longer runs a project's own `scripts/shoot.mjs`/`drive.mjs` copy; the bundled harness runs instead.
|
|
962
1139
|
- The duplicate `TerrainMaterial` export in `editor/terrainMaterials`.
|
|
963
1140
|
|
|
964
|
-
|
|
965
|
-
- `map/terrainMap` (bakes a top-down map image) stays with the map concept; `shell/src/terrain.ts` stays as the package's `@jgengine/shell/terrain` entry.
|
|
966
|
-
- Moved files count as new exports, so 19 terrain and terraform symbols that had no doc line got one.
|
|
1141
|
+
- 19 terrain and terraform symbols that had no doc line got one.
|
|
967
1142
|
|
|
968
1143
|
Verification:
|
|
969
1144
|
- `bun run check types`, `bun run gen` (all stages), `bun run check concept-homes --all` (0 duplicates), `bun test packages/core/src packages/shell/src packages/editor/src apps/web/src scripts` (11223 pass, 0 fail).
|
|
@@ -971,12 +1146,6 @@ Verification:
|
|
|
971
1146
|
- Standalone editor: created a terrain, the Paint tab lists all eight presets, a grass stroke paints the ground.
|
|
972
1147
|
- Play shots of Beacon Bastion, Harbor Heat, Odd Orbit, Scrap Signal and Fernwild render as before; load guard passes for all five (Harbor Heat failed once while the full test suite ran beside it, then passed alone).
|
|
973
1148
|
|
|
974
|
-
### Bends
|
|
975
|
-
|
|
976
|
-
- Remaining instanced/plain pairs in harbor-heat are vehicles and characters: far ones draw through the baked crowd (its own shader), near ones on their full rig. That is two techniques, not one material compiled twice, so they stay.
|
|
977
|
-
- Making every lone prop a batch of one was tried and dropped: drift-foundry's scene ready went from about 10 s to 19–28 s (per-batch machinery on hundreds of one-prop batches).
|
|
978
|
-
- None.
|
|
979
|
-
|
|
980
1149
|
## 0.45.0
|
|
981
1150
|
|
|
982
1151
|
### Migrate
|
|
@@ -1121,8 +1290,6 @@ Root cause: #2505 removed the `games:<id>` root scripts without checking the des
|
|
|
1121
1290
|
Verification:
|
|
1122
1291
|
- `bun test packages/jgengine/src/create.test.ts apps/desktop/project/commands.test.ts`: 23 pass, 0 fail.
|
|
1123
1292
|
- `bun --cwd=games/harbor-heat run dev`, the argv `buildShellCommand` builds for Standalone: serves `http://localhost:5173/` with 200, title "Harbor Heat", and `#root`.
|
|
1124
|
-
|
|
1125
|
-
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
|
1126
1293
|
- `assets pull` sends the GitHub token (`GITHUB_TOKEN`, `GH_TOKEN`, else the signed-in `gh` CLI's) to `api.github.com`, `raw.githubusercontent.com` and `codeload.github.com`, so parallel pulls on one machine no longer hit the anonymous 60-an-hour limit and fail with 403. Every pull request also has a hard budget (`JG_FETCH_TIMEOUT_MS`, 10 minutes) and fails naming the URL that stalled.
|
|
1127
1294
|
- A model's `materialAssignments` no longer throw "matched no slots" on a variant level whose simplified asset dropped the selected slot. The source level still validates strictly, and a level skips only the selectors it lacks; `applyMaterialAssignments` takes `{ unmatched: "skip" }` for the same policy. Harbor Heat's HHPD cruiser crashed on spawn at its far level (issue #259).
|
|
1128
1295
|
- Camera shake now reaches the screen on every rig: `cameraShake()` used to feed a channel the rigs never read, and the orbit, first-person and inspection rigs applied no shake at all.
|
|
@@ -1143,11 +1310,6 @@ Prevention: `games-sync` gate; `packages/jgengine/src/sync.test.ts`.
|
|
|
1143
1310
|
|
|
1144
1311
|
Verification: `bun test packages/jgengine/src` pass; `bun run jgengine sync --check` up to date for all 18 games; a `--template 3d` game created outside the repo with npm boots, and after renaming it and turning the editor off in `jgengine.json`, `jgengine sync` rewrote the page, wiring and package.json and it still boots.
|
|
1145
1312
|
|
|
1146
|
-
## Bends
|
|
1147
|
-
|
|
1148
|
-
- Deleted three template tests (`world`, `track`, `places` `.test.ts.tmpl`) together with the `games:new` generator they tested.
|
|
1149
|
-
- `games/scripts/vite-engine-source.test.ts` no longer checks the deleted `games:new` template's `vite.config.ts`; every game's config is still checked.
|
|
1150
|
-
- `games/scripts/check-capabilities.test.ts` and `scripts/ci-scope.test.ts` expectations changed to the new file and message names, not loosened.
|
|
1151
1313
|
- `packages/jgengine/src/sync.test.ts` builds its games under the OS temp folder (`fixture-location: allow`): a standalone game has to sit outside every workspace.
|
|
1152
1314
|
- Capability answers are synced only for games in the engine repo's `games/`; an outside game's `jgengine.json` has none (the ledger reads engine source).
|
|
1153
1315
|
- `physics/kinematicVehicle`, `physics/vehicleBody`, `damagedTuning`, and Harbor Heat's own tyre-smoke glow (the engine's `tires` option replaces it). The retired `run-loop` and `kinematic-vehicle` capabilities are dropped from game declarations.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/convex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.49.1",
|
|
4
4
|
"description": "Convex adapters for JGengine: game transport, presence transport, and backend wiring over @jgengine/core.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"check-types": "tsgo --noEmit -p tsconfig.json"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@jgengine/core": "^0.
|
|
33
|
+
"@jgengine/core": "^0.49.1"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"convex": "^1.31.2",
|
|
@@ -20,13 +20,13 @@ Adapted for JGengine from [EveryInc/compound-engineering-plugin `ce-handoff`](ht
|
|
|
20
20
|
|
|
21
21
|
### Outcome
|
|
22
22
|
|
|
23
|
-
Create one immutable handoff at the destination the user requested, or use the managed temporary store by default. Briefly summarize what the handoff captured, then report its final path or URL, retention limits, and continuity warnings. The handoff supplements authoritative artifacts (issues, PRs,
|
|
23
|
+
Create one immutable handoff at the destination the user requested, or use the managed temporary store by default. Briefly summarize what the handoff captured, then report its final path or URL, retention limits, and continuity warnings. The handoff supplements authoritative artifacts (issues, PRs, skills); it does not replace them.
|
|
24
24
|
|
|
25
25
|
### Build the handoff
|
|
26
26
|
|
|
27
27
|
1. Distill the current objective and the user's latest intent. If a focus was supplied, make it the `resume_focus`.
|
|
28
28
|
2. Inspect only the workspace state needed to explain what exists now. Use JGengine project instructions already in context (`AGENTS.md`, `workflow`, domain skills).
|
|
29
|
-
3. Point to plans, issues, commits, diffs, documentation, and relevant files instead of reproducing their contents. Prefer repository-relative paths (`
|
|
29
|
+
3. Point to plans, issues, commits, diffs, documentation, and relevant files instead of reproducing their contents. Prefer repository-relative paths (`packages/…`, `games/…`, `changes/…`).
|
|
30
30
|
4. Redact secrets, credentials, tokens, and unrelated personal information. Preserve operational paths only when the next agent needs them.
|
|
31
31
|
5. Write or publish with existing tools. If the user named another path, folder, format, or publication destination, honor it. Do not also create a managed-store copy unless asked.
|
|
32
32
|
|
package/skills/jgengine/SKILL.md
CHANGED
|
@@ -19,15 +19,15 @@ capability, adopt it, or improve its shared owner before adding reusable game-lo
|
|
|
19
19
|
Games keep unique rules, content, assets and presentation. Custom markup is not a reason
|
|
20
20
|
to copy focus handling, input tracking, storage or model lifecycle.
|
|
21
21
|
|
|
22
|
-
Check
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
Check engine `beta` before recreating an upstream fix: games run the engine source from
|
|
23
|
+
the same commit, so a fix that exists is adopted by importing it. After adoption, remove
|
|
24
|
+
superseded local machinery and verify the actual game. Outside this repository, report
|
|
25
|
+
missing SDK contracts upstream rather than treating a local engine implementation as the
|
|
26
|
+
finished solution.
|
|
27
27
|
|
|
28
28
|
## 1. Establish the target
|
|
29
29
|
|
|
30
|
-
For greenfield builds, write `src/art-direction.md` before any code.
|
|
30
|
+
Before building a game or a big feature, plan it: `npx jgengine plan <game> --brief "<brief>"` writes `PLAN.md`; list every feature the brief implies under `## Features` and run `npx jgengine plan <game>` again to fill each feature's engine owner and the gaps; then order `## Milestones`, one PR each, and tick each as it lands. For greenfield builds, also write `src/art-direction.md` before any code.
|
|
31
31
|
|
|
32
32
|
Treat the pitch as a unique composition of needs, not a genre to fill in — never reach for a genre kit, preset, archetype, or class template ("default sports car", "default RPG", "default FPS") in the SDK or skills (see [AGENTS.md](../../../AGENTS.md)). Use `game-design` to turn the pitch into a testable player promise, verbs, loops, progression, failure, and completion scenario. Use `level-design` when play depends on authored spaces, routes, encounters, exploration, or spatial pacing. Capture only decisions that change architecture:
|
|
33
33
|
|
|
@@ -38,7 +38,7 @@ Treat the pitch as a unique composition of needs, not a genre to fill in — nev
|
|
|
38
38
|
- HUD/menu states and **custom** UI art direction (compose shipped building blocks + HudTheme; no unskinned stock face)
|
|
39
39
|
- one observable completion scenario
|
|
40
40
|
|
|
41
|
-
Pick the dimension first: `create` defaults to a 3D world (terrain, sky, player model, editor scene); a grid, card, tile, or other board game with no 3D scene takes `create --template 2d` (a `board` world drawn by GameUI over `@jgengine/core/puzzle/cellGrid`). `create --template 3d|2d|builder` is the one way to start a game, in this monorepo too (`bun run jgengine create "<Title>"` lands in `games/`); never copy `games/*`. The game's `jgengine.json` says what it is and uses (template, editor, `@jgengine` packages, and in-repo its
|
|
41
|
+
Pick the dimension first: `create` defaults to a 3D world (terrain, sky, player model, editor scene); a grid, card, tile, or other board game with no 3D scene takes `create --template 2d` (a `board` world drawn by GameUI over `@jgengine/core/puzzle/cellGrid`). `create --template 3d|2d|builder` is the one way to start a game, in this monorepo too (`bun run jgengine create "<Title>"` lands in `games/`); never copy `games/*`. The game's `jgengine.json` says what it is and uses (template, editor, `@jgengine` packages, and in-repo its adoption debt and boot probes). Change what a game uses by editing it, then run `jgengine sync`, which also brings a game up to the running engine: its `@jgengine` versions and its template's shared files (wiring, page, tooling scripts, AGENTS.md). A shared file the game rewrote goes in its `owns` list so sync leaves it alone.
|
|
42
42
|
|
|
43
43
|
For an existing game asked to pick up a new engine release ("jgengine published a new version, grab it"), run `npx jgengine@latest sync` in the project (or at its workspace root): it moves every `@jgengine` pin and catalog entry to that release, refreshes the template's files, and prints every Migrate step and Adopt-worthy addition in between. Install, migrate oldest-first, then work the Adopt lists instead of reconstructing the changelog by hand.
|
|
44
44
|
|
|
@@ -70,7 +70,7 @@ World content routes editor-first: any request that adds, moves, restyles, or re
|
|
|
70
70
|
Within each selected domain:
|
|
71
71
|
|
|
72
72
|
0. Fastest start for a common composition: `npx jgengine recipe <name>` prints a vetted, SDK-typechecked snippet (imports + wired code) (`combat-loop`, `loot`, `world-drops`, `click-target`, `ability-bar`, `hitscan-weapon`, `rolled-gear`, `enter-vehicle` and more); `npx jgengine recipe` lists them. Copy that instead of reconning per-file.
|
|
73
|
-
1. Search capabilities by intent; do not read `capabilities.md` top to bottom. `npx jgengine find <intent>` names the concept's home folder in each layer (read from the engine source or the installed packages), searches every domain's index and every recipe at once, and prints the primitive and its import (or the recipe to open) and matching assets; agents can use `--json --limit 5` for ranked imports and recipes with total, partial-match, and truncation fields. In the monorepo, `rg -i "<intent>" .claude/skills/*/capabilities.md` does the same search. Search the moment you are tempted to hand-roll a HUD window, inventory grid, paperdoll, hotkey listener, minimap, stat bar, or character motion. A `z-index`, a `keydown` for a panel, a `<div>` inventory, or a limb bob almost certainly has a shipped drop-in (`usePanels`/`PanelHost`, `InventoryGrid`, `CharacterSheet`/`Paperdoll`, `PartMotionRig`, `EntityPreview`); games own the *skin*, not the re-derivation.
|
|
73
|
+
1. Search capabilities by intent; do not read `capabilities.md` top to bottom. Several features at once: `npx jgengine has torpor riding "snap building"` answers each (covered, partial or gap, with folders and imports) as one JSON document. `npx jgengine find <intent>` names the concept's home folder in each layer (read from the engine source or the installed packages), searches every domain's index and every recipe at once, and prints the primitive and its import (or the recipe to open) and matching assets; agents can use `--json --limit 5` for ranked imports and recipes with total, partial-match, and truncation fields. In the monorepo, `rg -i "<intent>" .claude/skills/*/capabilities.md` does the same search. Search the moment you are tempted to hand-roll a HUD window, inventory grid, paperdoll, hotkey listener, minimap, stat bar, or character motion. A `z-index`, a `keydown` for a panel, a `<div>` inventory, or a limb bob almost certainly has a shipped drop-in (`usePanels`/`PanelHost`, `InventoryGrid`, `CharacterSheet`/`Paperdoll`, `PartMotionRig`, `EntityPreview`); games own the *skin*, not the re-derivation.
|
|
74
74
|
2. Open `api.md` (monorepo) or the package `.d.ts` only when exact signatures are needed; grep it, never read it whole.
|
|
75
75
|
3. Open the linked reference only for a deeper recipe or trap.
|
|
76
76
|
4. For how several primitives wire into a running loop, read the domain's `recipes/` — connected, genre-free walkthroughs organized by composition seam; this skill's own [recipes/minimal-game.md](recipes/minimal-game.md) is the default whole-game walkthrough. Prefer a recipe over reading a game's source; the games under `games/*` are not templates or references, and a hybrid game is just a different composition of the same primitives.
|
package/skills/jgengine/api.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
- `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
|
|
8
8
|
- `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
|
|
9
|
-
- `VERSION` (const): const VERSION: "0.
|
|
9
|
+
- `VERSION` (const): const VERSION: "0.49.1" — Installed `@jgengine/core` semver - compare against {@link CHANGELOG} keys when migrating.
|
|
10
10
|
|
|
11
11
|
## @jgengine/core/authoring
|
|
12
12
|
|
|
@@ -184,7 +184,7 @@
|
|
|
184
184
|
- `DevtoolsLogEntry` (interface): interface DevtoolsLogEntry — A single captured log line with timestamp, level, owning system tag, and formatted message.
|
|
185
185
|
- `DevtoolsLogLevel` (type): type DevtoolsLogLevel = "log" | "info" | "warn" | "error" — Severity level for a captured devtools log entry.
|
|
186
186
|
- `DevtoolsLogs` (interface): interface DevtoolsLogs — Bounded log ring with per-system tags, a warning/error ring, and lifetime counts.
|
|
187
|
-
- `DevtoolsOverrides` (interface): interface DevtoolsOverrides { version: number; enabled: string[]; values: Record<string, unknown>; schemas?: Record<string, {kind: DevtoolsControlKind; schemaVersion?: number}> } — ⚠ undocumented
|
|
187
|
+
- `DevtoolsOverrides` (interface): interface DevtoolsOverrides { version: number; enabled: string[]; values: Record<string, unknown>; schemas?: Record<string, {kind: DevtoolsControlKind; schemaVersion?: number | undefined}> | undefined } — ⚠ undocumented
|
|
188
188
|
- `DevtoolsSnapshot` (interface): interface DevtoolsSnapshot — Serializable point-in-time snapshot of devtools state: frame, render, latency, logs, controls, and discovered fields.
|
|
189
189
|
- `DiscoveredEntry` (interface): interface DiscoveredEntry — A control auto-discovered by scanning an object/table, with its binding metadata and current reader.
|
|
190
190
|
- `DiscoverySkip` (interface): interface DiscoverySkip { readonly path: string; readonly reason: string } — ⚠ undocumented
|
|
@@ -246,7 +246,7 @@
|
|
|
246
246
|
- `DiagnosticOverlay` (function): function DiagnosticOverlay({ diagnostics, gameName }: { diagnostics: RuntimeDiagnostic[]; gameName: string }): React.JSX.Element | null — ⚠ undocumented
|
|
247
247
|
- `ErrorReportActions` (function): function ErrorReportActions({ report, issueTitle }: { report: string; issueTitle: string }): React.JSX.Element — "Copy error" + "File issue" buttons for any error surface. `report` is the full text to copy / prefill.
|
|
248
248
|
- `GameUiErrorBoundary` (class): class GameUiErrorBoundary extends Component< { children: ReactNode; onRuntimeError: (error: unknown, phase: string, componentStack?: string) => void }, { failed: boolean } > — ⚠ undocumented
|
|
249
|
-
- `RuntimeDiagnostic` (interface): interface RuntimeDiagnostic { id: number; phase: string; message: string; stack?: string; componentStack?: string; capturedAt: string } — ⚠ undocumented
|
|
249
|
+
- `RuntimeDiagnostic` (interface): interface RuntimeDiagnostic { id: number; phase: string; message: string; stack?: string | undefined; componentStack?: string | undefined; capturedAt: string } — ⚠ undocumented
|
|
250
250
|
- `errorReportContext` (function): function errorReportContext(): string — Standalone error footer text (page, browser, engine) shared by every error surface.
|
|
251
251
|
- `logRuntimeError` (function): function logRuntimeError(error: unknown, phase: string, componentStack?: string): Omit<RuntimeDiagnostic, "id"> — ⚠ undocumented
|
|
252
252
|
|
|
@@ -274,6 +274,23 @@
|
|
|
274
274
|
- `FrameModule` (interface): interface FrameModule — Frame subsystem exposing the frame-timing, profiling, and render-sample facades.
|
|
275
275
|
- `createFrameModule` (const): const createFrameModule: (deps: { readProbes: () => Record<string, unknown>; }) => FrameModule — Create the frame subsystem that records frame/sim/phase timings, computes stats, and captures long-frame events.
|
|
276
276
|
|
|
277
|
+
## @jgengine/core/devtools/frameBreakdown
|
|
278
|
+
|
|
279
|
+
- `DrawCost` (interface): interface DrawCost — One object or material's share of the draws: totals while gathering, per-frame averages in a summary.
|
|
280
|
+
- `FrameBreakdownRecord` (interface): interface FrameBreakdownRecord — One frame, as the hooks saw it. GPU times arrive a few frames late; `gpu` is null until they do.
|
|
281
|
+
- `FrameBreakdownSummary` (interface): interface FrameBreakdownSummary — A window of frames, summarized for a person or a diff.
|
|
282
|
+
- `HITCH_MS` (const): const HITCH_MS: 33 — Where each frame goes: the record of one frame (CPU per system, GPU per render pass, draws, shader compiles, uploads, heap) and the summary of a window of them (percentiles, the costliest systems, passes, objects and materials, and every hitch with what caused it). This file is the arithmetic; `frameBreakdown.draw.ts` fills the records from a live renderer.
|
|
283
|
+
- `Hitch` (interface): interface Hitch — A frame over {@link HITCH_MS}, with the cause read from what happened inside it.
|
|
284
|
+
- `NamedCost` (interface): interface NamedCost — A named cost over the window: its average per frame and its 95th percentile.
|
|
285
|
+
- `Spread` (interface): interface Spread — Percentiles of one series.
|
|
286
|
+
|
|
287
|
+
## @jgengine/core/devtools/frameBreakdown.draw
|
|
288
|
+
|
|
289
|
+
- `BreakdownRenderer` (interface): interface BreakdownRenderer — What the breakdown needs from a three.js WebGLRenderer.
|
|
290
|
+
- `BreakdownStore` (interface): interface BreakdownStore — What it needs from the r3f store: the subscriber list and the subscribe call.
|
|
291
|
+
- `FrameBreakdown` (interface): interface FrameBreakdown — A running breakdown.
|
|
292
|
+
- `measureComposerPasses` (function): function measureComposerPasses(composer: { passes: readonly object[] }): void — Time each post pass of a composer by name, CPU and GPU. Cheap when no breakdown is installed; call it each frame before `composer.render` so passes added later are timed too.
|
|
293
|
+
|
|
277
294
|
## @jgengine/core/devtools/frameSampler.view
|
|
278
295
|
|
|
279
296
|
- `FrameReportInputs` (interface): interface FrameReportInputs — Everything the report needs, gathered by whichever producer is asking.
|
|
@@ -364,7 +381,7 @@
|
|
|
364
381
|
- `AngleUnit` (type): type AngleUnit = "rad" | "deg" — ⚠ undocumented
|
|
365
382
|
- `CONTROL_SCHEMA_VERSION` (const): const CONTROL_SCHEMA_VERSION: 1 — ⚠ undocumented
|
|
366
383
|
- `DevtoolsControlKind` (type): type DevtoolsControlKind = | "slider" | "toggle" | "color" | "select" | "text" | "vec2" | "vec3" | "vec4" | "interval" | "angle" | "enum" — ⚠ undocumented
|
|
367
|
-
- `DevtoolsOverrides` (interface): interface DevtoolsOverrides { version: number; enabled: string[]; values: Record<string, unknown>; schemas?: Record<string, {kind: DevtoolsControlKind; schemaVersion?: number}> } — ⚠ undocumented
|
|
384
|
+
- `DevtoolsOverrides` (interface): interface DevtoolsOverrides { version: number; enabled: string[]; values: Record<string, unknown>; schemas?: Record<string, {kind: DevtoolsControlKind; schemaVersion?: number | undefined}> | undefined } — ⚠ undocumented
|
|
368
385
|
- `DiscoverySkip` (interface): interface DiscoverySkip { readonly path: string; readonly reason: string } — ⚠ undocumented
|
|
369
386
|
- `MAX_SCAN_DEPTH` (const): const MAX_SCAN_DEPTH: 5 — ⚠ undocumented
|
|
370
387
|
- `MAX_SCAN_TARGETS` (const): const MAX_SCAN_TARGETS: 512 — ⚠ undocumented
|
|
@@ -395,6 +412,7 @@
|
|
|
395
412
|
- `DiscoveredEntry` (interface): interface DiscoveredEntry — A control auto-discovered by scanning an object/table, with its binding metadata and current reader.
|
|
396
413
|
- `FrameRecordSample` (interface): interface FrameRecordSample — Per-frame timing input recorded by callers: total frame time, sim time, and optional named phase durations.
|
|
397
414
|
- `FrameStats` (interface): interface FrameStats — Aggregated frame-timing statistics over the recent sampling window, including per-phase breakdown.
|
|
415
|
+
- `LastFrameSample` (interface): interface LastFrameSample — The last frame `devtools.frame.record` saw: its sim milliseconds and named phases.
|
|
398
416
|
- `LatencyStats` (interface): interface LatencyStats — Aggregated latency statistics (last/avg/min/max) over recorded latency samples.
|
|
399
417
|
- `LongFrameEvent` (interface): interface LongFrameEvent — Recorded spike event for a frame that exceeded the long-frame threshold, with culprit attribution.
|
|
400
418
|
- `OverrideApplyResult` (interface): interface OverrideApplyResult — Outcome of applying a devtools overrides payload: counts of applied and skipped entries plus diagnostics.
|
|
@@ -420,11 +438,11 @@
|
|
|
420
438
|
|
|
421
439
|
- `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
|
|
422
440
|
- `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
|
|
423
|
-
- `VERSION` (const): const VERSION: "0.
|
|
441
|
+
- `VERSION` (const): const VERSION: "0.49.1" — Installed `@jgengine/core` semver - compare against {@link CHANGELOG} keys when migrating.
|
|
424
442
|
|
|
425
443
|
## @jgengine/core/runtime/adapter
|
|
426
444
|
|
|
427
|
-
- `MultiplayerAdapterConfig` (type): type MultiplayerAdapterConfig = | { kind: "convex"; topology?: MultiplayerTopology; authority?: MultiplayerAuthority } | { kind: "ws"; topology?: MultiplayerTopology; url?: string; authority?: MultiplayerAuthority } | { kind: "socketio";
|
|
445
|
+
- `MultiplayerAdapterConfig` (type): type MultiplayerAdapterConfig = | { kind: "convex"; topology?: MultiplayerTopology | undefined; authority?: MultiplayerAuthority | undefined } | { kind: "ws"; topology?: MultiplayerTopology | undefined; url?: string | undefined; authority?: MultiplayerAuthority | undefined } | { kind: "socketio"; to… — ⚠ undocumented · used by `convex`: Convex transport.
|
|
428
446
|
- `MultiplayerAuthority` (type): type MultiplayerAuthority = "server" | "client" — Where the world simulation is authoritative.
|
|
429
447
|
- `MultiplayerTopology` (type): type MultiplayerTopology = "shared" | "lobbies" | "private" — ⚠ undocumented · used by `convex`: Convex transport.
|
|
430
448
|
- `ServersPoolConfig` (type): type ServersPoolConfig = { minPlayersToStart?: number; adapter: MultiplayerAdapterConfig; } & ( | { topology: "shared"; maxServers?: number; slotsPerServer?: number } | { topology?: "rooms"; maxServers: number; slotsPerServer: number } ) — ⚠ undocumented · used by `servers`: Resolve the shared-world pool to one unbounded world; room pools keep explicit capacities.
|
|
@@ -536,7 +554,7 @@
|
|
|
536
554
|
|
|
537
555
|
## @jgengine/core/runtime/gameRuntime
|
|
538
556
|
|
|
539
|
-
- `GameRuntime` (type): type GameRuntime = { gameId: string; topology?: "shared" | "rooms"; save: SaveConfig; /** * Whether this runtime declares `loop.onTick`. A host's tick cron reads it to skip hydrating and * persisting a server whose `tick` is a no-op by construction — the difference between a world that
|
|
557
|
+
- `GameRuntime` (type): type GameRuntime = { gameId: string; topology?: "shared" | "rooms" | undefined; save: SaveConfig; /** * Whether this runtime declares `loop.onTick`. A host's tick cron reads it to skip hydrating and * persisting a server whose `tick` is a no-op by construction — the difference between a world that *… — ⚠ undocumented · used by `initialPlayerState`: Seed a fresh player through the same onNewPlayer hook as joining, without hydrating or modifying a world.
|
|
540
558
|
- `GameRuntimeDefinition` (type): type GameRuntimeDefinition = { gameId: string; topology?: "shared" | "rooms"; save: SaveConfig; commands: Record<string, CommandDef>; loop?: ServerLoopHooks; } — ⚠ undocumented
|
|
541
559
|
- `HydrateInput` (type): type HydrateInput = { gameId: string; serverId: string; serverRow: RuntimeServerRow; playersByUserId: Record<string, RuntimePlayerRow>; chunksByKey: Record<string, RuntimeChunkRow>; revision?: number; /** Host wall clock for `onInit`, in ms. Defaults to `Date.now()`. */ nowMs?: number; } — ⚠ undocumented
|
|
542
560
|
- `RuntimeInitContext` (type): type RuntimeInitContext = { snapshot: GameRuntimeSnapshot; setSnapshot: (snapshot: GameRuntimeSnapshot) => void; /** * Host wall clock at the start of this call, in ms. The host already knows it, so anything keyed to * real time — a UTC date rollover, a `lastTickAt` anchor other code paths read, a s… — ⚠ undocumented
|
|
@@ -548,9 +566,11 @@
|
|
|
548
566
|
## @jgengine/core/runtime/headlessRunner
|
|
549
567
|
|
|
550
568
|
- `HeadlessCommandInterface` (interface): interface HeadlessCommandInterface — Canvas-free command/intent surface for {@link HeadlessRunner} — invoke a game's registered UI commands and read the resulting reactive state off `ctx`.
|
|
569
|
+
- `HeadlessGameSource` (type): type HeadlessGameSource<TAssetRef extends ModelAssetRef, TMultiplayer> = | { game: HeadlessPlayable; definition?: never; content?: never; loop?: never; movement?: never; collision?: never; } | { definition: GameDefinition<TAssetRef, TMultiplayer>; game?: never; /** Catalog resolvers (`entityById`/`o… — Where a headless run gets its game. Pass the whole playable (`game`, what `defineGame` returns) and the runner reads its content, its phase-synced loop, its movement and collision config and whether the shell walks the player, the way play does. Pass a bare `definition` only for a test that builds a partial game; then content, loop, movement and collision are yours to pass, and passing them alongside `game` is a type error.
|
|
551
570
|
- `HeadlessInput` (interface): interface HeadlessInput — One step's worth of player intent handed to {@link HeadlessRunner.step} — the held-action set and pointer state the shell would otherwise publish from the browser each frame.
|
|
552
|
-
- `
|
|
553
|
-
- `
|
|
571
|
+
- `HeadlessPlayable` (type): type HeadlessPlayable<TProbe extends ProbeReadings = ProbeReadings> = PlayableGameContract<unknown, unknown, unknown, unknown, unknown, unknown, TProbe> — Any playable game, whatever its UI and render types (what `defineGame` returns); `TProbe` is what its `capture.probe` returns.
|
|
572
|
+
- `HeadlessRunner` (interface): interface HeadlessRunner — A renderer-free driver for a game loop: builds a {@link GameContext} from a game, runs the init hooks, then advances the simulation one step at a time from injected input. No React, R3F, or three.js — the whole play path (time, input, `onTick`, behaviour nav, player movement) runs from `core` primitives alone, so a non-React host (a server tick, a test, a CLI replay) can play a real game and read its world snapshot. The shell's FrameDriver is one such driver bolted to `useFrame`; this is the same step distilled out of the render tree.
|
|
573
|
+
- `HeadlessRunnerOptions` (type): type HeadlessRunnerOptions<TAssetRef extends ModelAssetRef, TMultiplayer> = HeadlessGameSource<TAssetRef, TMultiplayer> & { /** The single local player; defaults to `{ userId: "player", isNew: true }`. */ player?: { userId: string; isNew: boolean }; /** Wall-clock source for the sim clock — inject a… — ⚠ undocumented
|
|
554
574
|
- `createHeadlessRunner` (function): function createHeadlessRunner<TAssetRef extends ModelAssetRef, TMultiplayer>(options: HeadlessRunnerOptions<TAssetRef, TMultiplayer>): HeadlessRunner — ⚠ undocumented
|
|
555
575
|
|
|
556
576
|
## @jgengine/core/runtime/hostPersistence
|
|
@@ -566,7 +586,7 @@
|
|
|
566
586
|
- `OPEN_SERVER_LISTING_LIMIT` (const): const OPEN_SERVER_LISTING_LIMIT: 20 — ⚠ undocumented
|
|
567
587
|
- `OPEN_SERVER_LISTING_MAX` (const): const OPEN_SERVER_LISTING_MAX: 100 — ⚠ undocumented
|
|
568
588
|
- `PlayerProfileRecord` (type): type PlayerProfileRecord = { userId: string; gameId: string; playerState: RuntimePlayerRow; revision: number; updatedAt: number; } — ⚠ undocumented
|
|
569
|
-
- `ServerListing` (type): type ServerListing = { serverId: string; status: GameServerStatus; memberCount: number; slotsPerServer: number; mode?: string; label?: string; visibility?: SessionVisibility; joinCode?: string; tags?: string[]; updatedAt: number; } — ⚠ undocumented
|
|
589
|
+
- `ServerListing` (type): type ServerListing = { serverId: string; status: GameServerStatus; memberCount: number; slotsPerServer: number; mode?: string | undefined; label?: string | undefined; visibility?: SessionVisibility | undefined; joinCode?: string; tags?: string[] | undefined; updatedAt: number; } — ⚠ undocumented
|
|
570
590
|
- `ServerPersistPlan` (type): type ServerPersistPlan = { server: GameServerRecord; profiles: PlayerProfileRecord[]; chunks: WorldChunkRecord[]; deletedChunks: string[]; leaderboard: LeaderboardIncrement[]; /** * Which fields of the server row the plan actually moved. A store that pays per written field — * or whose subscribers r… — ⚠ undocumented
|
|
571
591
|
- `SessionAttributes` (type): type SessionAttributes = { label?: string; mode?: string; visibility?: SessionVisibility; joinCode?: string; tags?: string[]; } — ⚠ undocumented
|
|
572
592
|
- `StoredLeaderboardRow` (type): type StoredLeaderboardRow = LeaderboardRow & { gameId: string; updatedAt: number; } — A {@link LeaderboardRow} as a host stores it: keyed by game, stamped with its last write.
|
|
@@ -685,7 +705,7 @@
|
|
|
685
705
|
- `SimContextState` (interface): interface SimContextState — Serializable `ctx.sim` state: loop position plus the interpolation pose history.
|
|
686
706
|
- `SimStage` (interface): interface SimStage — Extra work a game or engine seam (a physics backend, a netcode buffer) runs inside every simulation step.
|
|
687
707
|
- `SimStagePhase` (type): type SimStagePhase = "beforeMovement" | "afterMovement" | "afterTick" — Where a registered stage runs inside one simulation step.
|
|
688
|
-
- `createSimContext` (function): function createSimContext(options: { config?: SimulationConfig; entities: EntityStore; time?: Pick<SimClock, "advance"> }): SimContext — Build `ctx.sim` for a context; `createGameContext` calls this from `definition.simulation`.
|
|
708
|
+
- `createSimContext` (function): function createSimContext(options: { config?: SimulationConfig | undefined; entities: EntityStore; time?: Pick<SimClock, "advance"> }): SimContext — Build `ctx.sim` for a context; `createGameContext` calls this from `definition.simulation`.
|
|
689
709
|
|
|
690
710
|
## @jgengine/core/runtime/simLoop
|
|
691
711
|
|
|
@@ -700,7 +720,7 @@
|
|
|
700
720
|
- `SimSnapshot` (type): type SimSnapshot = Record<string, unknown> — Captured contributor data keyed by contributor id.
|
|
701
721
|
- `SimSnapshotContributor` (interface): interface SimSnapshotContributor<T = unknown> — One piece of simulation state that can be captured and restored by id.
|
|
702
722
|
- `SimSnapshotRegistry` (interface): interface SimSnapshotRegistry — Registry of {@link SimSnapshotContributor}s that captures and restores them together.
|
|
703
|
-
- `createContextSimSnapshot` (function): function createContextSimSnapshot(ctx: GameContext): SimSnapshotRegistry — The engine's own contributors for a context:
|
|
723
|
+
- `createContextSimSnapshot` (function): function createContextSimSnapshot(ctx: GameContext): SimSnapshotRegistry — The engine's own contributors for a context: authoritative world state with every system save, the sim clock, the loop and pose history, and the rng cursor when `ctx.rng` is a seeded stream. Games register theirs on the same registry.
|
|
704
724
|
- `createSimSnapshotRegistry` (function): function createSimSnapshotRegistry(): SimSnapshotRegistry — A registry of everything that must round-trip for a simulation to resume bit-for-bit: world state, the clock, the rng cursor, the loop accumulator, and whatever a physics backend or game adds. Pair with {@link createInputRecorder} for replay.
|
|
705
725
|
|
|
706
726
|
## @jgengine/core/runtime/snapshot
|
|
@@ -791,8 +811,8 @@
|
|
|
791
811
|
|
|
792
812
|
## @jgengine/shell/defineGame
|
|
793
813
|
|
|
794
|
-
- `GameConfig` (type): type GameConfig<TAssetRef extends ModelAssetRef = ModelAssetRef> = EngineFields<TAssetRef> & PresentationFields — ⚠ undocumented · used by `defineGame`: The one public authoring entry point: compose engine fields (systems, world, physics, input) and presentation fields (camera, HUD, audio, au…
|
|
795
|
-
- `defineGame` (function): function defineGame<TAssetRef extends ModelAssetRef = ModelAssetRef>(config: GameConfig<TAssetRef>): PlayableGame — The one public authoring entry point: compose engine fields (systems, world, physics, input) and presentation fields (camera, HUD, audio, authored scene) into a `PlayableGame` ready for `GameHost`. Defaults to solo/offline multiplayer; `editorLayers` auto-mounts the authored scene document.
|
|
814
|
+
- `GameConfig` (type): type GameConfig<TAssetRef extends ModelAssetRef = ModelAssetRef, TProbe extends ProbeReadings = ProbeReadings> = EngineFields<TAssetRef> & PresentationFields<TProbe> — ⚠ undocumented · used by `defineGame`: The one public authoring entry point: compose engine fields (systems, world, physics, input) and presentation fields (camera, HUD, audio, au…
|
|
815
|
+
- `defineGame` (function): function defineGame<TAssetRef extends ModelAssetRef = ModelAssetRef, TProbe extends ProbeReadings = ProbeReadings>(config: GameConfig<TAssetRef, TProbe>): PlayableGame<TProbe> — The one public authoring entry point: compose engine fields (systems, world, physics, input) and presentation fields (camera, HUD, audio, authored scene) into a `PlayableGame` ready for `GameHost`. Defaults to solo/offline multiplayer; `editorLayers` auto-mounts the authored scene document.
|
|
796
816
|
|
|
797
817
|
## @jgengine/shell/gameKit
|
|
798
818
|
|
|
@@ -804,7 +824,7 @@
|
|
|
804
824
|
- `EditorDocument` (interface): interface EditorDocument — The full authored scene: every marker, volume, path, note, and sculpted terrain for a game.
|
|
805
825
|
- `EditorSummonModule` (interface): interface EditorSummonModule — Structural shape of the module `GameHost`'s `editor` loader resolves — `import("@jgengine/editor")` satisfies it.
|
|
806
826
|
- `GameCameraConfig` (interface): interface GameCameraConfig — Camera tuning for the shell's rig stack: pick the rig via `rig`, then tune it through its matching config block. All fields optional — the default is the third-person orbit rig.
|
|
807
|
-
- `GameConfig` (type): type GameConfig<TAssetRef extends ModelAssetRef = ModelAssetRef> = EngineFields<TAssetRef> & PresentationFields — ⚠ undocumented · used by `defineGame`: The one public authoring entry point: compose engine fields (systems, world, physics, input) and presentation fields (camera, HUD, audio, au…
|
|
827
|
+
- `GameConfig` (type): type GameConfig<TAssetRef extends ModelAssetRef = ModelAssetRef, TProbe extends ProbeReadings = ProbeReadings> = EngineFields<TAssetRef> & PresentationFields<TProbe> — ⚠ undocumented · used by `defineGame`: The one public authoring entry point: compose engine fields (systems, world, physics, input) and presentation fields (camera, HUD, audio, au…
|
|
808
828
|
- `GameContext` (interface): interface GameContext — The live engine handle a game's loop, systems, commands, and UI read and mutate — entities, objects, the reactive store, opted-in `game.*` subsystems, world queries, and the sim clock. One context = one running world.
|
|
809
829
|
- `GameHost` (function): function GameHost({ playable, gameId, wsUrl, multiplayer, resolveMultiplayer, editor, creator, creatorOpen = false, onCreatorOpenChange }: GameHostProps): React.JSX.Element — The one documented mount: resolves multiplayer for the playable and renders the shell. With the `editor` loader prop it also owns the whole editor summon (F2+E, `?mode=editor`, dev save endpoint).
|
|
810
830
|
- `GameHostProps` (interface): interface GameHostProps — Props for {@link GameHost}: the playable, optional multiplayer overrides, and the optional editor loader that enables the engine-owned F2+E summon.
|
|
@@ -814,12 +834,12 @@
|
|
|
814
834
|
- `HudFade` (function): function HudFade({ watch, idleMs = 3500, dimTo = 0.18, hold = false, children, className, style }: HudFadeProps): ReactNode — A HUD group that steps back when nothing is happening: it fades after a quiet spell and comes back the moment a watched value changes, so the world fills the screen and the HUD speaks only when it has news. Hovering it also brings it back.
|
|
815
835
|
- `HudPanel` (function): function HudPanel({ id, anchor = "top-left", order, chip, interactive, inset, locked, showDuring, priority, mobileBehavior, allowOverlapWith, collisionGroup, region = true, width, height, type, className, style, children, }: { id: string; anchor?: HudAnchor; /** Stack position within the anchor, asc… — A HUD block anchored to a corner, edge or the centre. `HudCanvas` places every panel together: panels sharing an anchor stack outward from the screen edge in ascending `order`, and a panel that would cover another slides to the nearest free spot. When the screen is too small, lower-`priority` panels fold into a toggle first. On fine pointers panels stay draggable through the edit chord; a dragged panel leaves the flow and keeps its custom placement.
|
|
816
836
|
- `HudSlots` (function): function HudSlots({ storageKey, slots, overlay, showDuring = DEFAULT_SLOT_PHASES }: HudSlotsProps): ReactNode — The play HUD in one line: a few small panels pinned to the screen's edges, scaled from a 1600x900 design to any window and movable by the player (the layout is remembered per `storageKey`). Keep it to a handful of slots; the world is the picture.
|
|
817
|
-
- `PlayableGame` (type): type PlayableGame = PlayableGameContract< ComponentType, ComponentType, RenderEntity, RenderObject, ComponentType<ViewmodelProps>, ComponentType<WorldOverlayProps
|
|
837
|
+
- `PlayableGame` (type): type PlayableGame<TProbe extends ProbeReadings = ProbeReadings> = PlayableGameContract< ComponentType, ComponentType, RenderEntity, RenderObject, ComponentType<ViewmodelProps>, ComponentType<WorldOverlayProps>, TProbe > & { /** HUD theme the shell applies at the `GameUI` root. Unset uses `defaultHud… — The concrete React-bound playable a shell `defineGame` returns and `GameHost` mounts — the one PlayableGame games handle. `TProbe` is what the game's `capture.probe` returns, so a proof flow over `typeof game` names only real probes.
|
|
818
838
|
- `StoreHandle` (interface): interface StoreHandle<T> — A typed, cast-free handle onto one slot of the reactive game store (`ctx.game.store`). The single type parameter is fixed at definition; `read`/`write`/`update` never widen to `unknown`, so no call site repeats `store.get(key) as T`. Writes flow through the engine store, so they bump `ctx.version()`, serialize into a {@link WorldSnapshot}, and replay under host authority — the same guarantees a hand-rolled `store.get`/`store.set` pair gives up the moment it forks run state into a module-level singleton.
|
|
819
839
|
- `SystemDefinition` (interface): interface SystemDefinition — A reusable game capability — lifecycle, timing, events, and optional save / replication / reset / disposal. Pass instances via `defineGame({ systems })`. Prefer one system per meaningful capability (`combat`, `quests`), not per micro-tick.
|
|
820
840
|
- `authoredSpawnPosition` (function): function authoredSpawnPosition(document: AuthoredSpawnDocumentLike, options: AuthoredSpawnOptions = {}): [number, number, number] | null — Position of the authored spawn marker as a spawn-ready `[x, y, z]` tuple, or null when the document has none. Reads the first `player_spawn` marker by default, so dragging the marker in the editor moves where players spawn — no coordinates copied into game code.
|
|
821
841
|
- `collectAuthoredTriggers` (function): function collectAuthoredTriggers(document: SceneDocumentLike): AuthoredTrigger[] — Collect every authored trigger on a document's markers and volumes. Pure — no runtime state. Action params use the live {@link registerTriggerAction} registry when present.
|
|
822
|
-
- `defineGame` (function): function defineGame<TAssetRef extends ModelAssetRef = ModelAssetRef>(config: GameConfig<TAssetRef>): PlayableGame — The one public authoring entry point: compose engine fields (systems, world, physics, input) and presentation fields (camera, HUD, audio, authored scene) into a `PlayableGame` ready for `GameHost`. Defaults to solo/offline multiplayer; `editorLayers` auto-mounts the authored scene document.
|
|
842
|
+
- `defineGame` (function): function defineGame<TAssetRef extends ModelAssetRef = ModelAssetRef, TProbe extends ProbeReadings = ProbeReadings>(config: GameConfig<TAssetRef, TProbe>): PlayableGame<TProbe> — The one public authoring entry point: compose engine fields (systems, world, physics, input) and presentation fields (camera, HUD, audio, authored scene) into a `PlayableGame` ready for `GameHost`. Defaults to solo/offline multiplayer; `editorLayers` auto-mounts the authored scene document.
|
|
823
843
|
- `defineKeyedStore` (function): function defineKeyedStore<T>(keyFor: (id: string) => string, initial: T | (() => T)): KeyedStoreHandle<T> — Define a typed per-owner keyed family on the game store: game code expresses `read(ctx, userId)`/`write(ctx, userId, v)`/`update(...)` with the value type it means, and both the `` `prefix:${id}` `` key composition and the `unknown → T` cast live once, here, behind the boundary. Reach for this over N separate `defineStore` calls whenever the owning id varies at runtime (per-user class, per-instance auras) — `defineStore` stays the right call for a single fixed slot.
|
|
824
844
|
- `defineStore` (function): function defineStore<T>(key: string, initial: T | (() => T)): StoreHandle<T> — Define a typed slot on the game store: game code expresses `read(ctx)`/`write(ctx, v)`/`update(...)` with the value type it means, and the `unknown → T` cast lives once, here, behind the boundary. Pass a factory for `initial` when the fallback is a fresh mutable object; the factory runs at most once and its result is reused, so an unwritten slot keeps a stable identity across reads (no per-read churn for a React selector, no allocation on a hot path). A value that carries functions (a closure-based run store, an input handle) is runtime-only: snapshots, saves and replication skip it and hydration keeps it in place. Hold such objects with `perContext` when they need no store notifications.
|
|
825
845
|
- `defineSystem` (function): function defineSystem(definition: SystemDefinition): SystemDefinition — Declare a composable game system. Pure data + hooks — the engine compiles the schedule and installs lifecycle when the game boots.
|
|
@@ -185,15 +185,15 @@
|
|
|
185
185
|
- `PoseSyncTuning` (interface): interface PoseSyncTuning { minIntervalMs: number; heartbeatMs: number; positionEpsilon: number; verticalEpsilon: number; rotationEpsilon: number } — ⚠ undocumented · used by `DEFAULT_POSE_SYNC_TUNING` (@jgengine/core/multiplayer/poseSyncGate): Default ten-hertz client pose gate with a five-second heartbeat.
|
|
186
186
|
- `PresenceActions` (interface): interface PresenceActions<TGameId extends string = string> { ensurePresence(args: EnsurePresenceArgs<TGameId>): Promise<EnsurePresenceResult | null>; leavePresence(args: LeavePresenceArgs<TGameId>): Promise<{left: boolean}>; syncPose(pose: PresencePose): void } — ⚠ undocumented
|
|
187
187
|
- `PresenceFeeds` (interface): interface PresenceFeeds<TRow, TLocation> { myPresenceLocation: TLocation | null | undefined; onlinePresences: readonly TRow[] | undefined; dormantPresences: readonly TRow[] | undefined } — ⚠ undocumented
|
|
188
|
-
- `PresencePoseState` (interface): interface PresencePoseState { position: {x: number; y: number; z: number}; rotationY: number; rotationPitch?: number; lastSeenAtMs?: number; appearance?: Record<string, string> } — ⚠ undocumented
|
|
188
|
+
- `PresencePoseState` (interface): interface PresencePoseState { position: {x: number; y: number; z: number}; rotationY: number; rotationPitch?: number; lastSeenAtMs?: number; appearance?: Record<string, string> | undefined } — ⚠ undocumented
|
|
189
189
|
- `PresenceSession` (interface): interface PresenceSession<TGameId extends string = string> { homeGameId: TGameId; externalId: string; viewerChunkKey?: string } — ⚠ undocumented
|
|
190
190
|
- `PresenceTransport` (interface): interface PresenceTransport<TRow, TLocation, TGameId extends string = string> — Backend seam for multiplayer presence. Feeds are reactive data and change identity whenever any player's pose updates; actions MUST be identity-stable for the lifetime of a mounted session so join/leave lifecycle effects can depend on them without re-running per pose tick. The use* members are called as React hooks by consumers, so a mounted transport must never change identity — remount the subtree to switch backends.
|
|
191
191
|
- `PushToTalkMode` (type): type PushToTalkMode = "hold" | "toggle" | "openMic" — ⚠ undocumented
|
|
192
192
|
- `PushToTalkStatus` (type): type PushToTalkStatus = "idle" | "keyed" | "open" — ⚠ undocumented
|
|
193
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.
|
|
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
|
|
194
|
+
- `SessionListing` (interface): interface SessionListing { serverId: string; gameId: string; status: SessionStatus; visibility: SessionVisibility; memberCount: number; slotsPerServer: number; label?: string | undefined; mode?: string | undefined; joinCode?: string | undefined; tags?: readonly str… — ⚠ undocumented · used by `useWorldBrowser` (@jgengine/react): Polls a host-supplied session fetcher (e.g.
|
|
195
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.
|
|
196
|
-
- `VoiceParticipant` (interface): interface VoiceParticipant { userId: string; streamId?: string } — ⚠ undocumented
|
|
196
|
+
- `VoiceParticipant` (interface): interface VoiceParticipant { userId: string; streamId?: string | undefined } — ⚠ undocumented
|
|
197
197
|
- `VoiceRoute` (interface): interface VoiceRoute { fromUserId: string; channelId: string; gain: number } — ⚠ undocumented
|
|
198
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.
|
|
199
199
|
- `browseSessions` (function): function browseSessions(listings: readonly SessionListing[], filter: MatchFilter = {}, options: BrowseOptions = {}): SessionListing[] — ⚠ undocumented
|
|
@@ -268,9 +268,9 @@
|
|
|
268
268
|
|
|
269
269
|
## @jgengine/core/multiplayer/matchmaking
|
|
270
270
|
|
|
271
|
-
- `BrowseOptions` (interface): interface BrowseOptions { limit?: number } — ⚠ undocumented
|
|
271
|
+
- `BrowseOptions` (interface): interface BrowseOptions { limit?: number | undefined } — ⚠ undocumented
|
|
272
272
|
- `MatchFilter` (interface): interface MatchFilter { mode?: string; tags?: readonly string[]; query?: string; notFull?: boolean; status?: readonly SessionStatus[]; includePrivate?: boolean } — ⚠ undocumented · used by `useWorldBrowser` (@jgengine/react): Polls a host-supplied session fetcher (e.g.
|
|
273
|
-
- `SessionListing` (interface): interface SessionListing { serverId: string; gameId: string; status: SessionStatus; visibility: SessionVisibility; memberCount: number; slotsPerServer: number; label?: string; mode?: string; joinCode?: string; tags?: readonly
|
|
273
|
+
- `SessionListing` (interface): interface SessionListing { serverId: string; gameId: string; status: SessionStatus; visibility: SessionVisibility; memberCount: number; slotsPerServer: number; label?: string | undefined; mode?: string | undefined; joinCode?: string | undefined; tags?: readonly str… — ⚠ undocumented · used by `useWorldBrowser` (@jgengine/react): Polls a host-supplied session fetcher (e.g.
|
|
274
274
|
- `SessionStatus` (type): type SessionStatus = "open" | "running" | "closed" — ⚠ undocumented
|
|
275
275
|
- `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.
|
|
276
276
|
- `browseSessions` (function): function browseSessions(listings: readonly SessionListing[], filter: MatchFilter = {}, options: BrowseOptions = {}): SessionListing[] — ⚠ undocumented
|
|
@@ -286,7 +286,7 @@
|
|
|
286
286
|
- `DEFAULT_FEED_ACTIONS` (const): const DEFAULT_FEED_ACTIONS: string[] — ⚠ undocumented
|
|
287
287
|
- `PeerShellFactories` (interface): interface PeerShellFactories — Alternate peer factories for hosts that provide their own WebRTC implementation or transport pipe.
|
|
288
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
|
|
289
|
+
- `ResolveShellMultiplayerArgs` (type): type ResolveShellMultiplayerArgs = { game: GameDefinition; gameId: string; url?: string | undefined; userId?: string; force?: boolean; feedActions?: string[]; } — ⚠ undocumented
|
|
290
290
|
- `ShellMultiplayer` (type): type ShellMultiplayer = MultiplayerSession — ⚠ undocumented · used by `resolvePeerShellMultiplayer`: Opens a peer session.
|
|
291
291
|
- `randomPlayerId` (function): function randomPlayerId(): string — ⚠ undocumented
|
|
292
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.
|
|
@@ -320,9 +320,9 @@
|
|
|
320
320
|
- `DEFAULT_POSE_SYNC_RULES` (const): const DEFAULT_POSE_SYNC_RULES: PoseSyncRules — Canonical client-authoritative pose-sync tuning shared by every host transport (WS, Convex).
|
|
321
321
|
- `DEFAULT_SPAWN_ORIGIN` (const): const DEFAULT_SPAWN_ORIGIN: { readonly x: 0; readonly y: 0; readonly z: 0; } — ⚠ undocumented
|
|
322
322
|
- `IncomingPose` (interface): interface IncomingPose { position: {x: number; z: number; y?: number}; rotationY?: number; rotationPitch?: number; appearance?: Record<string, string> } — ⚠ undocumented
|
|
323
|
-
- `PoseSyncDecision` (interface): interface PoseSyncDecision { position: {x: number; y: number; z: number}; rotationY: number; rotationPitch: number; appearance?: Record<string, string
|
|
323
|
+
- `PoseSyncDecision` (interface): interface PoseSyncDecision { position: {x: number; y: number; z: number}; rotationY: number; rotationPitch: number; appearance?: Record<string, string> | undefined; changed: boolean; refreshKeepAlive: boolean } — ⚠ undocumented
|
|
324
324
|
- `PoseSyncRules` (interface): interface PoseSyncRules { maxSpeed: number; minIntervalMs?: number; maxVerticalOffset: number; floorY?: number; minElapsedSec: number; maxElapsedSec: number; keepAliveRefreshMs: number } — ⚠ undocumented · used by `DEFAULT_POSE_SYNC_RULES`: Canonical client-authoritative pose-sync tuning shared by every host transport (WS, Convex).
|
|
325
|
-
- `PresencePoseState` (interface): interface PresencePoseState { position: {x: number; y: number; z: number}; rotationY: number; rotationPitch?: number; lastSeenAtMs?: number; appearance?: Record<string, string> } — ⚠ undocumented
|
|
325
|
+
- `PresencePoseState` (interface): interface PresencePoseState { position: {x: number; y: number; z: number}; rotationY: number; rotationPitch?: number; lastSeenAtMs?: number; appearance?: Record<string, string> | undefined } — ⚠ undocumented
|
|
326
326
|
|
|
327
327
|
## @jgengine/core/multiplayer/simultaneousCommit
|
|
328
328
|
|
|
@@ -350,7 +350,7 @@
|
|
|
350
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
|
|
351
351
|
- `PushToTalkMode` (type): type PushToTalkMode = "hold" | "toggle" | "openMic" — ⚠ undocumented
|
|
352
352
|
- `PushToTalkStatus` (type): type PushToTalkStatus = "idle" | "keyed" | "open" — ⚠ undocumented
|
|
353
|
-
- `VoiceParticipant` (interface): interface VoiceParticipant { userId: string; streamId?: string } — ⚠ undocumented
|
|
353
|
+
- `VoiceParticipant` (interface): interface VoiceParticipant { userId: string; streamId?: string | undefined } — ⚠ undocumented
|
|
354
354
|
- `VoiceRoute` (interface): interface VoiceRoute { fromUserId: string; channelId: string; gain: number } — ⚠ undocumented
|
|
355
355
|
- `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.
|
|
356
356
|
- `createLocalVoiceTransport` (function): function createLocalVoiceTransport(options?: { userId?: string }): { transport: VoiceTransport; participants(channelId: string): readonly VoiceParticipant[]; } — ⚠ undocumented
|
|
@@ -365,8 +365,8 @@
|
|
|
365
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.
|
|
366
366
|
- `DEFAULT_MAX_CONNECTIONS` (const): const DEFAULT_MAX_CONNECTIONS: 10000 — Default max concurrent sockets this server accepts before rejecting new ones.
|
|
367
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.
|
|
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;
|
|
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.
|
|
368
|
+
- `GameServerHost` (type): type GameServerHost = { joinServer: (args: { userId: string; gameId: string; serverId?: string | undefined; attributes?: SessionAttributes | undefined; code?: string | undefined; role?: SnapshotViewer["role"] | undefined; }) => Promise<JoinServerResult>; browseServers: (args: { gameId: string; filte… — A transport-agnostic authoritative game server host that manages sessions, ticking, and persistence.
|
|
369
|
+
- `GameServerHostOptions` (type): type GameServerHostOptions = { runtimes?: GameRuntime[] | undefined; persistence: HostPersistence; tickMs?: number | undefined; slotsPerServer?: number; now?: () => number; createServerId?: () => string; allowedFeedActions?: readonly string[]; } — Configuration for {@link createGameServerHost}, including persistence, tick rate, and game runtimes.
|
|
370
370
|
- `GameSocketIoServer` (type): type GameSocketIoServer = { rewind: (args: { serverId: string; atMs: number }) => RewoundPosition[]; close: () => void; } — ⚠ undocumented
|
|
371
371
|
- `GameSocketIoServerOptions` (type): type GameSocketIoServerOptions = HostRouterOptions & { io: SocketIoLikeServer } — ⚠ undocumented
|
|
372
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
|
|
@@ -422,8 +422,8 @@
|
|
|
422
422
|
|
|
423
423
|
## @jgengine/node/host
|
|
424
424
|
|
|
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;
|
|
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.
|
|
425
|
+
- `GameServerHost` (type): type GameServerHost = { joinServer: (args: { userId: string; gameId: string; serverId?: string | undefined; attributes?: SessionAttributes | undefined; code?: string | undefined; role?: SnapshotViewer["role"] | undefined; }) => Promise<JoinServerResult>; browseServers: (args: { gameId: string; filte… — A transport-agnostic authoritative game server host that manages sessions, ticking, and persistence.
|
|
426
|
+
- `GameServerHostOptions` (type): type GameServerHostOptions = { runtimes?: GameRuntime[] | undefined; persistence: HostPersistence; tickMs?: number | undefined; slotsPerServer?: number; now?: () => number; createServerId?: () => string; allowedFeedActions?: readonly string[]; } — Configuration for {@link createGameServerHost}, including persistence, tick rate, and game runtimes.
|
|
427
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
428
|
- `createGameServerHost` (function): function createGameServerHost(options: GameServerHostOptions): GameServerHost — Creates a `GameServerHost` that runs game servers over the given persistence and runtimes.
|
|
429
429
|
- `memoryPersistence` (function): function memoryPersistence(now: () => number = Date.now): HostPersistence — Creates an in-memory `HostPersistence` implementation, useful for tests and ephemeral hosts.
|
|
@@ -506,25 +506,25 @@
|
|
|
506
506
|
|
|
507
507
|
## @jgengine/ws
|
|
508
508
|
|
|
509
|
-
- `CommandAuthorize` (type): type CommandAuthorize = (args: { userId: string; op: HostCommandOp; serverId?: string; command?: string; }) => boolean | Promise<boolean> — Per-command authorization hook: return `false` to reject. Defaults to allow-all when omitted.
|
|
509
|
+
- `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.
|
|
510
510
|
- `CommandCatalog` (type): type CommandCatalog = Record<string, CommandCatalogEntry> — Declared `runCommand` names and their input validators. When set, any `runCommand` name absent from this catalog is rejected as unknown.
|
|
511
511
|
- `CommandCatalogEntry` (type): type CommandCatalogEntry = { validate?: (input: unknown) => CommandRejection | null; } — A declared `runCommand` name's input validator, run before the command reaches the game host.
|
|
512
|
-
- `CommandGateArgs` (type): type CommandGateArgs = { connection: object; userId: string; op: HostCommandOp; atMs: number; serverId?: string; command?: string; input?: unknown; } — One op attempt to run through the middleware pipeline: which connection, which op, and (for `runCommand`) the command name/input.
|
|
512
|
+
- `CommandGateArgs` (type): type CommandGateArgs = { connection: object; userId: string; op: HostCommandOp; atMs: number; serverId?: string | undefined; command?: string; input?: unknown; } — One op attempt to run through the middleware pipeline: which connection, which op, and (for `runCommand`) the command name/input.
|
|
513
513
|
- `CommandGateDecision` (type): type CommandGateDecision = { allow: true } | { allow: false; reason: string } — The pipeline's verdict for one {@link CommandGateArgs}: allowed, or rejected with a client-facing reason.
|
|
514
514
|
- `CommandLimits` (type): type CommandLimits = Partial<Record<HostCommandOp, CommandRateLimit>> — Per-op rate limits. An op with no entry (or an undefined `limits`) is unlimited.
|
|
515
515
|
- `CommandMiddleware` (type): type CommandMiddleware = { check: (args: CommandGateArgs) => Promise<CommandGateDecision>; } — A composable rate-limit → validate → authorize pipeline the host router runs before dispatching pose/runCommand/join/browse/voice ops. Every stage defaults to a no-op, so an unconfigured router behaves exactly as before.
|
|
516
|
-
- `CommandMiddlewareOptions` (type): type CommandMiddlewareOptions = { limits?: CommandLimits; authorize?: CommandAuthorize; validate?: CommandCatalog; } — Config for {@link createCommandMiddleware}: the same `limits`/`authorize`/`validate` fields accepted by `HostRouterOptions`.
|
|
516
|
+
- `CommandMiddlewareOptions` (type): type CommandMiddlewareOptions = { limits?: CommandLimits; authorize?: CommandAuthorize | undefined; validate?: CommandCatalog | undefined; } — Config for {@link createCommandMiddleware}: the same `limits`/`authorize`/`validate` fields accepted by `HostRouterOptions`.
|
|
517
517
|
- `CommandRateLimit` (type): type CommandRateLimit = { count: number; perMs: number } — A sliding-window rate limit: at most `count` calls per `perMs` window.
|
|
518
518
|
- `CommandRateLimiter` (type): type CommandRateLimiter = { allow: (connection: object, op: HostCommandOp, atMs: number) => boolean; } — A composable per-connection/per-op sliding-window rate limiter.
|
|
519
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.
|
|
520
520
|
- `DEFAULT_GRACE_MS` (const): const DEFAULT_GRACE_MS: 15000 — Default reconnect grace period for hosted socket sessions.
|
|
521
521
|
- `DEFAULT_POSE_RULES` (const): const DEFAULT_POSE_RULES: PoseSyncRules — ⚠ undocumented
|
|
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;
|
|
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.
|
|
522
|
+
- `GameServerHost` (type): type GameServerHost = { joinServer: (args: { userId: string; gameId: string; serverId?: string | undefined; attributes?: SessionAttributes | undefined; code?: string | undefined; role?: SnapshotViewer["role"] | undefined; }) => Promise<JoinServerResult>; browseServers: (args: { gameId: string; filte… — A transport-agnostic authoritative game server host that manages sessions, ticking, and persistence.
|
|
523
|
+
- `GameServerHostOptions` (type): type GameServerHostOptions = { runtimes?: GameRuntime[] | undefined; persistence: HostPersistence; tickMs?: number | undefined; slotsPerServer?: number; now?: () => number; createServerId?: () => string; allowedFeedActions?: readonly string[]; } — Configuration for {@link createGameServerHost}, including persistence, tick rate, and game runtimes.
|
|
524
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.
|
|
525
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.
|
|
526
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
|
|
527
|
-
- `HostRouterAuthenticate` (type): type HostRouterAuthenticate = (args: { userId: string; token?: string; }) => Promise<string | null> | string | null — ⚠ undocumented
|
|
527
|
+
- `HostRouterAuthenticate` (type): type HostRouterAuthenticate = (args: { userId: string; token?: string | undefined; }) => Promise<string | null> | string | null — ⚠ undocumented
|
|
528
528
|
- `HostRouterConnection` (type): type HostRouterConnection = { handleRaw: (raw: unknown) => void; close: () => void; } — ⚠ undocumented
|
|
529
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
|
|
530
530
|
- `HostRouterTransport` (type): type HostRouterTransport = { send: (data: string) => void; close: () => void; } — ⚠ undocumented
|
|
@@ -546,7 +546,7 @@
|
|
|
546
546
|
- `PeerSignaling` (type): type PeerSignaling = { publishOffer: (offerCode: string) => Promise<string>; onOffer: (answer: (offerCode: string) => Promise<string>) => () => void; close: () => void; } — ⚠ undocumented
|
|
547
547
|
- `ReadsAuthenticate` (type): type ReadsAuthenticate = ( request: Request, ) => Promise<string | null> | string | null — ⚠ undocumented
|
|
548
548
|
- `ReadsHandler` (type): type ReadsHandler = (request: Request) => Promise<Response> — ⚠ undocumented
|
|
549
|
-
- `ReadsHandlerOptions` (type): type ReadsHandlerOptions = { persistence: ReadsPersistence | (() => Promise<ReadsPersistence>); basePath?: string; listOpenServers?: (args: { gameId: string; limit?: number }) => Promise<ServerListing[]>; authenticate?: ReadsAuthenticate; allowPublicProfiles?: boolean; } — ⚠ undocumented
|
|
549
|
+
- `ReadsHandlerOptions` (type): type ReadsHandlerOptions = { persistence: ReadsPersistence | (() => Promise<ReadsPersistence>); basePath?: string; listOpenServers?: (args: { gameId: string; limit?: number | undefined }) => Promise<ServerListing[]>; authenticate?: ReadsAuthenticate; allowPublicProfiles?: boolean; } — ⚠ undocumented
|
|
550
550
|
- `ReadsPersistence` (type): type ReadsPersistence = Pick< HostPersistence, "listServers" | "loadProfile" | "getLeaderboardTop" | "getLeaderboardProfile" > — ⚠ undocumented
|
|
551
551
|
- `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
552
|
- `SocketIoBackendOptions` (type): type SocketIoBackendOptions = Omit<WsBackendOptions, "url" | "pipe" | "webSocketFactory"> & { socket: SocketIoLikeSocket; } — ⚠ undocumented
|
|
@@ -564,22 +564,22 @@
|
|
|
564
564
|
- `WorldGameHostOptions` (interface): interface WorldGameHostOptions — Config for {@link createWorldGameHost}: how to resolve a hosted world's authoritative session per server.
|
|
565
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.
|
|
566
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
|
|
567
|
-
- `WsBackendOptions` (type): type WsBackendOptions = { url?: string; pipe?: TransportPipeFactory; userId: string; token?: string; webSocketFactory?: (url: string) => WebSocket; reconnectDelayMs?: number; maxReconnectDelayMs?: number; rpcTimeoutMs?: number; poseTuning?: PoseSyncTuning; now?: () => number;
|
|
567
|
+
- `WsBackendOptions` (type): type WsBackendOptions = { url?: string; pipe?: TransportPipeFactory; userId: string; token?: string | undefined; webSocketFactory?: (url: string) => WebSocket; reconnectDelayMs?: number; maxReconnectDelayMs?: number; rpcTimeoutMs?: number; poseTuning?: PoseSyncTuning; now?: () => number; setTimeoutF… — ⚠ undocumented
|
|
568
568
|
- `WsBrowseResult` (type): type WsBrowseResult = SessionListing[] — ⚠ undocumented
|
|
569
569
|
- `WsChannel` (type): type WsChannel = "server" | "player" | "feed" | "presence" | "chat" | "voice" — ⚠ undocumented
|
|
570
570
|
- `WsChatMessage` (type): type WsChatMessage = { id: string; channelId: string; fromUserId: string; body: string; at: number; } — ⚠ undocumented
|
|
571
571
|
- `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 } | { v: 1; t: "join"; id: number; gameId: string; serverId?: string; attributes?: SessionAttributes; code?: string
|
|
573
|
-
- `WsDecodeFailure` (type): type WsDecodeFailure = { reason: string; id?: number; } — ⚠ 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 | undefined; code?: string | undefined; r… — ⚠ undocumented
|
|
573
|
+
- `WsDecodeFailure` (type): type WsDecodeFailure = { reason: string; id?: number | undefined; } — ⚠ undocumented
|
|
574
574
|
- `WsJoinByCodeResult` (type): type WsJoinByCodeResult = JoinServerResult | null — ⚠ undocumented
|
|
575
575
|
- `WsJoinResult` (type): type WsJoinResult = JoinServerResult — ⚠ undocumented
|
|
576
|
-
- `WsPose` (type): type WsPose = PlayerPose & { appearance?: WsAppearance } — ⚠ undocumented
|
|
577
|
-
- `WsPresenceRow` (type): type WsPresenceRow = PresencePoseRow & { appearance?: WsAppearance } — ⚠ undocumented
|
|
576
|
+
- `WsPose` (type): type WsPose = PlayerPose & { appearance?: WsAppearance | undefined } — ⚠ undocumented
|
|
577
|
+
- `WsPresenceRow` (type): type WsPresenceRow = PresencePoseRow & { appearance?: WsAppearance | undefined } — ⚠ undocumented
|
|
578
578
|
- `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
|
|
579
579
|
- `WsRunCommandResult` (type): type WsRunCommandResult = TransportRunCommandResult — ⚠ undocumented
|
|
580
580
|
- `WsServerMessage` (type): type WsServerMessage = | { v: 1; t: "pong"; id: number; at: number; serverAt: number } | { v: 1; t: "reply"; id: number; ok: true; result?: unknown } | { v: 1; t: "reply"; id: number; ok: false; reason: string } | WsUpdateMessage — ⚠ undocumented
|
|
581
581
|
- `WsUpdateMessage` (type): type WsUpdateMessage = | { v: 1; t: "update"; channel: "server"; serverId: string; data: GameRuntimeServerView | null } | { v: 1; t: "update"; channel: "player"; serverId: string; data: GameRuntimePlayerView | null } | { v: 1; t: "update"; channel: "feed"; serverId: string; action: string; data: unk… — ⚠ undocumented
|
|
582
|
-
- `WsVoiceParticipant` (type): type WsVoiceParticipant = { userId: string; streamId?: string; } — ⚠ undocumented
|
|
582
|
+
- `WsVoiceParticipant` (type): type WsVoiceParticipant = { userId: string; streamId?: string | undefined; } — ⚠ undocumented
|
|
583
583
|
- `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
|
|
584
584
|
- `announcePeerHost` (function): function announcePeerHost(host: PeerHost, signaling: PeerSignaling): () => void — ⚠ undocumented
|
|
585
585
|
- `broadcastChannelSignaling` (function): function broadcastChannelSignaling(room: string): PeerSignaling — ⚠ undocumented
|
|
@@ -607,14 +607,14 @@
|
|
|
607
607
|
|
|
608
608
|
## @jgengine/ws/commandMiddleware
|
|
609
609
|
|
|
610
|
-
- `CommandAuthorize` (type): type CommandAuthorize = (args: { userId: string; op: HostCommandOp; serverId?: string; command?: string; }) => boolean | Promise<boolean> — Per-command authorization hook: return `false` to reject. Defaults to allow-all when omitted.
|
|
610
|
+
- `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.
|
|
611
611
|
- `CommandCatalog` (type): type CommandCatalog = Record<string, CommandCatalogEntry> — Declared `runCommand` names and their input validators. When set, any `runCommand` name absent from this catalog is rejected as unknown.
|
|
612
612
|
- `CommandCatalogEntry` (type): type CommandCatalogEntry = { validate?: (input: unknown) => CommandRejection | null; } — A declared `runCommand` name's input validator, run before the command reaches the game host.
|
|
613
|
-
- `CommandGateArgs` (type): type CommandGateArgs = { connection: object; userId: string; op: HostCommandOp; atMs: number; serverId?: string; command?: string; input?: unknown; } — One op attempt to run through the middleware pipeline: which connection, which op, and (for `runCommand`) the command name/input.
|
|
613
|
+
- `CommandGateArgs` (type): type CommandGateArgs = { connection: object; userId: string; op: HostCommandOp; atMs: number; serverId?: string | undefined; command?: string; input?: unknown; } — One op attempt to run through the middleware pipeline: which connection, which op, and (for `runCommand`) the command name/input.
|
|
614
614
|
- `CommandGateDecision` (type): type CommandGateDecision = { allow: true } | { allow: false; reason: string } — The pipeline's verdict for one {@link CommandGateArgs}: allowed, or rejected with a client-facing reason.
|
|
615
615
|
- `CommandLimits` (type): type CommandLimits = Partial<Record<HostCommandOp, CommandRateLimit>> — Per-op rate limits. An op with no entry (or an undefined `limits`) is unlimited.
|
|
616
616
|
- `CommandMiddleware` (type): type CommandMiddleware = { check: (args: CommandGateArgs) => Promise<CommandGateDecision>; } — A composable rate-limit → validate → authorize pipeline the host router runs before dispatching pose/runCommand/join/browse/voice ops. Every stage defaults to a no-op, so an unconfigured router behaves exactly as before.
|
|
617
|
-
- `CommandMiddlewareOptions` (type): type CommandMiddlewareOptions = { limits?: CommandLimits; authorize?: CommandAuthorize; validate?: CommandCatalog; } — Config for {@link createCommandMiddleware}: the same `limits`/`authorize`/`validate` fields accepted by `HostRouterOptions`.
|
|
617
|
+
- `CommandMiddlewareOptions` (type): type CommandMiddlewareOptions = { limits?: CommandLimits; authorize?: CommandAuthorize | undefined; validate?: CommandCatalog | undefined; } — Config for {@link createCommandMiddleware}: the same `limits`/`authorize`/`validate` fields accepted by `HostRouterOptions`.
|
|
618
618
|
- `CommandRateLimit` (type): type CommandRateLimit = { count: number; perMs: number } — A sliding-window rate limit: at most `count` calls per `perMs` window.
|
|
619
619
|
- `CommandRateLimiter` (type): type CommandRateLimiter = { allow: (connection: object, op: HostCommandOp, atMs: number) => boolean; } — A composable per-connection/per-op sliding-window rate limiter.
|
|
620
620
|
- `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.
|
|
@@ -626,7 +626,7 @@
|
|
|
626
626
|
## @jgengine/ws/createWsBackend
|
|
627
627
|
|
|
628
628
|
- `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; webSocketFactory?: (url: string) => WebSocket; reconnectDelayMs?: number; maxReconnectDelayMs?: number; rpcTimeoutMs?: number; poseTuning?: PoseSyncTuning; now?: () => number;
|
|
629
|
+
- `WsBackendOptions` (type): type WsBackendOptions = { url?: string; pipe?: TransportPipeFactory; userId: string; token?: string | undefined; webSocketFactory?: (url: string) => WebSocket; reconnectDelayMs?: number; maxReconnectDelayMs?: number; rpcTimeoutMs?: number; poseTuning?: PoseSyncTuning; now?: () => number; setTimeoutF… — ⚠ undocumented
|
|
630
630
|
- `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
631
|
- `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
632
|
- `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
|
|
@@ -634,8 +634,8 @@
|
|
|
634
634
|
|
|
635
635
|
## @jgengine/ws/host
|
|
636
636
|
|
|
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;
|
|
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.
|
|
637
|
+
- `GameServerHost` (type): type GameServerHost = { joinServer: (args: { userId: string; gameId: string; serverId?: string | undefined; attributes?: SessionAttributes | undefined; code?: string | undefined; role?: SnapshotViewer["role"] | undefined; }) => Promise<JoinServerResult>; browseServers: (args: { gameId: string; filte… — A transport-agnostic authoritative game server host that manages sessions, ticking, and persistence.
|
|
638
|
+
- `GameServerHostOptions` (type): type GameServerHostOptions = { runtimes?: GameRuntime[] | undefined; persistence: HostPersistence; tickMs?: number | undefined; slotsPerServer?: number; now?: () => number; createServerId?: () => string; allowedFeedActions?: readonly string[]; } — Configuration for {@link createGameServerHost}, including persistence, tick rate, and game runtimes.
|
|
639
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.
|
|
640
640
|
- `OP_LEDGER_LIMIT` (const): const OP_LEDGER_LIMIT: 64 — Max recently-applied `runCommand` op IDs retained per (serverId, userId), oldest evicted first.
|
|
641
641
|
- `createGameServerHost` (function): function createGameServerHost(options: GameServerHostOptions): GameServerHost — Creates a `GameServerHost` that runs game servers over the given persistence and runtimes.
|
|
@@ -646,7 +646,7 @@
|
|
|
646
646
|
- `DEFAULT_GRACE_MS` (const): const DEFAULT_GRACE_MS: 15000 — Default reconnect grace period for hosted socket sessions.
|
|
647
647
|
- `DEFAULT_POSE_RULES` (const): const DEFAULT_POSE_RULES: PoseSyncRules — ⚠ undocumented
|
|
648
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
|
|
649
|
-
- `HostRouterAuthenticate` (type): type HostRouterAuthenticate = (args: { userId: string; token?: string; }) => Promise<string | null> | string | null — ⚠ undocumented
|
|
649
|
+
- `HostRouterAuthenticate` (type): type HostRouterAuthenticate = (args: { userId: string; token?: string | undefined; }) => Promise<string | null> | string | null — ⚠ undocumented
|
|
650
650
|
- `HostRouterConnection` (type): type HostRouterConnection = { handleRaw: (raw: unknown) => void; close: () => void; } — ⚠ undocumented
|
|
651
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
|
|
652
652
|
- `HostRouterTransport` (type): type HostRouterTransport = { send: (data: string) => void; close: () => void; } — ⚠ undocumented
|
|
@@ -697,22 +697,22 @@
|
|
|
697
697
|
- `WsBrowseResult` (type): type WsBrowseResult = SessionListing[] — ⚠ undocumented
|
|
698
698
|
- `WsChannel` (type): type WsChannel = "server" | "player" | "feed" | "presence" | "chat" | "voice" — ⚠ undocumented
|
|
699
699
|
- `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 } | { v: 1; t: "join"; id: number; gameId: string; serverId?: string; attributes?: SessionAttributes; code?: string
|
|
701
|
-
- `WsDecodeFailure` (type): type WsDecodeFailure = { reason: string; id?: 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 | undefined; code?: string | undefined; r… — ⚠ undocumented
|
|
701
|
+
- `WsDecodeFailure` (type): type WsDecodeFailure = { reason: string; id?: number | undefined; } — ⚠ undocumented
|
|
702
702
|
- `WsJoinByCodeResult` (type): type WsJoinByCodeResult = JoinServerResult | null — ⚠ undocumented
|
|
703
703
|
- `WsJoinResult` (type): type WsJoinResult = JoinServerResult — ⚠ undocumented
|
|
704
|
-
- `WsPose` (type): type WsPose = PlayerPose & { appearance?: WsAppearance } — ⚠ undocumented
|
|
705
|
-
- `WsPresenceRow` (type): type WsPresenceRow = PresencePoseRow & { appearance?: WsAppearance } — ⚠ undocumented
|
|
704
|
+
- `WsPose` (type): type WsPose = PlayerPose & { appearance?: WsAppearance | undefined } — ⚠ undocumented
|
|
705
|
+
- `WsPresenceRow` (type): type WsPresenceRow = PresencePoseRow & { appearance?: WsAppearance | undefined } — ⚠ undocumented
|
|
706
706
|
- `WsRunCommandResult` (type): type WsRunCommandResult = TransportRunCommandResult — ⚠ undocumented
|
|
707
707
|
- `WsServerMessage` (type): type WsServerMessage = | { v: 1; t: "pong"; id: number; at: number; serverAt: number } | { v: 1; t: "reply"; id: number; ok: true; result?: unknown } | { v: 1; t: "reply"; id: number; ok: false; reason: string } | WsUpdateMessage — ⚠ undocumented
|
|
708
708
|
- `WsUpdateMessage` (type): type WsUpdateMessage = | { v: 1; t: "update"; channel: "server"; serverId: string; data: GameRuntimeServerView | null } | { v: 1; t: "update"; channel: "player"; serverId: string; data: GameRuntimePlayerView | null } | { v: 1; t: "update"; channel: "feed"; serverId: string; action: string; data: unk… — ⚠ undocumented
|
|
709
|
-
- `WsVoiceParticipant` (type): type WsVoiceParticipant = { userId: string; streamId?: string; } — ⚠ undocumented
|
|
709
|
+
- `WsVoiceParticipant` (type): type WsVoiceParticipant = { userId: string; streamId?: string | undefined; } — ⚠ undocumented
|
|
710
710
|
|
|
711
711
|
## @jgengine/ws/readsHandler
|
|
712
712
|
|
|
713
713
|
- `ReadsAuthenticate` (type): type ReadsAuthenticate = ( request: Request, ) => Promise<string | null> | string | null — ⚠ undocumented
|
|
714
714
|
- `ReadsHandler` (type): type ReadsHandler = (request: Request) => Promise<Response> — ⚠ undocumented
|
|
715
|
-
- `ReadsHandlerOptions` (type): type ReadsHandlerOptions = { persistence: ReadsPersistence | (() => Promise<ReadsPersistence>); basePath?: string; listOpenServers?: (args: { gameId: string; limit?: number }) => Promise<ServerListing[]>; authenticate?: ReadsAuthenticate; allowPublicProfiles?: boolean; } — ⚠ undocumented
|
|
715
|
+
- `ReadsHandlerOptions` (type): type ReadsHandlerOptions = { persistence: ReadsPersistence | (() => Promise<ReadsPersistence>); basePath?: string; listOpenServers?: (args: { gameId: string; limit?: number | undefined }) => Promise<ServerListing[]>; authenticate?: ReadsAuthenticate; allowPublicProfiles?: boolean; } — ⚠ undocumented
|
|
716
716
|
- `ReadsPersistence` (type): type ReadsPersistence = Pick< HostPersistence, "listServers" | "loadProfile" | "getLeaderboardTop" | "getLeaderboardProfile" > — ⚠ undocumented
|
|
717
717
|
- `createReadsHandler` (function): function createReadsHandler(options: ReadsHandlerOptions): ReadsHandler — ⚠ undocumented
|
|
718
718
|
|