@jgengine/core 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/audio/musicState.d.ts +47 -0
- package/dist/audio/musicState.js +38 -0
- package/dist/audio/stepCadence.d.ts +47 -0
- package/dist/audio/stepCadence.js +29 -0
- package/dist/audio/synth.d.ts +12 -0
- package/dist/game/playableGame.d.ts +36 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +167 -1
- package/dist/multiplayer.d.ts +1 -0
- package/dist/multiplayer.js +1 -0
- package/dist/runtime/context/objectSlots.d.ts +60 -0
- package/dist/runtime/context/objectSlots.js +103 -0
- package/dist/runtime/context/player.d.ts +3 -1
- package/dist/runtime/context/player.js +1 -0
- package/dist/runtime/context/scene.d.ts +9 -0
- package/dist/runtime/context/scene.js +14 -0
- package/dist/runtime/descriptors/baseline.d.ts +3 -1
- package/dist/runtime/descriptors/baseline.js +11 -0
- package/dist/runtime/gameContext.js +6 -0
- package/dist/runtime/gameContextTypes.d.ts +13 -0
- package/dist/runtime/hostPersistence.js +9 -5
- package/dist/runtime/objectRows.d.ts +17 -0
- package/dist/runtime/objectRows.js +35 -0
- package/dist/runtime/snapshot.d.ts +15 -0
- package/dist/runtime/snapshot.js +12 -0
- package/dist/scene/objectStore.d.ts +26 -0
- package/dist/scene/objectStore.js +51 -9
- package/dist/world/buildingKit.d.ts +110 -0
- package/dist/world/buildingKit.js +93 -0
- package/dist/world/buildings.d.ts +25 -3
- package/dist/world/buildings.js +39 -5
- package/dist/world/cityKind.d.ts +6 -0
- package/dist/world/cityKind.js +3 -0
- package/dist/world/environmentSummary.js +13 -3
- package/dist/world/features.d.ts +7 -1
- package/dist/world/features.js +1 -0
- package/dist/world.d.ts +1 -0
- package/dist/world.js +1 -0
- package/package.json +1 -1
- package/skills/jgengine/api.md +15 -3
- package/skills/jgengine/capabilities.md +12 -0
- package/skills/jgengine-gameplay/api.md +2 -1
- 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
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seeded procedural buildings: a footprint of bays × floors emitted as placed facade parts — walls,
|
|
3
|
+
* windows, storefronts, roof and decor — each tagged with a kit slot `world/buildingKit` binds to a
|
|
4
|
+
* model. Deterministic and renderer-agnostic.
|
|
5
|
+
*/
|
|
1
6
|
import { type Aabb, type Footprint, type Vec2 } from "./geometry.js";
|
|
2
7
|
export type BuildingSeed = number | string;
|
|
3
8
|
export type Vec3 = readonly [number, number, number];
|
|
@@ -105,11 +110,28 @@ export declare const DEFAULT_BUILDING_STYLE: BuildingStyle;
|
|
|
105
110
|
export declare const BUILDING_STYLE_PALETTES: Record<BuildingStyle, BuildingPalette>;
|
|
106
111
|
/** @internal */
|
|
107
112
|
export declare function resolveBuildingPalette(style?: BuildingStyle, overrides?: BuildingPaletteOverrides): BuildingPalette;
|
|
108
|
-
/**
|
|
113
|
+
/**
|
|
114
|
+
* Fill a partial building description out to a complete {@link BuildingConfig}, clamping counts and
|
|
115
|
+
* probabilities. Use it to inspect or adjust the resolved shape before generating.
|
|
116
|
+
*
|
|
117
|
+
* @capability building-generator generate seeded buildings as placed facade parts — bays, floors, roof, decor
|
|
118
|
+
*/
|
|
109
119
|
export declare function createBuildingConfig(input?: BuildingConfigInput): BuildingConfig;
|
|
110
|
-
/**
|
|
120
|
+
/**
|
|
121
|
+
* Generate one building as a flat list of placed facade parts: bays × floors per facade, a ground
|
|
122
|
+
* row, corners, and a roof, with every decorative part rolled from `probabilities` and every part
|
|
123
|
+
* tagged with a `kit` slot the renderer binds to a model (see `world/buildingKit`). Deterministic
|
|
124
|
+
* from `seed`.
|
|
125
|
+
*
|
|
126
|
+
* @capability building-generator generate a seeded building as placed facade parts (bays, floors, roof, decor)
|
|
127
|
+
*/
|
|
111
128
|
export declare function generateBuilding(input?: BuildingConfigInput): GeneratedBuilding;
|
|
112
129
|
/** @internal */
|
|
113
130
|
export declare function createBuildingGrid(config: BuildingGridConfig): BuildingLot[];
|
|
114
|
-
/**
|
|
131
|
+
/**
|
|
132
|
+
* Generate a grid of buildings on lots sized by `lotSize` and separated by `streetWidth`, each with
|
|
133
|
+
* its floor count rolled from `floorRange`.
|
|
134
|
+
*
|
|
135
|
+
* @capability building-generator generate a grid district of seeded buildings
|
|
136
|
+
*/
|
|
115
137
|
export declare function generateBuildingDistrict(config: BuildingGridConfig): GeneratedBuilding[];
|
package/dist/world/buildings.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seeded procedural buildings: a footprint of bays × floors emitted as placed facade parts — walls,
|
|
3
|
+
* windows, storefronts, roof and decor — each tagged with a kit slot `world/buildingKit` binds to a
|
|
4
|
+
* model. Deterministic and renderer-agnostic.
|
|
5
|
+
*/
|
|
1
6
|
import { footprintAabb } from "./geometry.js";
|
|
2
7
|
const DEFAULT_PROBABILITIES = {
|
|
3
8
|
window: 0.88,
|
|
@@ -229,7 +234,12 @@ function rangeInt(seed, key, min, max) {
|
|
|
229
234
|
const hi = Math.max(min, max);
|
|
230
235
|
return lo + Math.floor(hash01(seed, key) * (hi - lo + 1));
|
|
231
236
|
}
|
|
232
|
-
/**
|
|
237
|
+
/**
|
|
238
|
+
* Fill a partial building description out to a complete {@link BuildingConfig}, clamping counts and
|
|
239
|
+
* probabilities. Use it to inspect or adjust the resolved shape before generating.
|
|
240
|
+
*
|
|
241
|
+
* @capability building-generator generate seeded buildings as placed facade parts — bays, floors, roof, decor
|
|
242
|
+
*/
|
|
233
243
|
export function createBuildingConfig(input = {}) {
|
|
234
244
|
return {
|
|
235
245
|
id: input.id ?? DEFAULT_BUILDING_CONFIG.id,
|
|
@@ -291,6 +301,13 @@ function panelPosition(config, facade, bay, level, width, depth) {
|
|
|
291
301
|
const z = config.center[1] - depth / 2 + config.bayWidth * (bay + 0.5);
|
|
292
302
|
return [faceDepth(facade, width, depth, config), y, z];
|
|
293
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Panel dimensions inside one bay. Structural parts (wall, shutter) must pass 1/1 so they tile their
|
|
306
|
+
* bay completely — a generated building is a shell of thin panels with no solid core, so any ratio
|
|
307
|
+
* below 1 leaves a continuous slit at every bay seam and floor line that you can see through the
|
|
308
|
+
* building and out the far side. Decorative overlays (window, storefront, awning, sign) sit on top of
|
|
309
|
+
* a full wall and are meant to be inset.
|
|
310
|
+
*/
|
|
294
311
|
function panelScale(config, _facade, heightRatio, widthRatio = 0.92) {
|
|
295
312
|
const along = config.bayWidth * widthRatio;
|
|
296
313
|
const high = config.floorHeight * heightRatio;
|
|
@@ -333,7 +350,7 @@ function pushUpperFloorParts(parts, config, facade, bay, level, width, depth) {
|
|
|
333
350
|
const cell = { facade, level, bay };
|
|
334
351
|
const key = `${facade}:${level}:${bay}`;
|
|
335
352
|
const position = panelPosition(config, facade, bay, level, width, depth);
|
|
336
|
-
parts.push(part(config, "wall", facade, position, panelScale(config, facade,
|
|
353
|
+
parts.push(part(config, "wall", facade, position, panelScale(config, facade, 1, 1), `${key}:wall`, cell, ["structure", "upper"]));
|
|
337
354
|
if (!chance(config.seed, `${key}:window`, config.probabilities.window))
|
|
338
355
|
return;
|
|
339
356
|
parts.push(part(config, "window", facade, position, panelScale(config, facade, 0.46, 0.58), `${key}:window`, cell, ["upper"]));
|
|
@@ -355,6 +372,11 @@ function pushGroundFloorParts(parts, config, facade, bay, width, depth) {
|
|
|
355
372
|
const key = `${facade}:0:${bay}`;
|
|
356
373
|
const position = panelPosition(config, facade, bay, 0, width, depth);
|
|
357
374
|
if (facade === "front" && chance(config.seed, `${key}:open`, config.probabilities.openStore)) {
|
|
375
|
+
// Back the glass with a wall the way an upper floor backs its window. A generated building is a
|
|
376
|
+
// hollow shell, so a translucent storefront with nothing behind it is a hole straight through the
|
|
377
|
+
// building to the world beyond — and open stores only land on the street-facing facade, so a row
|
|
378
|
+
// of shops along a road was the most visible place for it to read as see-through.
|
|
379
|
+
parts.push(part(config, "wall", facade, position, panelScale(config, facade, 1, 1), `${key}:wall`, cell, ["structure", "ground"]));
|
|
358
380
|
parts.push(part(config, "storefront", facade, position, panelScale(config, facade, 0.86), `${key}:store`, cell, ["ground", "open"]));
|
|
359
381
|
if (chance(config.seed, `${key}:sign`, config.probabilities.storeSign)) {
|
|
360
382
|
const signPosition = [position[0], position[1] + config.floorHeight * 0.34, position[2]];
|
|
@@ -363,7 +385,7 @@ function pushGroundFloorParts(parts, config, facade, bay, width, depth) {
|
|
|
363
385
|
return;
|
|
364
386
|
}
|
|
365
387
|
const kind = facade === "front" ? "shutter" : "wall";
|
|
366
|
-
parts.push(part(config, kind, facade, position, panelScale(config, facade,
|
|
388
|
+
parts.push(part(config, kind, facade, position, panelScale(config, facade, 1, 1), `${key}:${kind}`, cell, ["ground", "closed"]));
|
|
367
389
|
}
|
|
368
390
|
function pushFacade(parts, config, facade, width, depth) {
|
|
369
391
|
const bays = facade === "front" || facade === "back" ? config.baysWide : config.baysDeep;
|
|
@@ -408,7 +430,14 @@ function pushCorners(parts, config, width, depth) {
|
|
|
408
430
|
parts.push(part(config, "corner", index < 2 ? "back" : "front", position, [config.facadeDepth * 1.35, height, config.facadeDepth * 1.35], `corner:${index}`, undefined, ["structure"]));
|
|
409
431
|
});
|
|
410
432
|
}
|
|
411
|
-
/**
|
|
433
|
+
/**
|
|
434
|
+
* Generate one building as a flat list of placed facade parts: bays × floors per facade, a ground
|
|
435
|
+
* row, corners, and a roof, with every decorative part rolled from `probabilities` and every part
|
|
436
|
+
* tagged with a `kit` slot the renderer binds to a model (see `world/buildingKit`). Deterministic
|
|
437
|
+
* from `seed`.
|
|
438
|
+
*
|
|
439
|
+
* @capability building-generator generate a seeded building as placed facade parts (bays, floors, roof, decor)
|
|
440
|
+
*/
|
|
412
441
|
export function generateBuilding(input = {}) {
|
|
413
442
|
const config = createBuildingConfig(input);
|
|
414
443
|
const width = config.baysWide * config.bayWidth;
|
|
@@ -471,7 +500,12 @@ export function createBuildingGrid(config) {
|
|
|
471
500
|
}
|
|
472
501
|
return lots;
|
|
473
502
|
}
|
|
474
|
-
/**
|
|
503
|
+
/**
|
|
504
|
+
* Generate a grid of buildings on lots sized by `lotSize` and separated by `streetWidth`, each with
|
|
505
|
+
* its floor count rolled from `floorRange`.
|
|
506
|
+
*
|
|
507
|
+
* @capability building-generator generate a grid district of seeded buildings
|
|
508
|
+
*/
|
|
475
509
|
export function generateBuildingDistrict(config) {
|
|
476
510
|
return createBuildingGrid(config).map((lot) => generateBuilding(lot.config));
|
|
477
511
|
}
|
package/dist/world/cityKind.d.ts
CHANGED
|
@@ -106,6 +106,12 @@ export interface CityRules {
|
|
|
106
106
|
sidewalkWidth: number;
|
|
107
107
|
/** Building style palette id (see {@link BUILDING_STYLE_PALETTES}). */
|
|
108
108
|
style: BuildingStyle;
|
|
109
|
+
/**
|
|
110
|
+
* Name of the `BuildingKit` the near-LOD facades render with, or `""` for the palette blocks.
|
|
111
|
+
* A game registers its bound art under this name; the document carries only the name, so a scene
|
|
112
|
+
* stays portable and small.
|
|
113
|
+
*/
|
|
114
|
+
buildingKit: string;
|
|
109
115
|
/** Seed string; same seed reproduces the same city. Empty falls back to the volume id. */
|
|
110
116
|
seed: string;
|
|
111
117
|
}
|
package/dist/world/cityKind.js
CHANGED
|
@@ -103,6 +103,7 @@ export const CITY_DEFAULTS = {
|
|
|
103
103
|
sidewalks: true,
|
|
104
104
|
sidewalkWidth: 1.9,
|
|
105
105
|
style: DEFAULT_BUILDING_STYLE,
|
|
106
|
+
buildingKit: "",
|
|
106
107
|
seed: "",
|
|
107
108
|
};
|
|
108
109
|
/** The city parameter schema — drives the inspector sliders and `meta` parse via the studio seam. */
|
|
@@ -184,6 +185,7 @@ export const CITY_SCHEMA = {
|
|
|
184
185
|
default: CITY_DEFAULTS.style,
|
|
185
186
|
options: Object.keys(BUILDING_STYLE_PALETTES).map((style) => ({ value: style })),
|
|
186
187
|
},
|
|
188
|
+
{ type: "text", key: "buildingKit", label: "building kit", group: "buildings", default: CITY_DEFAULTS.buildingKit },
|
|
187
189
|
{ type: "range", key: "treeDensity", label: "tree density", group: "greenery", min: 0, max: 1, step: 0.01, default: CITY_DEFAULTS.treeDensity },
|
|
188
190
|
{ type: "weightedList", key: "treeMix", label: "tree mix", group: "greenery", itemLabel: "species", default: CITY_DEFAULTS.treeMix },
|
|
189
191
|
{ type: "range", key: "lightDensity", label: "street lights", group: "greenery", min: 0, max: 1, step: 0.01, default: CITY_DEFAULTS.lightDensity },
|
|
@@ -563,6 +565,7 @@ export function readCityRules(meta) {
|
|
|
563
565
|
sidewalks: params["sidewalks"],
|
|
564
566
|
sidewalkWidth: params["sidewalkWidth"],
|
|
565
567
|
style: params["style"],
|
|
568
|
+
buildingKit: params["buildingKit"] ?? CITY_DEFAULTS.buildingKit,
|
|
566
569
|
seed: params["seed"],
|
|
567
570
|
};
|
|
568
571
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DEFAULT_BUILDING_CONFIG, generateBuilding, generateBuildingDistrict, resolveBuildingPalette, } from "./buildings.js";
|
|
2
|
+
import { buildingKitVariantCounts } from "./buildingKit.js";
|
|
2
3
|
import { deriveBuildingLots } from "./buildingLots.js";
|
|
3
4
|
import { pathLength } from "./roads.js";
|
|
4
5
|
import { resolveTerrainField, resolveTerrainPalette } from "./terrain.js";
|
|
@@ -21,10 +22,17 @@ export function resolveStructureBuildings(descriptor) {
|
|
|
21
22
|
lotSize: descriptor.footprint,
|
|
22
23
|
streetWidth: spacing,
|
|
23
24
|
floorRange: descriptor.stories,
|
|
24
|
-
base: { floorHeight: descriptor.storyHeight },
|
|
25
|
+
base: { floorHeight: descriptor.storyHeight, ...kitVariants(descriptor.kit) },
|
|
25
26
|
...(descriptor.seed === undefined ? {} : { seed: descriptor.seed }),
|
|
26
27
|
}).slice(0, descriptor.count);
|
|
27
28
|
}
|
|
29
|
+
/** Spread seeded variant picks across exactly the models a kit binds, so no bound variant goes unused. */
|
|
30
|
+
function kitVariants(kit) {
|
|
31
|
+
if (kit === undefined)
|
|
32
|
+
return {};
|
|
33
|
+
const variants = buildingKitVariantCounts(kit);
|
|
34
|
+
return Object.keys(variants).length === 0 ? {} : { variants };
|
|
35
|
+
}
|
|
28
36
|
function hashU32(input) {
|
|
29
37
|
let hash = 2166136261;
|
|
30
38
|
for (let i = 0; i < input.length; i += 1) {
|
|
@@ -57,9 +65,10 @@ function resolveFrontageBuildings(descriptor, along) {
|
|
|
57
65
|
});
|
|
58
66
|
const [floorsMin, floorsMax] = descriptor.stories;
|
|
59
67
|
const bayWidth = DEFAULT_BUILDING_CONFIG.bayWidth;
|
|
60
|
-
|
|
68
|
+
const variants = kitVariants(descriptor.kit);
|
|
69
|
+
return lots.map((lot, index) => buildLot(lot, index, seed, floorsMin, floorsMax, bayWidth, descriptor.storyHeight, variants));
|
|
61
70
|
}
|
|
62
|
-
function buildLot(lot, index, seed, floorsMin, floorsMax, bayWidth, storyHeight) {
|
|
71
|
+
function buildLot(lot, index, seed, floorsMin, floorsMax, bayWidth, storyHeight, variants = {}) {
|
|
63
72
|
const lo = Math.min(floorsMin, floorsMax);
|
|
64
73
|
const hi = Math.max(floorsMin, floorsMax);
|
|
65
74
|
const floors = lo + Math.floor((hashU32(`${seed}:floors:${index}`) / 4294967296) * (hi - lo + 1));
|
|
@@ -74,6 +83,7 @@ function buildLot(lot, index, seed, floorsMin, floorsMax, bayWidth, storyHeight)
|
|
|
74
83
|
baysDeep,
|
|
75
84
|
bayWidth,
|
|
76
85
|
floorHeight: storyHeight,
|
|
86
|
+
...variants,
|
|
77
87
|
});
|
|
78
88
|
return { ...building, rotationY: lot.rotationY };
|
|
79
89
|
}
|
package/dist/world/features.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BuildingPaletteOverrides, BuildingStyle } from "./buildings.js";
|
|
2
|
+
import type { BuildingKit } from "./buildingKit.js";
|
|
2
3
|
import type { AvoidZone } from "./geometry.js";
|
|
3
4
|
import type { TerrainPathProfile } from "./pathTerrain.js";
|
|
4
5
|
import type { PlaceWorldFeature } from "./place.js";
|
|
@@ -352,6 +353,11 @@ export interface BuildingEnvironmentConfig {
|
|
|
352
353
|
* back by a sidewalk strip) instead of the grid around `position`. See {@link BuildingFrontageConfig}.
|
|
353
354
|
*/
|
|
354
355
|
along?: BuildingFrontageConfig;
|
|
356
|
+
/**
|
|
357
|
+
* Binds facade part slots to real models (see `world/buildingKit`). Unbound kinds keep the
|
|
358
|
+
* untextured block; without a kit the whole facade stays blocks, as before.
|
|
359
|
+
*/
|
|
360
|
+
kit?: BuildingKit;
|
|
355
361
|
}
|
|
356
362
|
export type PadSize = readonly [number, number] | {
|
|
357
363
|
radius: number;
|
|
@@ -393,7 +399,7 @@ export type OceanEnvironmentDescriptor = {
|
|
|
393
399
|
} & Required<Pick<OceanEnvironmentConfig, "bounds" | "level" | "waveHeight" | "waveScale" | "waveSpeed" | "color">> & Pick<OceanEnvironmentConfig, "position" | "levelAt">;
|
|
394
400
|
export type BuildingEnvironmentDescriptor = {
|
|
395
401
|
kind: "building";
|
|
396
|
-
} & Required<Pick<BuildingEnvironmentConfig, "count" | "footprint" | "stories" | "storyHeight" | "spacing" | "style">> & Pick<BuildingEnvironmentConfig, "seed" | "position" | "palette" | "along">;
|
|
402
|
+
} & Required<Pick<BuildingEnvironmentConfig, "count" | "footprint" | "stories" | "storyHeight" | "spacing" | "style">> & Pick<BuildingEnvironmentConfig, "seed" | "position" | "palette" | "along" | "kit">;
|
|
397
403
|
export type PadEnvironmentDescriptor = {
|
|
398
404
|
kind: "pad";
|
|
399
405
|
} & Required<Pick<PadEnvironmentConfig, "center" | "size" | "height" | "color">> & Pick<PadEnvironmentConfig, "rotationY" | "elevation">;
|
package/dist/world/features.js
CHANGED
|
@@ -247,6 +247,7 @@ export function building(config = {}) {
|
|
|
247
247
|
...(config.position === undefined ? {} : { position: config.position }),
|
|
248
248
|
...(config.palette === undefined ? {} : { palette: config.palette }),
|
|
249
249
|
...(config.along === undefined ? {} : { along: config.along }),
|
|
250
|
+
...(config.kit === undefined ? {} : { kit: config.kit }),
|
|
250
251
|
});
|
|
251
252
|
}
|
|
252
253
|
export function pad(config) {
|
package/dist/world.d.ts
CHANGED
|
@@ -50,6 +50,7 @@ export { StructureGraph, type CollapseEvent, type StructureMaterial } from "./ph
|
|
|
50
50
|
export { Glide, Grapple } from "./physics/traversal.js";
|
|
51
51
|
export { DEFAULT_GRIP_CURVE, createVehicleBody, sampleGripCurve, type GripCurve } from "./physics/vehicleBody.js";
|
|
52
52
|
export { createVehicleObstacleClamp, type VehicleImpact, type VehicleObstacleClamp, } from "./physics/vehicleObstacles.js";
|
|
53
|
+
export { type ObjectSlotRejection, type ObjectSlotResult, type ObjectSlotTransfer, type SceneObjectSlots, } from "./runtime/context/objectSlots.js";
|
|
53
54
|
export { createAssetCatalog, type AssetCatalog, type ModelAssetRef, type ModelDims } from "./scene/assetCatalog.js";
|
|
54
55
|
export { partsBounds, registerAssetGenerator, type GeneratedAsset, type GeneratedPart } from "./scene/assetGenerator.js";
|
|
55
56
|
export { applyRotationPolicy, headingToRotationY, normalizeDegrees, parseAssetSpace, parseRotationPolicy, resolveAnchorOffset, resolveFacingRotationY, rotatedFootprint, rotationYToHeading, snapHeading, toEngineUnits, validateAssetSpace, type AssetAnchor, type AssetSpace, type AssetSpaceIssue, type PlacementRotationPolicy, } from "./scene/assetSpace.js";
|
package/dist/world.js
CHANGED
|
@@ -50,6 +50,7 @@ export { StructureGraph } from "./physics/structure.js";
|
|
|
50
50
|
export { Glide, Grapple } from "./physics/traversal.js";
|
|
51
51
|
export { DEFAULT_GRIP_CURVE, createVehicleBody, sampleGripCurve } from "./physics/vehicleBody.js";
|
|
52
52
|
export { createVehicleObstacleClamp, } from "./physics/vehicleObstacles.js";
|
|
53
|
+
export {} from "./runtime/context/objectSlots.js";
|
|
53
54
|
export { createAssetCatalog } from "./scene/assetCatalog.js";
|
|
54
55
|
export { partsBounds, registerAssetGenerator } from "./scene/assetGenerator.js";
|
|
55
56
|
export { applyRotationPolicy, headingToRotationY, normalizeDegrees, parseAssetSpace, parseRotationPolicy, resolveAnchorOffset, resolveFacingRotationY, rotatedFootprint, rotationYToHeading, snapHeading, toEngineUnits, validateAssetSpace, } from "./scene/assetSpace.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "jgengine core — pure-TypeScript game framework SDK (npm @jgengine/core). Entity stores, commands, combat, inventory, multiplayer seams. Zero dependencies. Not an ECS. Agent skills ship in the tarball. https://jgengine.com",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jgengine",
|
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"`
|
|
@@ -695,7 +695,8 @@
|
|
|
695
695
|
- `EntitySpriteConfig` (interface): interface EntitySpriteConfig — ⚠ undocumented
|
|
696
696
|
- `FirstPersonCameraConfig` (interface): interface FirstPersonCameraConfig — ⚠ undocumented
|
|
697
697
|
- `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.
|
|
698
|
-
- `GameCaptureConfig` (interface): interface GameCaptureConfig — How a
|
|
698
|
+
- `GameCaptureConfig` (interface): interface GameCaptureConfig — How a game makes itself screenshottable: the commands that reach live play or a named screen, the framings worth re-capturing, and the progress metrics a bot playtest samples. Declaring these once is what lets a capture host reproduce a view instead of re-deriving it by hand every run.
|
|
699
|
+
- `GameCaptureView` (interface): interface GameCaptureView — How a screenshot host reaches live gameplay in this game — the data behind `shoot --mode play`.
|
|
699
700
|
- `HemisphereLightingConfig` (interface): interface HemisphereLightingConfig — ⚠ undocumented
|
|
700
701
|
- `InspectionCameraConfig` (interface): interface InspectionCameraConfig — Model-viewer / inspection rig (#207.7) — orbit + pan + anchored zoom around a fixed point, never reads player input.
|
|
701
702
|
- `InspectionZoomAnchor` (type): type InspectionZoomAnchor = "target" | "cursor" | "center" — How scroll-zoom re-anchors the view for the inspection rig (#207.7): - `target` — dolly toward the orbit target (classic OrbitControls behavior). - `cursor` — dolly toward the point under the pointer. - `center` — dolly toward the viewport center; equivalent to `target` for an OrbitControls-driven rig, since the camera always faces `target` and that point already projects to the exact center of the viewport.
|
|
@@ -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
|
|
@@ -122,6 +122,18 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
122
122
|
|
|
123
123
|
- `createBuildSocketCatalog` (function) · `import { createBuildSocketCatalog } from "@jgengine/core/world/buildSockets"`
|
|
124
124
|
|
|
125
|
+
## building-generator — generate seeded buildings as placed facade parts — bays, floors, roof, decor
|
|
126
|
+
|
|
127
|
+
- `createBuildingConfig` (function) · `import { createBuildingConfig } from "@jgengine/core/world/buildings"`
|
|
128
|
+
- `generateBuilding` (function) · `import { generateBuilding } from "@jgengine/core/world/buildings"`
|
|
129
|
+
- `generateBuildingDistrict` (function) · `import { generateBuildingDistrict } from "@jgengine/core/world/buildings"`
|
|
130
|
+
|
|
131
|
+
## building-kit — bind generated facade part slots to real models instead of untextured blocks
|
|
132
|
+
|
|
133
|
+
- `buildingKitVariantCounts` (function) · `import { buildingKitVariantCounts } from "@jgengine/core/world/buildingKit"`
|
|
134
|
+
- `defineBuildingKit` (function) · `import { defineBuildingKit } from "@jgengine/core/world/buildingKit"`
|
|
135
|
+
- `resolveBuildingKitPart` (function) · `import { resolveBuildingKitPart } from "@jgengine/core/world/buildingKit"`
|
|
136
|
+
|
|
125
137
|
## building-lots — derive street-facing building lots from road frontage
|
|
126
138
|
|
|
127
139
|
- `deriveBuildingLots` (function) · `import { deriveBuildingLots } from "@jgengine/core/world/buildingLots"`
|
|
@@ -354,6 +366,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
354
366
|
|
|
355
367
|
- `defineMoveOrder` (function) · `import { defineMoveOrder } from "@jgengine/core/world"`
|
|
356
368
|
|
|
369
|
+
## object-slot-inventory — placed objects hold validated per-instance container contents that replicate and save with the placement
|
|
370
|
+
|
|
371
|
+
- `SceneObjectSlots` (interface) · `import { SceneObjectSlots } from "@jgengine/core/world"`
|
|
372
|
+
|
|
357
373
|
## order-queue — serializable per-entity command queue with lifecycle, preemption, and pluggable order kinds
|
|
358
374
|
|
|
359
375
|
- `createOrderQueue` (function) · `import { createOrderQueue } from "@jgengine/core/world"`
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Recipe — dressing generated buildings with a model kit
|
|
2
|
+
|
|
3
|
+
**What this wires:** the facade generator already emits every part slot a
|
|
4
|
+
building needs — walls, windows, storefronts, awnings, roof clutter. A
|
|
5
|
+
`BuildingKit` binds those slots to real GLBs, so the same massing renders as
|
|
6
|
+
untextured blocks, as a clean prefab town, or as a ruin, purely by swapping the
|
|
7
|
+
bound art.
|
|
8
|
+
|
|
9
|
+
## Bind a pack to slots
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { defineBuildingKit } from "@jgengine/core/world/buildingKit";
|
|
13
|
+
|
|
14
|
+
const kit = defineBuildingKit({
|
|
15
|
+
id: "settlement-clean",
|
|
16
|
+
parts: {
|
|
17
|
+
wall: ["/models/pack/Wall_Straight.glb", "/models/pack/Wall_Flat.glb"],
|
|
18
|
+
window: ["/models/pack/Wall_Window.glb"],
|
|
19
|
+
storefront: ["/models/pack/Door_Metal.glb"],
|
|
20
|
+
airConditioner: [{ model: "/models/pack/Vent_Big.glb", fit: "contain" }],
|
|
21
|
+
},
|
|
22
|
+
omit: ["clothesline"],
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
A bare string is shorthand for `{ model }`. `fit` decides how the model meets
|
|
27
|
+
its slot box: `stretch` (default) fills the bay — right for tiling panels;
|
|
28
|
+
`contain` keeps a prop's proportions. `defineBuildingKit` throws on an empty
|
|
29
|
+
variant list, so a typo fails at authoring time instead of rendering nothing.
|
|
30
|
+
|
|
31
|
+
Resolve `model` through your game's asset catalog rather than typing URLs, and a
|
|
32
|
+
missing id fails at boot:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
const url = assets.resolve("pack/Wall_Straight")?.url;
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Hand it to the world
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { building } from "@jgengine/core/world/features";
|
|
42
|
+
|
|
43
|
+
building({ position: [0, 0], count: 8, style: "desert", kit });
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Kinds the kit leaves unbound keep the engine's untextured block, so a partial
|
|
47
|
+
kit is a valid kit — bind walls and windows first, add roof clutter later.
|
|
48
|
+
Kinds listed in `omit` render nothing at all: use it for parts the setting
|
|
49
|
+
rejects, not for parts you have not got around to.
|
|
50
|
+
|
|
51
|
+
## Two kits, one generator
|
|
52
|
+
|
|
53
|
+
Bind a second kit with broken panels and welded-shut doors, pass it to the
|
|
54
|
+
zones that should read as derelict, and the generator, seeds, and layout stay
|
|
55
|
+
identical. That split is the point of the seam — style is art binding, not a
|
|
56
|
+
second code path.
|
|
57
|
+
|
|
58
|
+
## Spread the seeded picks
|
|
59
|
+
|
|
60
|
+
The generator's `variants` counts decide which slot index each bay asks for. If
|
|
61
|
+
a kit binds a different number of models than the config assumes, picks wrap
|
|
62
|
+
unevenly and one wall model dominates. When you drive the generator directly,
|
|
63
|
+
feed the kit's own counts back in:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { buildingKitVariantCounts } from "@jgengine/core/world/buildingKit";
|
|
67
|
+
import { generateBuilding } from "@jgengine/core/world/buildings";
|
|
68
|
+
|
|
69
|
+
generateBuilding({ seed: "town-1", variants: buildingKitVariantCounts(kit) });
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Common traps
|
|
73
|
+
|
|
74
|
+
- Do not bind `roof` to a wall-sized plate; the roof slot is one slab spanning
|
|
75
|
+
the whole footprint, not a tiling bay.
|
|
76
|
+
- `contain` for anything that must not distort (vents, crates, signs);
|
|
77
|
+
`stretch` for panels authored to fill a bay.
|
|
78
|
+
- `omit` and "unbound" are different outcomes — unbound still draws a block.
|