@jgengine/shell 0.15.0-next.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/dist/Shell3dPresentation.js +1 -1
- package/dist/audio/synthEngine.js +12 -2
- package/dist/environment/EnvironmentScene.js +1 -1
- package/dist/presentationResolve.d.ts +2 -0
- package/dist/presentationResolve.js +1 -0
- package/dist/render/modelLoad.d.ts +24 -0
- package/dist/render/modelLoad.js +46 -0
- package/dist/scene/CityRenderer.js +28 -54
- package/dist/structures/BuildingKitBatch.d.ts +13 -0
- package/dist/structures/BuildingKitBatch.js +78 -0
- package/dist/structures/GeneratedBuilding.d.ts +19 -3
- package/dist/structures/GeneratedBuilding.js +13 -81
- package/dist/structures/buildingKitFit.d.ts +59 -0
- package/dist/structures/buildingKitFit.js +188 -0
- package/dist/structures/buildingKitRegistry.d.ts +9 -0
- package/dist/structures/buildingKitRegistry.js +26 -0
- package/dist/weather/DustField.d.ts +41 -0
- package/dist/weather/DustField.js +129 -0
- package/dist/weather/WeatherLayer.d.ts +8 -2
- package/dist/weather/WeatherLayer.js +5 -2
- package/dist/weather/index.d.ts +1 -0
- package/dist/weather/index.js +1 -0
- package/dist/weather/weatherMath.d.ts +4 -0
- package/dist/weather/weatherMath.js +4 -0
- package/dist/world/WorldHud.d.ts +6 -2
- package/dist/world/WorldHud.js +4 -4
- package/dist/world/worldBarSamples.d.ts +15 -3
- package/dist/world/worldBarSamples.js +34 -3
- package/package.json +4 -4
- package/skills/jgengine/api.md +15 -3
- package/skills/jgengine/capabilities.md +12 -0
- package/skills/jgengine-ui/api.md +33 -12
- package/skills/jgengine-ui/capabilities.md +12 -0
- package/skills/jgengine-world/SKILL.md +5 -0
- package/skills/jgengine-world/api.md +40 -2
- package/skills/jgengine-world/capabilities.md +16 -0
- package/skills/jgengine-world/recipes/building-kit.md +78 -0
|
@@ -2,6 +2,10 @@ export declare const DEFAULT_RAIN_COUNT = 2000;
|
|
|
2
2
|
export declare const DEFAULT_SNOW_COUNT = 1500;
|
|
3
3
|
export declare const DEFAULT_RAIN_DENSITY = 0.45;
|
|
4
4
|
export declare const DEFAULT_SNOW_DENSITY = 0.5;
|
|
5
|
+
/** Mote budget a {@link DustField} allocates before `density` scales it down. */
|
|
6
|
+
export declare const DEFAULT_DUST_COUNT = 1800;
|
|
7
|
+
/** Share of {@link DEFAULT_DUST_COUNT} drawn at intensity 1 — dust reads thinner than rain or snow. */
|
|
8
|
+
export declare const DEFAULT_DUST_DENSITY = 0.4;
|
|
5
9
|
/** @internal */
|
|
6
10
|
export declare function clampWeatherRatio(value: number): number;
|
|
7
11
|
/** @internal */
|
|
@@ -2,6 +2,10 @@ export const DEFAULT_RAIN_COUNT = 2000;
|
|
|
2
2
|
export const DEFAULT_SNOW_COUNT = 1500;
|
|
3
3
|
export const DEFAULT_RAIN_DENSITY = 0.45;
|
|
4
4
|
export const DEFAULT_SNOW_DENSITY = 0.5;
|
|
5
|
+
/** Mote budget a {@link DustField} allocates before `density` scales it down. */
|
|
6
|
+
export const DEFAULT_DUST_COUNT = 1800;
|
|
7
|
+
/** Share of {@link DEFAULT_DUST_COUNT} drawn at intensity 1 — dust reads thinner than rain or snow. */
|
|
8
|
+
export const DEFAULT_DUST_DENSITY = 0.4;
|
|
5
9
|
/** @internal */
|
|
6
10
|
export function clampWeatherRatio(value) {
|
|
7
11
|
if (!Number.isFinite(value))
|
package/dist/world/WorldHud.d.ts
CHANGED
|
@@ -5,13 +5,15 @@ import { type NameplateSample } from "./worldBarSamples.js";
|
|
|
5
5
|
export type { NameplateSample, WorldBarSample } from "./worldBarSamples.js";
|
|
6
6
|
export { collectNameplateSamples, collectWorldBarSamples, paintWorldBarSamples } from "./worldBarSamples.js";
|
|
7
7
|
export { telegraphPulseOpacity } from "./telegraphPulse.js";
|
|
8
|
-
export declare function WorldEntityBars({ statId, height, roles, resolveRole, maxDistance, }: {
|
|
8
|
+
export declare function WorldEntityBars({ statId, height, roles, resolveRole, maxDistance, occlude, }: {
|
|
9
9
|
statId: string;
|
|
10
10
|
height?: number;
|
|
11
11
|
roles?: readonly CatalogEntityRole[];
|
|
12
12
|
resolveRole?: (entity: SceneEntity) => CatalogEntityRole | undefined;
|
|
13
13
|
/** Hide bars for entities farther than this from the player (world units). Default 60. */
|
|
14
14
|
maxDistance?: number;
|
|
15
|
+
/** Hide the bar when world geometry stands between the player and the entity. These bars are a screen-space overlay outside the depth buffer, so without this they read through walls. Default false, preserving the always-visible behaviour. */
|
|
16
|
+
occlude?: boolean;
|
|
15
17
|
}): import("react").JSX.Element;
|
|
16
18
|
/** Props for `WorldNameplates` — entity filter, refresh rate, and headless className/render hooks. */
|
|
17
19
|
export interface WorldNameplatesProps {
|
|
@@ -21,6 +23,8 @@ export interface WorldNameplatesProps {
|
|
|
21
23
|
resolveRole?: (entity: SceneEntity) => CatalogEntityRole | undefined;
|
|
22
24
|
/** Hide nameplates for entities farther than this from the player (world units). Default 40. */
|
|
23
25
|
maxDistance?: number;
|
|
26
|
+
/** Hide the plate when world geometry stands between the player and the entity. Default false. */
|
|
27
|
+
occlude?: boolean;
|
|
24
28
|
/** Minimum ms between position/health refreshes — trades smoothness for fewer re-renders at scale. Default 120. */
|
|
25
29
|
tickMs?: number;
|
|
26
30
|
/** Draw the built-in HP bar under the name. Set `false` for a name-only plate when the game already draws its own health bar (its own HUD, or `worldHealthBars`). Default true. */
|
|
@@ -41,7 +45,7 @@ export interface WorldNameplatesProps {
|
|
|
41
45
|
*
|
|
42
46
|
* @capability nameplates billboarded name + HP bar over nearby entities
|
|
43
47
|
*/
|
|
44
|
-
export declare function WorldNameplates({ statId, height, roles, resolveRole, maxDistance, tickMs, showHealth, className, nameplateClassName, nameClassName, barClassName, fillClassName, renderNameplate, }: WorldNameplatesProps): import("react").JSX.Element;
|
|
48
|
+
export declare function WorldNameplates({ statId, height, roles, resolveRole, maxDistance, occlude, tickMs, showHealth, className, nameplateClassName, nameClassName, barClassName, fillClassName, renderNameplate, }: WorldNameplatesProps): import("react").JSX.Element;
|
|
45
49
|
/** Props for {@link WorldObjectHighlights}. */
|
|
46
50
|
export interface WorldObjectHighlightsProps {
|
|
47
51
|
/** Ring tint. Default a construction-select amber. */
|
package/dist/world/WorldHud.js
CHANGED
|
@@ -16,7 +16,7 @@ export { telegraphPulseOpacity } from "./telegraphPulse.js";
|
|
|
16
16
|
function pinOverlayToViewport(_el, _camera, size) {
|
|
17
17
|
return [size.width / 2, size.height / 2];
|
|
18
18
|
}
|
|
19
|
-
export function WorldEntityBars({ statId, height = 2.2, roles, resolveRole, maxDistance = 60, }) {
|
|
19
|
+
export function WorldEntityBars({ statId, height = 2.2, roles, resolveRole, maxDistance = 60, occlude = false, }) {
|
|
20
20
|
const ctx = useGameContext();
|
|
21
21
|
const camera = useThree((state) => state.camera);
|
|
22
22
|
const size = useThree((state) => state.size);
|
|
@@ -38,7 +38,7 @@ export function WorldEntityBars({ statId, height = 2.2, roles, resolveRole, maxD
|
|
|
38
38
|
canvas.height = pixelH;
|
|
39
39
|
}
|
|
40
40
|
camera.updateMatrixWorld();
|
|
41
|
-
collectWorldBarSamples(ctx, statId, height, roles, resolveRole, camera, { width: cssW, height: cssH }, samplesRef.current, projectRef.current, maxDistance);
|
|
41
|
+
collectWorldBarSamples(ctx, statId, height, roles, resolveRole, camera, { width: cssW, height: cssH }, samplesRef.current, projectRef.current, maxDistance, occlude);
|
|
42
42
|
paintWorldBarSamples(canvas, samplesRef.current, dpr);
|
|
43
43
|
});
|
|
44
44
|
return (_jsx(Html, { fullscreen: true, calculatePosition: pinOverlayToViewport, zIndexRange: [20, 0], style: { pointerEvents: "none" }, children: _jsx("canvas", { ref: canvasRef, style: { width: "100%", height: "100%", display: "block", pointerEvents: "none" } }) }));
|
|
@@ -51,7 +51,7 @@ export function WorldEntityBars({ statId, height = 2.2, roles, resolveRole, maxD
|
|
|
51
51
|
*
|
|
52
52
|
* @capability nameplates billboarded name + HP bar over nearby entities
|
|
53
53
|
*/
|
|
54
|
-
export function WorldNameplates({ statId = "health", height = 2.3, roles, resolveRole, maxDistance = 40, tickMs = 120, showHealth = true, className, nameplateClassName, nameClassName, barClassName, fillClassName, renderNameplate, }) {
|
|
54
|
+
export function WorldNameplates({ statId = "health", height = 2.3, roles, resolveRole, maxDistance = 40, occlude = false, tickMs = 120, showHealth = true, className, nameplateClassName, nameClassName, barClassName, fillClassName, renderNameplate, }) {
|
|
55
55
|
const ctx = useGameContext();
|
|
56
56
|
const camera = useThree((state) => state.camera);
|
|
57
57
|
const size = useThree((state) => state.size);
|
|
@@ -65,7 +65,7 @@ export function WorldNameplates({ statId = "health", height = 2.3, roles, resolv
|
|
|
65
65
|
return;
|
|
66
66
|
lastTickRef.current = nowMs;
|
|
67
67
|
camera.updateMatrixWorld();
|
|
68
|
-
collectNameplateSamples(ctx, statId, height, roles, resolveRole, camera, { width: size.width, height: size.height }, samplesRef.current, projectRef.current, maxDistance);
|
|
68
|
+
collectNameplateSamples(ctx, statId, height, roles, resolveRole, camera, { width: size.width, height: size.height }, samplesRef.current, projectRef.current, maxDistance, occlude);
|
|
69
69
|
setSamples([...samplesRef.current]);
|
|
70
70
|
});
|
|
71
71
|
return (_jsx(Html, { fullscreen: true, calculatePosition: pinOverlayToViewport, zIndexRange: [19, 0], style: { pointerEvents: "none" }, children: _jsx("div", { className: className, "data-nameplates": true, children: samples.map((sample) => renderNameplate !== undefined ? (_jsx("div", { style: { position: "absolute", left: sample.x, top: sample.y }, children: renderNameplate(sample) }, sample.id)) : (_jsxs("div", { className: nameplateClassName, "data-nameplate": sample.id, style: {
|
|
@@ -16,14 +16,26 @@ export interface Projectable {
|
|
|
16
16
|
y: number;
|
|
17
17
|
z: number;
|
|
18
18
|
}
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* True when world geometry stands between the player's eye and `target`. World bars and nameplates
|
|
21
|
+
* are DOM/canvas overlays projected to screen space, so nothing in the depth buffer can hide them —
|
|
22
|
+
* without this test an enemy's health bar reads straight through the building it is standing behind.
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export declare function worldBarOccluded(ctx: GameContext, from: readonly [number, number, number], target: readonly [number, number, number], eyeHeight: number): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Projects every non-local, role/distance-filtered entity carrying `statId` into `into` as a
|
|
28
|
+
* `WorldBarSample`. Pure data step behind `WorldHealthBars` — call it directly to paint the bars
|
|
29
|
+
* with your own renderer.
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
20
32
|
export declare function collectWorldBarSamples(ctx: GameContext, statId: string, height: number, roles: readonly CatalogEntityRole[] | undefined, resolveRole: ((entity: SceneEntity) => CatalogEntityRole | undefined) | undefined, camera: {
|
|
21
33
|
matrixWorldInverse: unknown;
|
|
22
34
|
projectionMatrix: unknown;
|
|
23
35
|
}, viewport: {
|
|
24
36
|
width: number;
|
|
25
37
|
height: number;
|
|
26
|
-
}, into: WorldBarSample[], project: Projectable, maxDistance?: number): number;
|
|
38
|
+
}, into: WorldBarSample[], project: Projectable, maxDistance?: number, occlude?: boolean): number;
|
|
27
39
|
/** One entity's projected nameplate: screen `x`/`y`, display `name`, health `percent` (or `null` when statless), and world `distance` from the player. */
|
|
28
40
|
export interface NameplateSample {
|
|
29
41
|
id: string;
|
|
@@ -46,7 +58,7 @@ export declare function collectNameplateSamples(ctx: GameContext, statId: string
|
|
|
46
58
|
}, viewport: {
|
|
47
59
|
width: number;
|
|
48
60
|
height: number;
|
|
49
|
-
}, into: NameplateSample[], project: Projectable, maxDistance?: number): number;
|
|
61
|
+
}, into: NameplateSample[], project: Projectable, maxDistance?: number, occlude?: boolean): number;
|
|
50
62
|
/** @internal */
|
|
51
63
|
export declare function paintWorldBarSamples(canvas: {
|
|
52
64
|
width: number;
|
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
import { worldHealthBarAllowsRole } from "@jgengine/core/game/playableGame";
|
|
2
|
-
/**
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* True when world geometry stands between the player's eye and `target`. World bars and nameplates
|
|
4
|
+
* are DOM/canvas overlays projected to screen space, so nothing in the depth buffer can hide them —
|
|
5
|
+
* without this test an enemy's health bar reads straight through the building it is standing behind.
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export function worldBarOccluded(ctx, from, target, eyeHeight) {
|
|
9
|
+
const origin = [from[0], from[1] + eyeHeight, from[2]];
|
|
10
|
+
const dx = target[0] - origin[0];
|
|
11
|
+
const dy = target[1] + eyeHeight - origin[1];
|
|
12
|
+
const dz = target[2] - origin[2];
|
|
13
|
+
const length = Math.hypot(dx, dy, dz);
|
|
14
|
+
if (length < 0.001)
|
|
15
|
+
return false;
|
|
16
|
+
const hit = ctx.scene.object.raycast({
|
|
17
|
+
origin,
|
|
18
|
+
direction: [dx / length, dy / length, dz / length],
|
|
19
|
+
// Stop short of the entity so its own collider never counts as its own occluder.
|
|
20
|
+
maxDistance: length - 0.5,
|
|
21
|
+
});
|
|
22
|
+
return hit !== null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Projects every non-local, role/distance-filtered entity carrying `statId` into `into` as a
|
|
26
|
+
* `WorldBarSample`. Pure data step behind `WorldHealthBars` — call it directly to paint the bars
|
|
27
|
+
* with your own renderer.
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
export function collectWorldBarSamples(ctx, statId, height, roles, resolveRole, camera, viewport, into, project, maxDistance = 60, occlude = false) {
|
|
4
31
|
into.length = 0;
|
|
5
32
|
const playerId = ctx.player.userId;
|
|
6
33
|
const player = ctx.scene.entity.get(playerId);
|
|
@@ -14,6 +41,8 @@ export function collectWorldBarSamples(ctx, statId, height, roles, resolveRole,
|
|
|
14
41
|
maxDistance) {
|
|
15
42
|
continue;
|
|
16
43
|
}
|
|
44
|
+
if (occlude && player !== null && worldBarOccluded(ctx, player.position, entity.position, 1.5))
|
|
45
|
+
continue;
|
|
17
46
|
const stat = ctx.scene.entity.stats.get(entity.id, statId);
|
|
18
47
|
if (stat === null)
|
|
19
48
|
continue;
|
|
@@ -35,7 +64,7 @@ export function collectWorldBarSamples(ctx, statId, height, roles, resolveRole,
|
|
|
35
64
|
* own renderer by calling this directly instead of the component.
|
|
36
65
|
* @internal
|
|
37
66
|
*/
|
|
38
|
-
export function collectNameplateSamples(ctx, statId, height, roles, resolveRole, camera, viewport, into, project, maxDistance = 40) {
|
|
67
|
+
export function collectNameplateSamples(ctx, statId, height, roles, resolveRole, camera, viewport, into, project, maxDistance = 40, occlude = false) {
|
|
39
68
|
into.length = 0;
|
|
40
69
|
const playerId = ctx.player.userId;
|
|
41
70
|
const player = ctx.scene.entity.get(playerId);
|
|
@@ -49,6 +78,8 @@ export function collectNameplateSamples(ctx, statId, height, roles, resolveRole,
|
|
|
49
78
|
: Math.hypot(entity.position[0] - player.position[0], entity.position[2] - player.position[2]);
|
|
50
79
|
if (player !== null && distance > maxDistance)
|
|
51
80
|
continue;
|
|
81
|
+
if (occlude && player !== null && worldBarOccluded(ctx, player.position, entity.position, 1.5))
|
|
82
|
+
continue;
|
|
52
83
|
const stat = ctx.scene.entity.stats.get(entity.id, statId);
|
|
53
84
|
let percent = null;
|
|
54
85
|
if (stat !== null) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Game player shell for JGengine: React Three Fiber canvas, orbit camera, input tracking, HUD mounting, GameUiPreview, and a demo game. Consumers supply a GameRegistry.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"test": "bun test src"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@jgengine/core": "^0.
|
|
39
|
-
"@jgengine/react": "^0.
|
|
40
|
-
"@jgengine/ws": "^0.
|
|
38
|
+
"@jgengine/core": "^0.16.0",
|
|
39
|
+
"@jgengine/react": "^0.16.0",
|
|
40
|
+
"@jgengine/ws": "^0.16.0"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"@react-three/drei": "^10.0.0",
|
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.16.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
|
|
10
10
|
|
|
11
11
|
## @jgengine/core/authoring
|
|
12
12
|
|
|
@@ -228,7 +228,7 @@
|
|
|
228
228
|
|
|
229
229
|
- `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
|
|
230
230
|
- `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
|
|
231
|
-
- `VERSION` (const): const VERSION: "0.
|
|
231
|
+
- `VERSION` (const): const VERSION: "0.16.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
|
|
232
232
|
|
|
233
233
|
## @jgengine/core/runtime/adapter
|
|
234
234
|
|
|
@@ -264,6 +264,13 @@
|
|
|
264
264
|
- `CommandValidationError` (type): type CommandValidationError = { reason: string } — ⚠ undocumented
|
|
265
265
|
- `RunCommandResult` (type): type RunCommandResult = | { ok: true; snapshot: GameRuntimeSnapshot } | { ok: false; reason: string } — ⚠ undocumented
|
|
266
266
|
|
|
267
|
+
## @jgengine/core/runtime/context/objectSlots
|
|
268
|
+
|
|
269
|
+
- `ObjectSlotRejection` (type): type ObjectSlotRejection = | "unknown-object" | "no-slot-inventory" | "unknown-inventory" | "no-space" | "wrong-kind" | "slot-occupied" | "invalid-slot" | "insufficient" — Why an object slot mutation was refused — the instance, its catalog entry, the player inventory, or the declared `accepts`/capacity.
|
|
270
|
+
- `ObjectSlotResult` (type): type ObjectSlotResult = { status: "ok" } | { status: "rejected"; reason: ObjectSlotRejection } — Outcome of an object slot mutation: applied, or refused with a reason and no state change on either side.
|
|
271
|
+
- `ObjectSlotTransfer` (interface): interface ObjectSlotTransfer — Which player inventory a transfer moves between, and what it moves.
|
|
272
|
+
- `SceneObjectSlots` (interface): interface SceneObjectSlots — Per-instance container contents for placed objects whose catalog entry declares `slotInventory` — the engine side of "install a GPU into a rack", "put an item in a chest". Contents live on `SceneObject.slots`, so they replicate and save with the object rather than needing a game-owned table keyed by `instanceId`, and every mutation is validated against the declared `accepts` and stack limits.
|
|
273
|
+
|
|
267
274
|
## @jgengine/core/runtime/gameContext
|
|
268
275
|
|
|
269
276
|
- `CatalogEntityRole` (type): type CatalogEntityRole = "player" | "enemy" | "hostile" | "npc" | "vehicle" — ⚠ undocumented
|
|
@@ -402,6 +409,11 @@
|
|
|
402
409
|
- `MotionIntentBatch` (interface): interface MotionIntentBatch — ⚠ undocumented
|
|
403
410
|
- `MotionIntents` (interface): interface MotionIntents — Seam for game code to reach the motion the shell's FrameDriver otherwise owns privately (#162.4). Game code calls `impulse`, `pushHorizontal`, `setVerticalVelocity`, and/or `setY` from `onTick` or commands; the shell calls `takePending()` once per frame, before integrating gravity, to drain what accumulated. `setY` wins over physics for that frame; impulses add to the velocity the driver is about to integrate; a later `setVerticalVelocity` replaces that velocity outright. Horizontal pushes compose with the walk controller (#282.4): they add to its horizontal velocity and decay naturally as it re-blends toward input — knockback, dashes, explosion shoves without raw `setPose` offsets.
|
|
404
411
|
|
|
412
|
+
## @jgengine/core/runtime/objectRows
|
|
413
|
+
|
|
414
|
+
- `fromRuntimeObjectRow` (function): function fromRuntimeObjectRow(row: RuntimeObjectRow): SceneObject — Inverse of {@link toRuntimeObjectRow}: rebuild the live placed object a host persisted.
|
|
415
|
+
- `toRuntimeObjectRow` (function): function toRuntimeObjectRow(object: SceneObject): RuntimeObjectRow — Convert a live placed object into its persisted row — the bridge between `ctx.scene.object` and `GameRuntimeSnapshot.server.objects`/`chunks[].objects`. Per-instance `state` is carried as `flags`. Presentation overrides (`visual`, `animation`) are catalog- and authoring-resolved and deliberately stay off the persisted row — put anything a command must own in `state`.
|
|
416
|
+
|
|
405
417
|
## @jgengine/core/runtime/perContext
|
|
406
418
|
|
|
407
419
|
- `perContext` (function): function perContext<T>(init: (ctx: GameContext) => T): (ctx: GameContext) => T — Per-`GameContext` lazy state, keyed by context identity through a `WeakMap` so it is reclaimed when the context is — the seam a game uses instead of a module-global `Map`, which is process-global and bleeds across worlds when one host serves several `serverId`s (#632). `init` runs once per distinct context on first access; the same context always resolves to the same value. This mirrors the engine's built-in per-ctx pattern (`ctx.player.motion`, `stepPlayerMovement`).
|
|
@@ -441,7 +453,7 @@
|
|
|
441
453
|
- `RuntimeChunkRow` (type): type RuntimeChunkRow = { chunkKey: string; objects: RuntimeObjectRow[]; entities: RuntimeEntityRow[]; flags?: Record<string, unknown>; } — ⚠ undocumented
|
|
442
454
|
- `RuntimeEntityRow` (type): type RuntimeEntityRow = { instanceId: string; catalogId: string; position?: [number, number, number]; rotationY?: number; parentSpace?: string; group?: string; stats?: Record<string, { current: number; max: number; min?: number }>; targetInstanceId?: string | null; userId?: string; } — ⚠ undocumented
|
|
443
455
|
- `RuntimeInventorySlot` (type): type RuntimeInventorySlot = { item: string; count: number; slot?: number; } — ⚠ undocumented
|
|
444
|
-
- `RuntimeObjectRow` (type): type RuntimeObjectRow = { instanceId: string; catalogId: string; position: [number, number, number]; rotationY?: number; parentSpace?: string; flags?: Record<string, unknown>;
|
|
456
|
+
- `RuntimeObjectRow` (type): type RuntimeObjectRow = { instanceId: string; catalogId: string; position: [number, number, number]; rotationY?: number; parentSpace?: string; /** Opaque per-instance state; the persisted name for `SceneObject.state`. */ flags?: Record<string, unknown>; /** Container contents for a catalog `slotInve… — The persisted form of a placed `SceneObject`: identity, placement, per-instance state, and slot contents. Convert with `toRuntimeObjectRow`/`fromRuntimeObjectRow` (`runtime/objectRows`).
|
|
445
457
|
- `RuntimePlayerRow` (type): type RuntimePlayerRow = { userId: string; inventories: Record<string, RuntimeInventorySlot[]>; economy: Record<string, number>; unlocks: string[]; quests?: unknown; social?: unknown; leaderboard?: Record<string, number>; session?: Record<string, unknown>; } — ⚠ undocumented
|
|
446
458
|
- `RuntimeProfileRow` (type): type RuntimeProfileRow = { userId: string; gameId: string; player: RuntimePlayerRow; updatedAt: number; } — ⚠ undocumented
|
|
447
459
|
- `RuntimeServerRow` (type): type RuntimeServerRow = { entities: RuntimeEntityRow[]; objects: RuntimeObjectRow[]; session: Record<string, unknown>; feeds?: Record<string, unknown[]>; } — ⚠ undocumented
|
|
@@ -100,6 +100,18 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
100
100
|
|
|
101
101
|
- `ws` (function) · `import { ws } from "@jgengine/core/runtime/adapter"`
|
|
102
102
|
|
|
103
|
+
## object-slot-inventory — placed objects hold validated per-instance container contents that replicate and save with the placement
|
|
104
|
+
|
|
105
|
+
- `SceneObjectSlots` (interface) · `import { SceneObjectSlots } from "@jgengine/core/runtime/context/objectSlots"`
|
|
106
|
+
|
|
107
|
+
## placed-object-rehydrate — rebuild live placed objects, with their state and slot contents, from persisted snapshot rows
|
|
108
|
+
|
|
109
|
+
- `fromRuntimeObjectRow` (function) · `import { fromRuntimeObjectRow } from "@jgengine/core/runtime/objectRows"`
|
|
110
|
+
|
|
111
|
+
## placed-object-rows — a placed object's per-instance state and container contents convert to and from the persisted snapshot row a host stores
|
|
112
|
+
|
|
113
|
+
- `toRuntimeObjectRow` (function) · `import { toRuntimeObjectRow } from "@jgengine/core/runtime/objectRows"`
|
|
114
|
+
|
|
103
115
|
## render-fallback-diagnostics — Enable/disable the dev-only placeholder-fallback probe.
|
|
104
116
|
|
|
105
117
|
- `armFallbackSeams` (function) · `import { armFallbackSeams } from "@jgengine/core/devtools/fallbackSeams"`
|
|
@@ -2190,6 +2190,11 @@
|
|
|
2190
2190
|
- `measureLocalCollisionTriangles` (function): function measureLocalCollisionTriangles(root: THREE.Object3D, transform?: { scale: number; offset: readonly [number, number, number] }): MeasuredCollisionTriangles | null — Collect the triangle soup of the meshes under `root` in `root`'s own frame — the mesh-accurate counterpart to `measureLocalBounds`, with the same visibility/sprite/{@link MEASURE_EXCLUDE_KEY} exclusions (skinned meshes contribute their bind pose). An optional uniform `scale` + `offset` maps the result into entity-local space when the caller mounts the root scaled/translated (the model-primitive path). Returns `null` when nothing is measurable or the soup exceeds the triangle budget — callers keep their box fallback.
|
|
2191
2191
|
- `reportMeasuredCollisionMesh` (function): function reportMeasuredCollisionMesh(ctx: GameContext, target: "entity" | "object", key: string, triangles: MeasuredCollisionTriangles): void — Forward measured triangles into the context (entity kind or object catalog id), deduping repeat reports of the same soup so remounts and multi-instance kinds don't rebuild the BVH.
|
|
2192
2192
|
|
|
2193
|
+
## @jgengine/shell/render/modelLoad
|
|
2194
|
+
|
|
2195
|
+
- `modelLoadFallbacks` (function): function modelLoadFallbacks(): readonly { url: string; message: string }[] — Every model the shared loader diagnosed as broken and replaced with a magenta placeholder, in load order.
|
|
2196
|
+
- `modelLoadIdleMs` (function): function modelLoadIdleMs(): number — How long the shared GLB loader has been idle, in ms — `0` while any model is still in flight. A capture host reads this to wait for streaming to finish instead of guessing a settle delay: models that pop in after the shot are why an establishing capture used to come back half-empty until someone hand-tuned `--settle`.
|
|
2197
|
+
|
|
2193
2198
|
## @jgengine/shell/render/modelRender
|
|
2194
2199
|
|
|
2195
2200
|
- `MaterialCache` (interface): interface MaterialCache — ⚠ undocumented
|
|
@@ -2323,10 +2328,10 @@
|
|
|
2323
2328
|
|
|
2324
2329
|
- `BuildingBlock` (function): function BuildingBlock({ part, palette }: BuildingBlockProps): React.JSX.Element — ⚠ undocumented
|
|
2325
2330
|
- `BuildingBlockProps` (interface): interface BuildingBlockProps — ⚠ undocumented
|
|
2326
|
-
- `GeneratedBuilding` (function): function GeneratedBuilding({ building, palette, kit, visibleKinds }: GeneratedBuildingProps): React.JSX.Element — ⚠ undocumented
|
|
2331
|
+
- `GeneratedBuilding` (function): function GeneratedBuilding({ building, palette, partRenderer, kit, resolveModelUrl, visibleKinds, }: GeneratedBuildingProps): React.JSX.Element — ⚠ undocumented
|
|
2327
2332
|
- `GeneratedBuildingProps` (interface): interface GeneratedBuildingProps — ⚠ undocumented
|
|
2328
2333
|
- `InstancedBuildingPlacement` (interface): interface InstancedBuildingPlacement — ⚠ undocumented
|
|
2329
|
-
- `InstancedBuildings` (function): function InstancedBuildings({ buildings, palette, visibleKinds }: InstancedBuildingsProps): React.JSX.Element | null — ⚠ undocumented
|
|
2334
|
+
- `InstancedBuildings` (function): function InstancedBuildings({ buildings, palette, kit, resolveModelUrl, visibleKinds, }: InstancedBuildingsProps): React.JSX.Element | null — ⚠ undocumented
|
|
2330
2335
|
- `InstancedBuildingsProps` (interface): interface InstancedBuildingsProps — ⚠ undocumented
|
|
2331
2336
|
- `PlacementGhost` (function): function PlacementGhost({ preview, height = 1, validColor = "#34d399", invalidColor = "#f87171", }: PlacementGhostProps): React.JSX.Element | null — Cursor-following build ghost: valid/invalid tint from a placement controller preview.
|
|
2332
2337
|
- `PlacementGhostProps` (interface): interface PlacementGhostProps — ⚠ undocumented
|
|
@@ -2340,10 +2345,10 @@
|
|
|
2340
2345
|
|
|
2341
2346
|
- `BuildingBlock` (function): function BuildingBlock({ part, palette }: BuildingBlockProps): React.JSX.Element — ⚠ undocumented
|
|
2342
2347
|
- `BuildingBlockProps` (interface): interface BuildingBlockProps — ⚠ undocumented
|
|
2343
|
-
- `GeneratedBuilding` (function): function GeneratedBuilding({ building, palette, kit, visibleKinds }: GeneratedBuildingProps): React.JSX.Element — ⚠ undocumented
|
|
2348
|
+
- `GeneratedBuilding` (function): function GeneratedBuilding({ building, palette, partRenderer, kit, resolveModelUrl, visibleKinds, }: GeneratedBuildingProps): React.JSX.Element — ⚠ undocumented
|
|
2344
2349
|
- `GeneratedBuildingProps` (interface): interface GeneratedBuildingProps — ⚠ undocumented
|
|
2345
2350
|
- `InstancedBuildingPlacement` (interface): interface InstancedBuildingPlacement — ⚠ undocumented
|
|
2346
|
-
- `InstancedBuildings` (function): function InstancedBuildings({ buildings, palette, visibleKinds }: InstancedBuildingsProps): React.JSX.Element | null — ⚠ undocumented
|
|
2351
|
+
- `InstancedBuildings` (function): function InstancedBuildings({ buildings, palette, kit, resolveModelUrl, visibleKinds, }: InstancedBuildingsProps): React.JSX.Element | null — ⚠ undocumented
|
|
2347
2352
|
- `InstancedBuildingsProps` (interface): interface InstancedBuildingsProps — ⚠ undocumented
|
|
2348
2353
|
- `PlacementGhost` (function): function PlacementGhost({ preview, height = 1, validColor = "#34d399", invalidColor = "#f87171", }: PlacementGhostProps): React.JSX.Element | null — Cursor-following build ghost: valid/invalid tint from a placement controller preview.
|
|
2349
2354
|
- `PlacementGhostProps` (interface): interface PlacementGhostProps — ⚠ undocumented
|
|
@@ -2362,11 +2367,11 @@
|
|
|
2362
2367
|
- `BuildingMaterialPalette` (interface): interface BuildingMaterialPalette — ⚠ undocumented
|
|
2363
2368
|
- `BuildingPartKind` (type): type BuildingPartKind = | "wall" | "window" | "awning" | "airConditioner" | "clothesline" | "storefront" | "shutter" | "storeSign" | "roof" | "roofProp" | "guardrail" | "corner" — ⚠ undocumented
|
|
2364
2369
|
- `BuildingPartPlacement` (interface): interface BuildingPartPlacement — ⚠ undocumented
|
|
2365
|
-
- `GeneratedBuilding` (function): function GeneratedBuilding({ building, palette, kit, visibleKinds }: GeneratedBuildingProps): React.JSX.Element — ⚠ undocumented
|
|
2370
|
+
- `GeneratedBuilding` (function): function GeneratedBuilding({ building, palette, partRenderer, kit, resolveModelUrl, visibleKinds, }: GeneratedBuildingProps): React.JSX.Element — ⚠ undocumented
|
|
2366
2371
|
- `GeneratedBuildingData` (interface): interface GeneratedBuildingData — ⚠ undocumented
|
|
2367
2372
|
- `GeneratedBuildingProps` (interface): interface GeneratedBuildingProps — ⚠ undocumented
|
|
2368
2373
|
- `InstancedBuildingPlacement` (interface): interface InstancedBuildingPlacement — ⚠ undocumented
|
|
2369
|
-
- `InstancedBuildings` (function): function InstancedBuildings({ buildings, palette, visibleKinds }: InstancedBuildingsProps): React.JSX.Element | null — ⚠ undocumented
|
|
2374
|
+
- `InstancedBuildings` (function): function InstancedBuildings({ buildings, palette, kit, resolveModelUrl, visibleKinds, }: InstancedBuildingsProps): React.JSX.Element | null — ⚠ undocumented
|
|
2370
2375
|
- `InstancedBuildingsProps` (interface): interface InstancedBuildingsProps — ⚠ undocumented
|
|
2371
2376
|
|
|
2372
2377
|
## @jgengine/shell/structures/PlacementGhost
|
|
@@ -2384,6 +2389,13 @@
|
|
|
2384
2389
|
- `TransformGizmoSnap` (type): type TransformGizmoSnap = "grid" | "free" | "ground" — Snap policy: grid quantize, free drag, or ground-height sample on release.
|
|
2385
2390
|
- `TransformGizmoSpace` (type): type TransformGizmoSpace = "world" | "local" — Gizmo handle orientation: world axes, or the target's local (yaw-rotated) axes.
|
|
2386
2391
|
|
|
2392
|
+
## @jgengine/shell/structures/buildingKitRegistry
|
|
2393
|
+
|
|
2394
|
+
- `clearBuildingKits` (function): function clearBuildingKits(): void — Drops every registration. For tests and hot-reload teardown.
|
|
2395
|
+
- `getBuildingKit` (function): function getBuildingKit(id: string | undefined): BuildingKit | undefined — The kit registered under `id`, or `undefined` for an unset or unknown name (renders blocks).
|
|
2396
|
+
- `listBuildingKits` (function): function listBuildingKits(): string[] — Every registered kit id — the editor's kit picker and diagnostics read this.
|
|
2397
|
+
- `registerBuildingKit` (function): function registerBuildingKit(kit: BuildingKit): void — Registers a kit under its own `id`, replacing any kit already registered under that name.
|
|
2398
|
+
|
|
2387
2399
|
## @jgengine/shell/terrain
|
|
2388
2400
|
|
|
2389
2401
|
- `CarvedTerrain` (function): function CarvedTerrain({ field, size, segments, center, colors, heightRange, paletteAt, roughness = 0.95, metalness = 0, surfaceMaterial, receiveShadow = true, epoch = 0, ...meshProps }: CarvedTerrainProps): React.JSX.Element — Renders a `TerrainField` as a deformed ground mesh — the crater/mound view for destructible terrain. Because the geometry samples `field.sampleHeight`, a `CarvableField.carve(...)` shows as a real bowl once `epoch` changes. Pair with `InstancedBodies` to see debris resting in the crater it blasted.
|
|
@@ -2686,6 +2698,8 @@
|
|
|
2686
2698
|
|
|
2687
2699
|
## @jgengine/shell/weather
|
|
2688
2700
|
|
|
2701
|
+
- `DustField` (function): function DustField({ count = DEFAULT_DUST_COUNT, density = DEFAULT_DUST_DENSITY, budget, volume = DEFAULT_VOLUME, wind, origin = DEFAULT_ORIGIN, followCamera = true, speed = 2.4, size = 0.16, sway = 0.5, opacity = 0.3, color = DEFAULT_DUST_COLOR, groundBias = 0.65, timeScale, seed = 51407, renderOrd… — Renders wind-borne particulate as camera-facing motes. Reach for it whenever the air itself should read as weather — a desert gale, ash off a volcano, pollen in a meadow, grain dust in a silo. Compose it through {@link WeatherLayer}'s `"dust"` mode (or `dustAlways` alongside rain and snow) rather than mounting it directly, so it shares one wind and time source with the other layers.
|
|
2702
|
+
- `DustFieldProps` (interface): interface DustFieldProps — Wind-borne particulate: dust, sand, ash, pollen, spores. Unlike rain and snow this is not falling — motion is dominated by horizontal wind with a slow vertical bob, and density thins with height so the air near the ground carries the most grit. Any arid, volcanic, or blown-out world needs it.
|
|
2689
2703
|
- `FireSpreadLayer` (function): function FireSpreadLayer({ grid, cellSize, origin = [0, 0], heightAt, flameHeight = 1.6, burningColor = "#ff6a1a", emberColor = "#4a1206", }: FireSpreadLayerProps): React.JSX.Element — ⚠ undocumented
|
|
2690
2704
|
- `FireSpreadLayerProps` (interface): interface FireSpreadLayerProps — ⚠ undocumented
|
|
2691
2705
|
- `LightningStrike` (function): function LightningStrike({ origin, target, strikeKey = 0, seed = 451, visible = true, duration = 0.18, color = DEFAULT_COLOR, glow = 2.4, branches = 5, jaggedness = 0.08, impactLight = 26, renderOrder = 20, }: LightningStrikeProps): React.JSX.Element — ⚠ undocumented
|
|
@@ -2694,13 +2708,18 @@
|
|
|
2694
2708
|
- `RainFieldProps` (interface): interface RainFieldProps — ⚠ undocumented
|
|
2695
2709
|
- `SnowField` (function): function SnowField({ count = DEFAULT_SNOW_COUNT, density = DEFAULT_SNOW_DENSITY, budget, volume = DEFAULT_VOLUME, wind, origin = DEFAULT_ORIGIN, followCamera = true, speed = 3.2, size = 0.11, sway = 0.62, opacity = 0.86, color = DEFAULT_SNOW_COLOR, timeScale, seed = 72931, renderOrder = 11, frustumC… — ⚠ undocumented
|
|
2696
2710
|
- `SnowFieldProps` (interface): interface SnowFieldProps — ⚠ undocumented
|
|
2697
|
-
- `WeatherLayer` (function): function WeatherLayer({ mode = "clear", intensity = 1, wind, lightning, timeScale, rain, snow, enabled = true, children, }: WeatherLayerProps): React.JSX.Element | null — ⚠ undocumented
|
|
2698
|
-
- `WeatherLayerMode` (type): type WeatherLayerMode = "clear" | "rain" | "snow" | "mixed" —
|
|
2711
|
+
- `WeatherLayer` (function): function WeatherLayer({ mode = "clear", intensity = 1, wind, lightning, timeScale, rain, snow, dust, dustAlways = false, enabled = true, children, }: WeatherLayerProps): React.JSX.Element | null — ⚠ undocumented
|
|
2712
|
+
- `WeatherLayerMode` (type): type WeatherLayerMode = "clear" | "rain" | "snow" | "mixed" | "dust" — `"mixed"` runs rain and snow together; `"dust"` is airborne particulate and composes with either via `dust`.
|
|
2699
2713
|
- `WeatherLayerProps` (interface): interface WeatherLayerProps — ⚠ undocumented
|
|
2700
2714
|
- `WeatherUniformOptions` (interface): interface WeatherUniformOptions — ⚠ undocumented
|
|
2701
2715
|
- `WeatherUniformSet` (interface): interface WeatherUniformSet — ⚠ undocumented
|
|
2702
2716
|
- `WeatherVector` (type): type WeatherVector = readonly [number, number, number] — ⚠ undocumented
|
|
2703
2717
|
|
|
2718
|
+
## @jgengine/shell/weather/DustField
|
|
2719
|
+
|
|
2720
|
+
- `DustField` (function): function DustField({ count = DEFAULT_DUST_COUNT, density = DEFAULT_DUST_DENSITY, budget, volume = DEFAULT_VOLUME, wind, origin = DEFAULT_ORIGIN, followCamera = true, speed = 2.4, size = 0.16, sway = 0.5, opacity = 0.3, color = DEFAULT_DUST_COLOR, groundBias = 0.65, timeScale, seed = 51407, renderOrd… — Renders wind-borne particulate as camera-facing motes. Reach for it whenever the air itself should read as weather — a desert gale, ash off a volcano, pollen in a meadow, grain dust in a silo. Compose it through {@link WeatherLayer}'s `"dust"` mode (or `dustAlways` alongside rain and snow) rather than mounting it directly, so it shares one wind and time source with the other layers.
|
|
2721
|
+
- `DustFieldProps` (interface): interface DustFieldProps — Wind-borne particulate: dust, sand, ash, pollen, spores. Unlike rain and snow this is not falling — motion is dominated by horizontal wind with a slow vertical bob, and density thins with height so the air near the ground carries the most grit. Any arid, volcanic, or blown-out world needs it.
|
|
2722
|
+
|
|
2704
2723
|
## @jgengine/shell/weather/FireSpreadLayer
|
|
2705
2724
|
|
|
2706
2725
|
- `FireSpreadLayer` (function): function FireSpreadLayer({ grid, cellSize, origin = [0, 0], heightAt, flameHeight = 1.6, burningColor = "#ff6a1a", emberColor = "#4a1206", }: FireSpreadLayerProps): React.JSX.Element — ⚠ undocumented
|
|
@@ -2723,12 +2742,14 @@
|
|
|
2723
2742
|
|
|
2724
2743
|
## @jgengine/shell/weather/WeatherLayer
|
|
2725
2744
|
|
|
2726
|
-
- `WeatherLayer` (function): function WeatherLayer({ mode = "clear", intensity = 1, wind, lightning, timeScale, rain, snow, enabled = true, children, }: WeatherLayerProps): React.JSX.Element | null — ⚠ undocumented
|
|
2727
|
-
- `WeatherLayerMode` (type): type WeatherLayerMode = "clear" | "rain" | "snow" | "mixed" —
|
|
2745
|
+
- `WeatherLayer` (function): function WeatherLayer({ mode = "clear", intensity = 1, wind, lightning, timeScale, rain, snow, dust, dustAlways = false, enabled = true, children, }: WeatherLayerProps): React.JSX.Element | null — ⚠ undocumented
|
|
2746
|
+
- `WeatherLayerMode` (type): type WeatherLayerMode = "clear" | "rain" | "snow" | "mixed" | "dust" — `"mixed"` runs rain and snow together; `"dust"` is airborne particulate and composes with either via `dust`.
|
|
2728
2747
|
- `WeatherLayerProps` (interface): interface WeatherLayerProps — ⚠ undocumented
|
|
2729
2748
|
|
|
2730
2749
|
## @jgengine/shell/weather/weatherMath
|
|
2731
2750
|
|
|
2751
|
+
- `DEFAULT_DUST_COUNT` (const): const DEFAULT_DUST_COUNT: 1800 — Mote budget a {@link DustField} allocates before `density` scales it down.
|
|
2752
|
+
- `DEFAULT_DUST_DENSITY` (const): const DEFAULT_DUST_DENSITY: 0.4 — Share of {@link DEFAULT_DUST_COUNT} drawn at intensity 1 — dust reads thinner than rain or snow.
|
|
2732
2753
|
- `DEFAULT_RAIN_COUNT` (const): const DEFAULT_RAIN_COUNT: 2000 — ⚠ undocumented
|
|
2733
2754
|
- `DEFAULT_RAIN_DENSITY` (const): const DEFAULT_RAIN_DENSITY: 0.45 — ⚠ undocumented
|
|
2734
2755
|
- `DEFAULT_SNOW_COUNT` (const): const DEFAULT_SNOW_COUNT: 1500 — ⚠ undocumented
|
|
@@ -2781,9 +2802,9 @@
|
|
|
2781
2802
|
- `ProjectileTracers` (function): function ProjectileTracers({ lifeMs = 130 }: { lifeMs?: number }): React.JSX.Element — ⚠ undocumented
|
|
2782
2803
|
- `Reticle` (function): function Reticle({ className }: { className?: string }): React.JSX.Element — ⚠ undocumented
|
|
2783
2804
|
- `WorldBarSample` (interface): interface WorldBarSample — ⚠ undocumented
|
|
2784
|
-
- `WorldEntityBars` (function): function WorldEntityBars({ statId, height = 2.2, roles, resolveRole, maxDistance = 60, }: { statId: string; height?: number; roles?: readonly CatalogEntityRole[]; resolveRole?: (entity: SceneEntity) => CatalogEntityRole | undefined; /** Hide bars for entities farther than this from
|
|
2805
|
+
- `WorldEntityBars` (function): function WorldEntityBars({ statId, height = 2.2, roles, resolveRole, maxDistance = 60, occlude = false, }: { statId: string; height?: number; roles?: readonly CatalogEntityRole[]; resolveRole?: (entity: SceneEntity) => CatalogEntityRole | undefined; /** Hide bars for entities farther than this from … — ⚠ undocumented
|
|
2785
2806
|
- `WorldFloatText` (function): function WorldFloatText({ height = 1.9, lifeMs = 950 }: { height?: number; lifeMs?: number }): React.JSX.Element — ⚠ undocumented
|
|
2786
|
-
- `WorldNameplates` (function): function WorldNameplates({ statId = "health", height = 2.3, roles, resolveRole, maxDistance = 40, tickMs = 120, showHealth = true, className, nameplateClassName, nameClassName, barClassName, fillClassName, renderNameplate, }: WorldNameplatesProps): React.JSX.Element — Billboarded name + 78×6px HP bar over every nearby non-local entity that passes `roles`/`maxDistance` — headless (className/data-* slots on every part, `renderNameplate` for a full swap), turned on declaratively via `defineGame({ nameplates })` rather than mounted by hand.
|
|
2807
|
+
- `WorldNameplates` (function): function WorldNameplates({ statId = "health", height = 2.3, roles, resolveRole, maxDistance = 40, occlude = false, tickMs = 120, showHealth = true, className, nameplateClassName, nameClassName, barClassName, fillClassName, renderNameplate, }: WorldNameplatesProps): React.JSX.Element — Billboarded name + 78×6px HP bar over every nearby non-local entity that passes `roles`/`maxDistance` — headless (className/data-* slots on every part, `renderNameplate` for a full swap), turned on declaratively via `defineGame({ nameplates })` rather than mounted by hand.
|
|
2787
2808
|
- `WorldNameplatesProps` (interface): interface WorldNameplatesProps — Props for `WorldNameplates` — entity filter, refresh rate, and headless className/render hooks.
|
|
2788
2809
|
- `WorldObjectHighlights` (function): function WorldObjectHighlights({ color = "#facc15", radius, y = 0.05 }: WorldObjectHighlightsProps): React.JSX.Element — Ground ring over every `ctx.scene.object.selection`-ed placed object — the object-layer counterpart to `WorldEntityBars`/`WorldNameplates`. Mount it once in the game's scene (headless: no defaults are imposed beyond a visible ring) instead of hand-rolling a selection highlight through `WorldOverlay` against external state.
|
|
2789
2810
|
- `WorldObjectHighlightsProps` (interface): interface WorldObjectHighlightsProps — Props for {@link WorldObjectHighlights}.
|
|
@@ -342,6 +342,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
342
342
|
|
|
343
343
|
- `createModalStack` (function) · `import { createModalStack } from "@jgengine/core/ui"`
|
|
344
344
|
|
|
345
|
+
## model-fallbacks — models the loader replaced with a placeholder, for capture and smoke assertions
|
|
346
|
+
|
|
347
|
+
- `modelLoadFallbacks` (function) · `import { modelLoadFallbacks } from "@jgengine/shell/render/modelLoad"`
|
|
348
|
+
|
|
349
|
+
## model-load-idle — how long the shared GLB loader has been idle, for capture settle waits
|
|
350
|
+
|
|
351
|
+
- `modelLoadIdleMs` (function) · `import { modelLoadIdleMs } from "@jgengine/shell/render/modelLoad"`
|
|
352
|
+
|
|
345
353
|
## nameplates — billboarded name + HP bar over nearby entities
|
|
346
354
|
|
|
347
355
|
- `WorldNameplates` (function) · `import { WorldNameplates } from "@jgengine/shell/world/WorldHud"`
|
|
@@ -627,6 +635,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
627
635
|
|
|
628
636
|
- `WaypointMarkers` (function) · `import { WaypointMarkers } from "@jgengine/react"`
|
|
629
637
|
|
|
638
|
+
## weather-dust — airborne particulate that turns still air into visible wind
|
|
639
|
+
|
|
640
|
+
- `DustField` (function) · `import { DustField } from "@jgengine/shell/weather"`
|
|
641
|
+
|
|
630
642
|
## window — standalone draggable, closable window primitive over HudFrame — no manager required
|
|
631
643
|
|
|
632
644
|
- `Window` (function) · `import { Window } from "@jgengine/react"`
|
|
@@ -14,6 +14,7 @@ Search [capabilities.md](capabilities.md) by intent before designing a primitive
|
|
|
14
14
|
For a *connected* walkthrough that wires several primitives into a running loop, read the goal-oriented recipes under [recipes/](recipes/) — organized by composition seam (what connects to what), never by genre. Start there before reading a game's source to learn how pieces fit; a hybrid game is just a different composition of the same primitives.
|
|
15
15
|
|
|
16
16
|
- [recipes/commanding-entities.md](recipes/commanding-entities.md) — select → order → tick → resolve: direct command over a group of entities (the command loop behind squad control, base-defense, herding, autobattle, or a mutating game's real-time phase).
|
|
17
|
+
- [recipes/building-kit.md](recipes/building-kit.md) — bind the facade generator's part slots to real models: one massing, swappable art per style, blocks where the kit is silent.
|
|
17
18
|
|
|
18
19
|
## Canonical workflows
|
|
19
20
|
|
|
@@ -32,6 +33,10 @@ A world is the place you play in: substrate + laws, via `world()` from `@jgengin
|
|
|
32
33
|
|
|
33
34
|
Choose input intent, controller/motor, collision/navigation, and camera as separate seams. Commands express game intent; world systems resolve motion. Interaction targets come from bounded spatial/sensor queries, not full-world scans.
|
|
34
35
|
|
|
36
|
+
### Stateful placed objects
|
|
37
|
+
|
|
38
|
+
Per-instance state belongs on the placement, not in a game-owned map keyed by `instanceId`. Set `SceneObject.state` (opaque) through `ctx.scene.object.setState`/`patchState`; declare `slotInventory` on the catalog entry and mutate contents through `ctx.scene.object.slots`, which enforces the declared capacity and `accepts`. Both replicate and save with the object through the always-on `objects` snapshot module, and `toRuntimeObjectRow`/`fromRuntimeObjectRow` (`@jgengine/core/multiplayer`) convert to and from the host's persisted rows.
|
|
39
|
+
|
|
35
40
|
### AI and navigation
|
|
36
41
|
|
|
37
42
|
Compose perception, selection, planning/behavior, movement, and lifecycle independently. Inject randomness and scheduling. Use spatial indexes, interest tiers, or bounded candidate sets for scale.
|
|
@@ -223,6 +223,25 @@
|
|
|
223
223
|
- `notesInWindow` (function): function notesInWindow(theme: MusicTheme, anchorSec: number, fromSec: number, toSec: number): ScheduledNote[] — Pure lookahead scheduler: every note occurrence of `theme` whose onset falls in the half-open window `(fromSec, toSec]`, given the theme's loop-zero at `anchorSec`. Handles any number of loop wraps, so a director calls it once per tick with a non-overlapping window and never double-schedules a note.
|
|
224
224
|
- `themeLoopSeconds` (function): function themeLoopSeconds(theme: MusicTheme): number — Loop length of a theme in seconds.
|
|
225
225
|
|
|
226
|
+
## @jgengine/core/audio/musicState
|
|
227
|
+
|
|
228
|
+
- `MusicStateConfig` (interface): interface MusicStateConfig — The tier ladder plus the release margin that keeps a hovering intensity from flapping between tiers.
|
|
229
|
+
- `MusicStateInput` (interface): interface MusicStateInput — The current intensity signal and clock reading for one resolve.
|
|
230
|
+
- `MusicStateResult` (interface): interface MusicStateResult — Next state, the theme that should be playing, and whether this advance is the one that changed it.
|
|
231
|
+
- `MusicStateSnapshot` (interface): interface MusicStateSnapshot — Which tier is playing and when it was entered, on the caller's clock. Serialisable.
|
|
232
|
+
- `MusicTier` (interface): interface MusicTier — Hysteretic music-state selector: turns a continuous intensity signal (nearby threat, alert count, chase proximity) into a stable theme id. The engine can already crossfade a soundtrack; this decides *when*, without the flapping a bare threshold produces when one enemy walks in and out of range.
|
|
233
|
+
- `createMusicState` (function): function createMusicState(): MusicStateSnapshot — Fresh state with no tier entered, so the first resolve always settles and reports `changed`.
|
|
234
|
+
- `resolveMusicState` (function): function resolveMusicState(state: MusicStateSnapshot, input: MusicStateInput, config: MusicStateConfig): MusicStateResult — Pick the theme for the current intensity. Escalation is immediate; de-escalation waits for the tier's `holdMs` and needs the intensity to fall a `release` margin below the tier it is leaving.
|
|
235
|
+
|
|
236
|
+
## @jgengine/core/audio/stepCadence
|
|
237
|
+
|
|
238
|
+
- `StepCadenceConfig` (interface): interface StepCadenceConfig — Distance-driven footstep cadence. Pure state-in/state-out: feed it how far a mover travelled this frame and it tells you how many steps landed, so foley stays locked to ground distance instead of a wall-clock timer that desyncs when speed changes.
|
|
239
|
+
- `StepCadenceInput` (interface): interface StepCadenceInput — What the mover did this advance: ground distance covered, whether feet are planting, and the stride multiplier.
|
|
240
|
+
- `StepCadenceResult` (interface): interface StepCadenceResult — Outcome of one advance: the next state plus how many steps landed and which foot led.
|
|
241
|
+
- `StepCadenceState` (interface): interface StepCadenceState — Cadence bookkeeping between advances — banked distance, last moving state, and the step count whose parity is the foot.
|
|
242
|
+
- `advanceStepCadence` (function): function advanceStepCadence(state: StepCadenceState, input: StepCadenceInput, config: StepCadenceConfig): StepCadenceResult — Bank this advance's distance and report the steps it completed. Pure — same state and input always give the same result, so footstep timing is testable headless and replays identically.
|
|
243
|
+
- `createStepCadenceState` (function): function createStepCadenceState(): StepCadenceState — Fresh cadence state for a mover that is standing still.
|
|
244
|
+
|
|
226
245
|
## @jgengine/core/audio/synth
|
|
227
246
|
|
|
228
247
|
- `NoiseVoice` (interface): interface NoiseVoice — A filtered white-noise burst — impacts, whooshes, breath, crackle. Realised from a shared 1s noise buffer at a randomised playback rate and start offset, decaying exponentially to silence at `duration * decay`.
|
|
@@ -1598,7 +1617,7 @@
|
|
|
1598
1617
|
- `BoundsSpec` (type): type BoundsSpec = | { readonly kind: "sphere"; readonly radius: number; readonly offset?: Vec3 } | { readonly kind: "aabb"; readonly half: Vec3; readonly offset?: Vec3 } | { readonly kind: "rect"; readonly halfWidth: number; readonly halfDepth: number; readonly halfHeight?: number; readonly offset?:… — How a renderable declares its extent. AABB, bounding sphere, and 2D rectangle cover the common cases; `point` is the degenerate zero-size default for objects that never override. `offset` shifts the volume from the object origin (e.g. a tall model whose pivot is at its feet).
|
|
1599
1618
|
- `BoxFormationOptions` (interface): interface BoxFormationOptions — Options for {@link boxFormation}.
|
|
1600
1619
|
- `BuildRole` (type): type BuildRole = "owner" | "editor" | "viewer" — ⚠ undocumented
|
|
1601
|
-
- `BuildingEnvironmentDescriptor` (type): type BuildingEnvironmentDescriptor = { kind: "building" } & Required< Pick<BuildingEnvironmentConfig, "count" | "footprint" | "stories" | "storyHeight" | "spacing" | "style"> > & Pick<BuildingEnvironmentConfig, "seed" | "position" | "palette" | "along"> — ⚠ undocumented
|
|
1620
|
+
- `BuildingEnvironmentDescriptor` (type): type BuildingEnvironmentDescriptor = { kind: "building" } & Required< Pick<BuildingEnvironmentConfig, "count" | "footprint" | "stories" | "storyHeight" | "spacing" | "style"> > & Pick<BuildingEnvironmentConfig, "seed" | "position" | "palette" | "along" | "kit"> — ⚠ undocumented
|
|
1602
1621
|
- `BuildingIndex` (interface): interface BuildingIndex — ⚠ undocumented
|
|
1603
1622
|
- `BuildingPaletteOverrides` (type): type BuildingPaletteOverrides = Partial<BuildingPalette> — ⚠ undocumented
|
|
1604
1623
|
- `BuildingStyle` (type): type BuildingStyle = | "generic" | "capital" | "village" | "desert" | "industrial" | "coastal" | "neon" | "ruin" | "frontier" | "aerial" — ⚠ undocumented
|
|
@@ -1818,6 +1837,9 @@
|
|
|
1818
1837
|
- `NoiseFieldConfig` (interface): interface NoiseFieldConfig — Configuration for {@link noiseField}: seed, amplitude, and fractal noise shaping.
|
|
1819
1838
|
- `NoiseVoice` (interface): interface NoiseVoice — A filtered white-noise burst — impacts, whooshes, breath, crackle. Realised from a shared 1s noise buffer at a randomised playback rate and start offset, decaying exponentially to silence at `duration * decay`.
|
|
1820
1839
|
- `NoteEvent` (interface): interface NoteEvent — One scheduled note in a theme, positioned on the loop's quarter-note grid.
|
|
1840
|
+
- `ObjectSlotRejection` (type): type ObjectSlotRejection = | "unknown-object" | "no-slot-inventory" | "unknown-inventory" | "no-space" | "wrong-kind" | "slot-occupied" | "invalid-slot" | "insufficient" — Why an object slot mutation was refused — the instance, its catalog entry, the player inventory, or the declared `accepts`/capacity.
|
|
1841
|
+
- `ObjectSlotResult` (type): type ObjectSlotResult = { status: "ok" } | { status: "rejected"; reason: ObjectSlotRejection } — Outcome of an object slot mutation: applied, or refused with a reason and no state change on either side.
|
|
1842
|
+
- `ObjectSlotTransfer` (interface): interface ObjectSlotTransfer — Which player inventory a transfer moves between, and what it moves.
|
|
1821
1843
|
- `ObjectVisual` (interface): interface ObjectVisual — ⚠ undocumented
|
|
1822
1844
|
- `OceanEnvironmentDescriptor` (type): type OceanEnvironmentDescriptor = { kind: "ocean" } & Required< Pick<OceanEnvironmentConfig, "bounds" | "level" | "waveHeight" | "waveScale" | "waveSpeed" | "color"> > & Pick<OceanEnvironmentConfig, "position" | "levelAt"> — ⚠ undocumented
|
|
1823
1845
|
- `Order` (interface): interface Order<TPayload = unknown> — One issued order. Fully serializable: `kind` is a registry key (not a closure), `payload` is the caller's plain data, and `state` is the kind's own plain progress scratch (e.g. a patrol waypoint index) written during `start`/`update`. Never holds functions, so the whole queue round-trips through `JSON`/`structuredClone`.
|
|
@@ -1932,6 +1954,7 @@
|
|
|
1932
1954
|
- `SceneKindObject` (interface): interface SceneKindObject — The raw document object a resolver receives — shape shared by markers, volumes, and paths.
|
|
1933
1955
|
- `SceneKindResolveContext` (interface): interface SceneKindResolveContext — Ground sampler + options a resolver may read (terrain height/normal snap).
|
|
1934
1956
|
- `SceneObject` (interface): interface SceneObject — ⚠ undocumented
|
|
1957
|
+
- `SceneObjectSlots` (interface): interface SceneObjectSlots — Per-instance container contents for placed objects whose catalog entry declares `slotInventory` — the engine side of "install a GPU into a rack", "put an item in a chest". Contents live on `SceneObject.slots`, so they replicate and save with the object rather than needing a game-owned table keyed by `instanceId`, and every mutation is validated against the declared `accepts` and stack limits.
|
|
1935
1958
|
- `SceneRaycastApi` (interface): interface SceneRaycastApi — ⚠ undocumented
|
|
1936
1959
|
- `SceneRaycastHit` (interface): interface SceneRaycastHit — ⚠ undocumented
|
|
1937
1960
|
- `ScreenEffect` (interface): interface ScreenEffect — A live effect in the current composite: its label, color, region, and the eased opacity `0..1` to draw *right now*. These objects are pooled and reused across {@link ScreenEffectsController.composite} calls — read them, don't retain.
|
|
@@ -2471,6 +2494,18 @@
|
|
|
2471
2494
|
- `BuildingIndex` (interface): interface BuildingIndex — ⚠ undocumented
|
|
2472
2495
|
- `buildingIndex` (function): function buildingIndex(buildings: readonly GeneratedBuilding[]): BuildingIndex — ⚠ undocumented
|
|
2473
2496
|
|
|
2497
|
+
## @jgengine/core/world/buildingKit
|
|
2498
|
+
|
|
2499
|
+
- `BuildingKit` (interface): interface BuildingKit — Binds a building's generated part slots to models. A kit is plain serializable data — scene documents can carry one — and is deliberately unopinionated about art direction: the engine ships the seam, games ship the bindings.
|
|
2500
|
+
- `BuildingKitBinding` (type): type BuildingKitBinding = | { readonly type: "model"; readonly part: BuildingKitPart; readonly fit: BuildingKitFit } | { readonly type: "box" } | { readonly type: "omit" } — What a renderer should do with one generated part.
|
|
2501
|
+
- `BuildingKitFit` (type): type BuildingKitFit = "stretch" | "contain" | "cover" | "native" — How a kit model's native bounds are mapped onto the slot box `BuildingPartPlacement.scale` describes.
|
|
2502
|
+
- `BuildingKitInput` (interface): interface BuildingKitInput — The authoring shape {@link defineBuildingKit} normalizes into a {@link BuildingKit}.
|
|
2503
|
+
- `BuildingKitPart` (interface): interface BuildingKitPart — One bindable variant for a building part slot. `model` is an opaque reference the host resolves (an asset id, a pack-relative path, or a URL) — core never loads anything.
|
|
2504
|
+
- `BuildingKitPartInput` (type): type BuildingKitPartInput = string | BuildingKitPart — A variant as authored: a bare model reference, or the full {@link BuildingKitPart} when it needs fit or transform tuning.
|
|
2505
|
+
- `buildingKitVariantCounts` (function): function buildingKitVariantCounts(kit: BuildingKit): Partial<BuildingVariantCounts> — The variant counts a kit actually supplies, shaped for `BuildingConfigInput.variants`. Feed this into `generateBuilding` so seeded picks spread across exactly the models the kit binds instead of indexing past them and wrapping unevenly.
|
|
2506
|
+
- `defineBuildingKit` (function): function defineBuildingKit(input: BuildingKitInput): BuildingKit — Validates and normalizes a kit description, accepting bare model strings as single-field variants. Throws on empty variant lists so a typo fails at authoring time rather than silently falling back to untextured boxes.
|
|
2507
|
+
- `resolveBuildingKitPart` (function): function resolveBuildingKitPart(kit: BuildingKit | undefined, kind: BuildingPartKind, slot?: BuildingKitSlot): BuildingKitBinding — Resolves one generated part against a kit. Pass the placement's `kind` and its `kit` slot; the slot's `variant` wraps around the bound list, so a kit may carry any number of variants independent of the generator's `variants` counts.
|
|
2508
|
+
|
|
2474
2509
|
## @jgengine/core/world/buildingLots
|
|
2475
2510
|
|
|
2476
2511
|
- `BuildingLotOptions` (interface): interface BuildingLotOptions — Options for {@link deriveBuildingLots}.
|
|
@@ -2504,6 +2539,9 @@
|
|
|
2504
2539
|
- `DEFAULT_BUILDING_STYLE` (const): const DEFAULT_BUILDING_STYLE: BuildingStyle — ⚠ undocumented
|
|
2505
2540
|
- `GeneratedBuilding` (interface): interface GeneratedBuilding — ⚠ undocumented
|
|
2506
2541
|
- `Vec3` (type): type Vec3 = readonly [number, number, number] — ⚠ undocumented
|
|
2542
|
+
- `createBuildingConfig` (function): function createBuildingConfig(input: BuildingConfigInput = {}): BuildingConfig — Fill a partial building description out to a complete {@link BuildingConfig}, clamping counts and probabilities. Use it to inspect or adjust the resolved shape before generating.
|
|
2543
|
+
- `generateBuilding` (function): function generateBuilding(input: BuildingConfigInput = {}): GeneratedBuilding — Generate one building as a flat list of placed facade parts: bays × floors per facade, a ground row, corners, and a roof, with every decorative part rolled from `probabilities` and every part tagged with a `kit` slot the renderer binds to a model (see `world/buildingKit`). Deterministic from `seed`.
|
|
2544
|
+
- `generateBuildingDistrict` (function): function generateBuildingDistrict(config: BuildingGridConfig): GeneratedBuilding[] — Generate a grid of buildings on lots sized by `lotSize` and separated by `streetWidth`, each with its floor count rolled from `floorRange`.
|
|
2507
2545
|
|
|
2508
2546
|
## @jgengine/core/world/carve
|
|
2509
2547
|
|
|
@@ -2713,7 +2751,7 @@
|
|
|
2713
2751
|
- `BiomeSky` (interface): interface BiomeSky — Per-band sky/light override cross-faded along z by `createBiomeSkySampler`; unset fields fall through to the base sky.
|
|
2714
2752
|
- `BiomesWorldConfig` (interface): interface BiomesWorldConfig extends WorldGridConfig — ⚠ undocumented
|
|
2715
2753
|
- `BuildingEnvironmentConfig` (interface): interface BuildingEnvironmentConfig — ⚠ undocumented
|
|
2716
|
-
- `BuildingEnvironmentDescriptor` (type): type BuildingEnvironmentDescriptor = { kind: "building" } & Required< Pick<BuildingEnvironmentConfig, "count" | "footprint" | "stories" | "storyHeight" | "spacing" | "style"> > & Pick<BuildingEnvironmentConfig, "seed" | "position" | "palette" | "along"> — ⚠ undocumented
|
|
2754
|
+
- `BuildingEnvironmentDescriptor` (type): type BuildingEnvironmentDescriptor = { kind: "building" } & Required< Pick<BuildingEnvironmentConfig, "count" | "footprint" | "stories" | "storyHeight" | "spacing" | "style"> > & Pick<BuildingEnvironmentConfig, "seed" | "position" | "palette" | "along" | "kit"> — ⚠ undocumented
|
|
2717
2755
|
- `BuildingFrontageConfig` (interface): interface BuildingFrontageConfig — Street-aware placement for `building()`: instead of a grid scattered around `position`, lots are stepped along each road's frontage, offset by a consistent setback (a curb + sidewalk strip), and turned so every building FRONT faces its road. `footprint` (`w` = frontage width, `d` = depth) and `spacing` (along-road gap) are shared with the grid mode. Deterministic and bounded by `maxLots`.
|
|
2718
2756
|
- `BuildingFrontageRoad` (interface): interface BuildingFrontageRoad — One road a `building({ along })` frontage lines with buildings: centerline + full width.
|
|
2719
2757
|
- `EnvironmentArea` (interface): interface EnvironmentArea extends WorldBounds — ⚠ undocumented
|