@jgengine/core 0.11.0 → 0.12.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 +26 -0
- package/dist/ai/populationDirector.d.ts +103 -0
- package/dist/ai/populationDirector.js +190 -0
- package/dist/ai/pursuit.d.ts +56 -0
- package/dist/ai/pursuit.js +53 -0
- package/dist/authoring.d.ts +1 -1
- package/dist/authoring.js +1 -1
- package/dist/combat.d.ts +1 -0
- package/dist/combat.js +1 -0
- package/dist/devtools/controls.d.ts +38 -0
- package/dist/devtools/controls.js +181 -0
- package/dist/devtools/devtools.d.ts +5 -197
- package/dist/devtools/devtools.js +38 -774
- package/dist/devtools/discover.d.ts +32 -0
- package/dist/devtools/discover.js +240 -0
- package/dist/devtools/fallbackSeams.d.ts +50 -0
- package/dist/devtools/fallbackSeams.js +111 -0
- package/dist/devtools/frame.d.ts +15 -0
- package/dist/devtools/frame.js +247 -0
- package/dist/devtools/logs.d.ts +15 -0
- package/dist/devtools/logs.js +86 -0
- package/dist/devtools/overrides.d.ts +16 -0
- package/dist/devtools/overrides.js +66 -0
- package/dist/devtools/types.d.ts +212 -0
- package/dist/devtools/types.js +1 -0
- package/dist/economy/auctionBook.d.ts +122 -0
- package/dist/economy/auctionBook.js +213 -0
- package/dist/economy/priceHistory.d.ts +44 -0
- package/dist/economy/priceHistory.js +89 -0
- package/dist/economy/unlockPoints.d.ts +92 -0
- package/dist/economy/unlockPoints.js +103 -0
- package/dist/editor/commandApply.d.ts +5 -0
- package/dist/editor/commandApply.js +755 -0
- package/dist/editor/commands.d.ts +48 -3
- package/dist/editor/commands.js +40 -754
- package/dist/editor/directives.d.ts +94 -0
- package/dist/editor/directives.js +206 -0
- package/dist/editor/document.d.ts +37 -2
- package/dist/editor/document.js +422 -6
- package/dist/editor/entityCatalog.d.ts +42 -0
- package/dist/editor/entityCatalog.js +101 -0
- package/dist/editor/environment.d.ts +74 -0
- package/dist/editor/environment.js +169 -0
- package/dist/editor/index.d.ts +26 -1
- package/dist/editor/index.js +8 -1
- package/dist/editor/minimap.d.ts +31 -0
- package/dist/editor/minimap.js +73 -0
- package/dist/editor/streamer.d.ts +66 -0
- package/dist/editor/streamer.js +68 -0
- package/dist/editor/types.d.ts +143 -0
- package/dist/editor/world.d.ts +136 -0
- package/dist/editor/world.js +303 -0
- package/dist/game/breeding.d.ts +161 -0
- package/dist/game/breeding.js +181 -0
- package/dist/game/cameraConfig.d.ts +451 -0
- package/dist/game/cameraConfig.js +1 -0
- package/dist/game/defineGame.d.ts +16 -9
- package/dist/game/defineGame.js +14 -6
- package/dist/game/defineSystem.d.ts +2 -0
- package/dist/game/defineSystem.js +2 -0
- package/dist/game/events.d.ts +28 -0
- package/dist/game/playableGame.d.ts +34 -435
- package/dist/game/playableGame.js +1 -1
- package/dist/game/progression.d.ts +35 -0
- package/dist/game/progression.js +19 -0
- package/dist/game/quest.js +1 -3
- package/dist/game/tribe.d.ts +184 -0
- package/dist/game/tribe.js +432 -0
- package/dist/gameplay.d.ts +3 -1
- package/dist/gameplay.js +3 -1
- package/dist/inventory/encumbrance.d.ts +69 -0
- package/dist/inventory/encumbrance.js +68 -0
- package/dist/item/durability.js +1 -3
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +24 -1
- package/dist/movement/movementModel.d.ts +15 -1
- package/dist/movement/movementModel.js +33 -15
- package/dist/movement/playerMovement.js +102 -2
- package/dist/multiplayer/combatSnapshot.js +13 -8
- package/dist/multiplayer/presenceModel.d.ts +2 -0
- package/dist/multiplayer/presenceModel.js +8 -0
- package/dist/physics/drivableVehicle.d.ts +2 -0
- package/dist/physics/drivableVehicle.js +1 -1
- package/dist/physics/flightDynamics.d.ts +82 -0
- package/dist/physics/flightDynamics.js +165 -0
- package/dist/physics/gravityField.d.ts +25 -0
- package/dist/physics/gravityField.js +41 -0
- package/dist/physics/kinematicVehicle.d.ts +87 -0
- package/dist/physics/kinematicVehicle.js +193 -11
- package/dist/physics/physicsWorld.d.ts +26 -0
- package/dist/physics/physicsWorld.js +109 -117
- package/dist/physics/vehicleObstacles.d.ts +44 -0
- package/dist/physics/vehicleObstacles.js +115 -0
- package/dist/random/rng.d.ts +4 -1
- package/dist/random/rng.js +4 -1
- package/dist/runtime/adapter.d.ts +13 -0
- package/dist/runtime/adapter.js +13 -0
- package/dist/runtime/context/combat.d.ts +51 -0
- package/dist/runtime/context/combat.js +130 -0
- package/dist/runtime/context/deathLoot.d.ts +30 -0
- package/dist/runtime/context/deathLoot.js +39 -0
- package/dist/runtime/context/player.d.ts +48 -0
- package/dist/runtime/context/player.js +125 -0
- package/dist/runtime/context/scene.d.ts +59 -0
- package/dist/runtime/context/scene.js +291 -0
- package/dist/runtime/context/snapshotCodecs.d.ts +12 -0
- package/dist/runtime/context/snapshotCodecs.js +26 -0
- package/dist/runtime/descriptors/baseline.js +26 -3
- package/dist/runtime/descriptors/features.d.ts +5 -0
- package/dist/runtime/descriptors/features.js +20 -2
- package/dist/runtime/gameContext.d.ts +2 -428
- package/dist/runtime/gameContext.js +88 -501
- package/dist/runtime/gameContextTypes.d.ts +467 -0
- package/dist/runtime/gameContextTypes.js +1 -0
- package/dist/runtime/headlessRunner.d.ts +4 -2
- package/dist/runtime/headlessRunner.js +1 -0
- package/dist/runtime/hostPolicy.d.ts +51 -0
- package/dist/runtime/hostPolicy.js +68 -0
- package/dist/runtime/hostedGameRunner.d.ts +3 -1
- package/dist/runtime/hostedGameRunner.js +1 -0
- package/dist/runtime/hostedWorldSession.d.ts +3 -1
- package/dist/runtime/hostedWorldSession.js +1 -0
- package/dist/runtime/worldSnapshot.d.ts +13 -1
- package/dist/runtime/worldSnapshot.js +15 -3
- package/dist/scene/assetCatalog.d.ts +5 -0
- package/dist/scene/assetSpace.js +1 -3
- package/dist/scene/authoredTriggers.d.ts +50 -0
- package/dist/scene/authoredTriggers.js +114 -0
- package/dist/scene/builtinSceneKinds.js +8 -3
- package/dist/scene/colliders.d.ts +58 -0
- package/dist/scene/colliders.js +108 -0
- package/dist/scene/collisionMesh.d.ts +94 -0
- package/dist/scene/collisionMesh.js +699 -0
- package/dist/scene/companion.d.ts +147 -0
- package/dist/scene/companion.js +193 -0
- package/dist/scene/entityStats.d.ts +7 -9
- package/dist/scene/entityStats.js +10 -14
- package/dist/scene/sceneKinds.d.ts +16 -0
- package/dist/scene/sceneKinds.js +4 -0
- package/dist/scene/sceneRaycast.js +15 -0
- package/dist/stats/spawnLevelStats.d.ts +110 -0
- package/dist/stats/spawnLevelStats.js +140 -0
- package/dist/stats/statPool.d.ts +70 -0
- package/dist/stats/statPool.js +59 -0
- package/dist/store/changeSignal.d.ts +3 -0
- package/dist/store/changeSignal.js +2 -0
- package/dist/store/observableKeyedStore.d.ts +2 -0
- package/dist/store/observableKeyedStore.js +1 -0
- package/dist/survival/decayMeter.js +1 -3
- package/dist/survival/regionHealth.js +1 -3
- package/dist/ui/hudLayout.d.ts +5 -0
- package/dist/ui/hudLayout.js +17 -1
- package/dist/world/authoredEntities.d.ts +29 -0
- package/dist/world/authoredEntities.js +29 -0
- package/dist/world/authoredObjects.d.ts +15 -1
- package/dist/world/authoredObjects.js +13 -1
- package/dist/world/authoredSpawn.d.ts +39 -0
- package/dist/world/authoredSpawn.js +37 -0
- package/dist/world/buildSockets.d.ts +152 -0
- package/dist/world/buildSockets.js +188 -0
- package/dist/world/cityBlocks.d.ts +152 -0
- package/dist/world/cityBlocks.js +670 -0
- package/dist/world/cityContent.d.ts +77 -0
- package/dist/world/cityContent.js +261 -0
- package/dist/world/cityGeometry.d.ts +114 -0
- package/dist/world/cityGeometry.js +573 -0
- package/dist/world/cityKind.d.ts +314 -0
- package/dist/world/cityKind.js +1578 -0
- package/dist/world/features.d.ts +18 -3
- package/dist/world/features.js +10 -1
- package/dist/world/grassKind.js +2 -2
- package/dist/world/markers.d.ts +50 -4
- package/dist/world/markers.js +49 -0
- package/dist/world/massing.js +1 -1
- package/dist/world/minimapBake.d.ts +78 -0
- package/dist/world/minimapBake.js +208 -0
- package/dist/world/minimapTrack.d.ts +17 -0
- package/dist/world/minimapTrack.js +22 -0
- package/dist/world/pathNetwork.d.ts +159 -0
- package/dist/world/pathNetwork.js +684 -0
- package/dist/world/place.d.ts +166 -0
- package/dist/world/place.js +96 -0
- package/dist/world/placeAsset.js +3 -1
- package/dist/world/poleLineKind.d.ts +40 -0
- package/dist/world/poleLineKind.js +97 -0
- package/dist/world/polyline.js +9 -2
- package/dist/world/resourceNode.d.ts +109 -0
- package/dist/world/resourceNode.js +140 -0
- package/dist/world/scatter.js +2 -20
- package/dist/world/streets.d.ts +29 -0
- package/dist/world/streets.js +55 -0
- package/dist/world/terraform.js +58 -88
- package/dist/world/terrain.d.ts +45 -0
- package/dist/world/terrain.js +78 -0
- package/dist/world.d.ts +14 -5
- package/dist/world.js +12 -3
- package/package.json +1 -1
- package/skills/game-design/SKILL.md +28 -0
- package/skills/game-design/agents/openai.yaml +4 -0
- package/skills/game-design/references/application-playbook.md +170 -0
- package/skills/game-design/references/field-guide.md +288 -0
- package/skills/jgengine/SKILL.md +7 -4
- package/skills/jgengine/api.md +172 -79
- package/skills/jgengine/capabilities.md +104 -2
- package/skills/jgengine/recipes/minimal-game.md +88 -0
- package/skills/jgengine-combat/SKILL.md +5 -1
- package/skills/jgengine-combat/api.md +36 -0
- package/skills/jgengine-combat/capabilities.md +44 -0
- package/skills/jgengine-combat/recipes/portable-stat-pools.md +122 -0
- package/skills/jgengine-combat/reference.md +12 -2
- package/skills/jgengine-gameplay/api.md +152 -26
- package/skills/jgengine-gameplay/capabilities.md +78 -2
- package/skills/jgengine-gameplay/reference-systems.md +1 -5
- package/skills/jgengine-world/SKILL.md +13 -0
- package/skills/jgengine-world/api.md +419 -15
- package/skills/jgengine-world/capabilities.md +108 -2
- package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
- package/skills/jgengine-world/reference.md +43 -4
- package/skills/jgengine/reference-cartridge.md +0 -72
package/dist/editor/document.js
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
import { cloneEditorUiDocument, decodeEditorUiDocument } from "../ui/hudDocument.js";
|
|
2
2
|
import { migrateTerrainSnapshot } from "../world/terraform.js";
|
|
3
3
|
import { cloneGridLayer, migrateGridLayer, } from "./grid.js";
|
|
4
|
+
/** Copies a baked minimap so a document's PNG + bounds are never shared by reference across rebuilds. */
|
|
5
|
+
function cloneMinimapBake(minimap) {
|
|
6
|
+
return { background: minimap.background, bounds: { ...minimap.bounds } };
|
|
7
|
+
}
|
|
8
|
+
/** Deep-copies scene-document environment/lighting so edits never mutate history snapshots. */
|
|
9
|
+
function cloneEnvironment(environment) {
|
|
10
|
+
return {
|
|
11
|
+
...(environment.preset === undefined ? {} : { preset: environment.preset }),
|
|
12
|
+
...(environment.timeOfDay === undefined ? {} : { timeOfDay: environment.timeOfDay }),
|
|
13
|
+
...(environment.horizonColor === undefined ? {} : { horizonColor: environment.horizonColor }),
|
|
14
|
+
...(environment.zenithColor === undefined ? {} : { zenithColor: environment.zenithColor }),
|
|
15
|
+
...(environment.sunIntensity === undefined ? {} : { sunIntensity: environment.sunIntensity }),
|
|
16
|
+
...(environment.ambientIntensity === undefined ? {} : { ambientIntensity: environment.ambientIntensity }),
|
|
17
|
+
...(environment.fog === undefined ? {} : { fog: { ...environment.fog } }),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function cloneDirective(directive) {
|
|
21
|
+
const area = directive.area === undefined
|
|
22
|
+
? undefined
|
|
23
|
+
: { min: [...directive.area.min], max: [...directive.area.max] };
|
|
24
|
+
const base = {
|
|
25
|
+
id: directive.id,
|
|
26
|
+
...(directive.region === undefined ? {} : { region: directive.region }),
|
|
27
|
+
...(area === undefined ? {} : { area }),
|
|
28
|
+
...(directive.seed === undefined ? {} : { seed: directive.seed }),
|
|
29
|
+
...(directive.meta === undefined ? {} : { meta: { ...directive.meta } }),
|
|
30
|
+
};
|
|
31
|
+
if (directive.kind === "scatter") {
|
|
32
|
+
return { ...base, kind: "scatter", asset: directive.asset, density: directive.density,
|
|
33
|
+
...(directive.minSpacing === undefined ? {} : { minSpacing: directive.minSpacing }),
|
|
34
|
+
...(directive.jitter === undefined ? {} : { jitter: directive.jitter }),
|
|
35
|
+
...(directive.minScale === undefined ? {} : { minScale: directive.minScale }),
|
|
36
|
+
...(directive.maxScale === undefined ? {} : { maxScale: directive.maxScale }),
|
|
37
|
+
...(directive.minYaw === undefined ? {} : { minYaw: directive.minYaw }),
|
|
38
|
+
...(directive.maxYaw === undefined ? {} : { maxYaw: directive.maxYaw }) };
|
|
39
|
+
}
|
|
40
|
+
return { ...base, kind: "population", species: directive.species.map((s) => ({ ...s })) };
|
|
41
|
+
}
|
|
42
|
+
function cloneDirectives(directives) {
|
|
43
|
+
return directives === undefined ? undefined : directives.map(cloneDirective);
|
|
44
|
+
}
|
|
4
45
|
function migrateGrids(grids) {
|
|
5
46
|
return grids === undefined ? undefined : grids.map(migrateGridLayer);
|
|
6
47
|
}
|
|
@@ -23,6 +64,9 @@ export function editorDocumentExtras(doc) {
|
|
|
23
64
|
...(doc.grids === undefined ? {} : { grids: doc.grids }),
|
|
24
65
|
...(doc.terrain === undefined ? {} : { terrain: doc.terrain }),
|
|
25
66
|
...(doc.ui === undefined ? {} : { ui: doc.ui }),
|
|
67
|
+
...(doc.directives === undefined ? {} : { directives: doc.directives }),
|
|
68
|
+
...(doc.minimap === undefined ? {} : { minimap: doc.minimap }),
|
|
69
|
+
...(doc.environment === undefined ? {} : { environment: doc.environment }),
|
|
26
70
|
};
|
|
27
71
|
}
|
|
28
72
|
/** Builds a fresh, empty editor document to start authoring a scene from scratch.
|
|
@@ -50,6 +94,8 @@ function cloneCatalogEntry(entry) {
|
|
|
50
94
|
function cloneCatalogs(catalogs) {
|
|
51
95
|
return (catalogs ?? []).map((catalog) => ({
|
|
52
96
|
id: catalog.id,
|
|
97
|
+
...(catalog.label === undefined ? {} : { label: catalog.label }),
|
|
98
|
+
...(catalog.schema === undefined ? {} : { schema: catalog.schema }),
|
|
53
99
|
entries: catalog.entries.map(cloneCatalogEntry),
|
|
54
100
|
}));
|
|
55
101
|
}
|
|
@@ -61,6 +107,7 @@ function cloneCatalogs(catalogs) {
|
|
|
61
107
|
*/
|
|
62
108
|
export function cloneEditorDocument(doc) {
|
|
63
109
|
const ui = cloneEditorUiDocument(doc.ui);
|
|
110
|
+
const directives = cloneDirectives(doc.directives);
|
|
64
111
|
return {
|
|
65
112
|
version: 1,
|
|
66
113
|
markers: doc.markers.map((marker) => ({
|
|
@@ -98,6 +145,9 @@ export function cloneEditorDocument(doc) {
|
|
|
98
145
|
...(doc.grids === undefined ? {} : { grids: doc.grids.map(cloneGridLayer) }),
|
|
99
146
|
...(doc.terrain === undefined ? {} : { terrain: doc.terrain }),
|
|
100
147
|
...(ui === undefined ? {} : { ui }),
|
|
148
|
+
...(directives === undefined ? {} : { directives }),
|
|
149
|
+
...(doc.minimap === undefined ? {} : { minimap: cloneMinimapBake(doc.minimap) }),
|
|
150
|
+
...(doc.environment === undefined ? {} : { environment: cloneEnvironment(doc.environment) }),
|
|
101
151
|
};
|
|
102
152
|
}
|
|
103
153
|
function asArray(value) {
|
|
@@ -107,11 +157,24 @@ function upsertCatalogEntries(base, overlay) {
|
|
|
107
157
|
return upsertById(base.map(cloneCatalogEntry), overlay.map(cloneCatalogEntry));
|
|
108
158
|
}
|
|
109
159
|
function upsertCatalogs(base, overlay) {
|
|
110
|
-
const byId = new Map((base ?? []).map((catalog) => [
|
|
160
|
+
const byId = new Map((base ?? []).map((catalog) => [
|
|
161
|
+
catalog.id,
|
|
162
|
+
{
|
|
163
|
+
id: catalog.id,
|
|
164
|
+
...(catalog.label === undefined ? {} : { label: catalog.label }),
|
|
165
|
+
...(catalog.schema === undefined ? {} : { schema: catalog.schema }),
|
|
166
|
+
entries: catalog.entries.map(cloneCatalogEntry),
|
|
167
|
+
},
|
|
168
|
+
]));
|
|
111
169
|
for (const catalog of overlay ?? []) {
|
|
112
170
|
const existing = byId.get(catalog.id);
|
|
171
|
+
// Overlay schema/label win when present; otherwise keep whatever the base carried.
|
|
172
|
+
const label = catalog.label ?? existing?.label;
|
|
173
|
+
const schema = catalog.schema ?? existing?.schema;
|
|
113
174
|
byId.set(catalog.id, {
|
|
114
175
|
id: catalog.id,
|
|
176
|
+
...(label === undefined ? {} : { label }),
|
|
177
|
+
...(schema === undefined ? {} : { schema }),
|
|
115
178
|
entries: existing === undefined ? catalog.entries.map(cloneCatalogEntry) : upsertCatalogEntries(existing.entries, catalog.entries),
|
|
116
179
|
});
|
|
117
180
|
}
|
|
@@ -125,6 +188,8 @@ export function normalizeEditorLayers(input) {
|
|
|
125
188
|
return createEmptyEditorDocument();
|
|
126
189
|
const resolved = typeof input === "function" ? input() : input;
|
|
127
190
|
const ui = cloneEditorUiDocument(resolved.ui);
|
|
191
|
+
const grids = migrateGrids(resolved.grids);
|
|
192
|
+
const directives = cloneDirectives(resolved.directives);
|
|
128
193
|
return {
|
|
129
194
|
version: 1,
|
|
130
195
|
markers: asArray(resolved.markers),
|
|
@@ -134,9 +199,12 @@ export function normalizeEditorLayers(input) {
|
|
|
134
199
|
prefabs: asArray(resolved.prefabs),
|
|
135
200
|
collections: asArray(resolved.collections),
|
|
136
201
|
catalogs: cloneCatalogs(resolved.catalogs),
|
|
137
|
-
...(
|
|
202
|
+
...(grids === undefined ? {} : { grids }),
|
|
138
203
|
...(resolved.terrain === undefined ? {} : { terrain: migrateTerrainSnapshot(resolved.terrain) }),
|
|
139
204
|
...(ui === undefined ? {} : { ui }),
|
|
205
|
+
...(directives === undefined ? {} : { directives }),
|
|
206
|
+
...(resolved.minimap === undefined ? {} : { minimap: cloneMinimapBake(resolved.minimap) }),
|
|
207
|
+
...(resolved.environment === undefined ? {} : { environment: cloneEnvironment(resolved.environment) }),
|
|
140
208
|
};
|
|
141
209
|
}
|
|
142
210
|
/**
|
|
@@ -186,6 +254,13 @@ export function mergeEditorDocuments(...docs) {
|
|
|
186
254
|
out.grids = mergedGrids;
|
|
187
255
|
if (doc.terrain !== undefined)
|
|
188
256
|
out.terrain = doc.terrain;
|
|
257
|
+
if (doc.minimap !== undefined)
|
|
258
|
+
out.minimap = cloneMinimapBake(doc.minimap);
|
|
259
|
+
if (doc.environment !== undefined)
|
|
260
|
+
out.environment = cloneEnvironment(doc.environment);
|
|
261
|
+
if (doc.directives !== undefined) {
|
|
262
|
+
out.directives = upsertById(out.directives ?? [], doc.directives);
|
|
263
|
+
}
|
|
189
264
|
if (doc.ui !== undefined) {
|
|
190
265
|
out.ui = {
|
|
191
266
|
panels: {
|
|
@@ -264,6 +339,8 @@ export function seedEditorCatalogs(doc, definitions) {
|
|
|
264
339
|
}
|
|
265
340
|
const catalog = {
|
|
266
341
|
id: definition.id,
|
|
342
|
+
...(existing?.label === undefined ? {} : { label: existing.label }),
|
|
343
|
+
...(existing?.schema === undefined ? {} : { schema: existing.schema }),
|
|
267
344
|
entries: [...byId.values()],
|
|
268
345
|
};
|
|
269
346
|
if (existingIndex >= 0)
|
|
@@ -273,12 +350,57 @@ export function seedEditorCatalogs(doc, definitions) {
|
|
|
273
350
|
}
|
|
274
351
|
return { ...doc, catalogs: next };
|
|
275
352
|
}
|
|
276
|
-
/**
|
|
353
|
+
/**
|
|
354
|
+
* True when an object carries a per-object `locked` flag (hierarchy lock toggle).
|
|
355
|
+
* Collection membership is checked separately by {@link isEditorObjectLocked}.
|
|
277
356
|
* @internal
|
|
278
357
|
*/
|
|
279
|
-
export function
|
|
358
|
+
export function isEditorObjectSelfLocked(doc, id) {
|
|
359
|
+
const marker = doc.markers.find((entry) => entry.id === id);
|
|
360
|
+
if (marker !== undefined)
|
|
361
|
+
return marker.locked === true;
|
|
362
|
+
const volume = doc.volumes.find((entry) => entry.id === id);
|
|
363
|
+
if (volume !== undefined)
|
|
364
|
+
return volume.locked === true;
|
|
365
|
+
const path = doc.paths.find((entry) => entry.id === id);
|
|
366
|
+
if (path !== undefined)
|
|
367
|
+
return path.locked === true;
|
|
368
|
+
const note = doc.annotations.find((entry) => entry.id === id);
|
|
369
|
+
return note?.locked === true;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* True when an object is a member of any locked collection (production group lock).
|
|
373
|
+
* @internal
|
|
374
|
+
*/
|
|
375
|
+
export function isEditorObjectCollectionLocked(doc, id) {
|
|
280
376
|
return doc.collections.some((collection) => collection.locked === true && collection.memberIds.includes(id));
|
|
281
377
|
}
|
|
378
|
+
/**
|
|
379
|
+
* True when an object id is locked for edit — either its own `locked` flag or membership in a
|
|
380
|
+
* locked collection. Blocks move/delete via session mutations.
|
|
381
|
+
* @internal
|
|
382
|
+
*/
|
|
383
|
+
export function isEditorObjectLocked(doc, id) {
|
|
384
|
+
return isEditorObjectSelfLocked(doc, id) || isEditorObjectCollectionLocked(doc, id);
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* True when an object carries `hidden: true` — omitted from editor viewport overlays and picking
|
|
388
|
+
* while remaining in the hierarchy and document.
|
|
389
|
+
* @internal
|
|
390
|
+
*/
|
|
391
|
+
export function isEditorObjectHidden(doc, id) {
|
|
392
|
+
const marker = doc.markers.find((entry) => entry.id === id);
|
|
393
|
+
if (marker !== undefined)
|
|
394
|
+
return marker.hidden === true;
|
|
395
|
+
const volume = doc.volumes.find((entry) => entry.id === id);
|
|
396
|
+
if (volume !== undefined)
|
|
397
|
+
return volume.hidden === true;
|
|
398
|
+
const path = doc.paths.find((entry) => entry.id === id);
|
|
399
|
+
if (path !== undefined)
|
|
400
|
+
return path.hidden === true;
|
|
401
|
+
const note = doc.annotations.find((entry) => entry.id === id);
|
|
402
|
+
return note?.hidden === true;
|
|
403
|
+
}
|
|
282
404
|
/**
|
|
283
405
|
* Extracts the selected ids into a prefab fragment centered on their own bounds centroid, so the
|
|
284
406
|
* same prefab reinserts consistently regardless of where in the scene (or which game) it lands.
|
|
@@ -312,6 +434,29 @@ export function createPrefabFragment(doc, ids) {
|
|
|
312
434
|
export function findEditorMarker(doc, id) {
|
|
313
435
|
return doc.markers.find((marker) => marker.id === id);
|
|
314
436
|
}
|
|
437
|
+
/** Looks up a marker by id and throws when it is absent — for scene contracts where a
|
|
438
|
+
* missing marker is an authoring error to surface loudly, not a runtime branch to handle.
|
|
439
|
+
* @internal
|
|
440
|
+
*/
|
|
441
|
+
export function requireEditorMarker(doc, id) {
|
|
442
|
+
const marker = findEditorMarker(doc, id);
|
|
443
|
+
if (marker === undefined) {
|
|
444
|
+
throw new Error(`editor document has no marker with id "${id}"`);
|
|
445
|
+
}
|
|
446
|
+
return marker;
|
|
447
|
+
}
|
|
448
|
+
/** A marker's world position as an `[x, y, z]` tuple — the shared shape gameplay tables read.
|
|
449
|
+
* @internal
|
|
450
|
+
*/
|
|
451
|
+
export function editorMarkerPosition(marker) {
|
|
452
|
+
return [marker.position.x, marker.position.y, marker.position.z];
|
|
453
|
+
}
|
|
454
|
+
/** A marker's ground position as an `[x, z]` tuple, dropping the vertical axis.
|
|
455
|
+
* @internal
|
|
456
|
+
*/
|
|
457
|
+
export function editorMarkerXZ(marker) {
|
|
458
|
+
return [marker.position.x, marker.position.z];
|
|
459
|
+
}
|
|
315
460
|
/** Looks up a volume by id in an editor document.
|
|
316
461
|
* @internal
|
|
317
462
|
*/
|
|
@@ -475,6 +620,10 @@ function decodeMarker(item, path, errors) {
|
|
|
475
620
|
marker.catalogId = item.catalogId;
|
|
476
621
|
if (typeof item.parentId === "string")
|
|
477
622
|
marker.parentId = item.parentId;
|
|
623
|
+
if (typeof item.locked === "boolean")
|
|
624
|
+
marker.locked = item.locked;
|
|
625
|
+
if (typeof item.hidden === "boolean")
|
|
626
|
+
marker.hidden = item.hidden;
|
|
478
627
|
const meta = decodeMeta(item.meta, `${path}.meta`, errors);
|
|
479
628
|
if (meta !== undefined)
|
|
480
629
|
marker.meta = meta;
|
|
@@ -512,6 +661,10 @@ function decodeVolume(item, path, errors) {
|
|
|
512
661
|
volume.label = item.label;
|
|
513
662
|
if (typeof item.parentId === "string")
|
|
514
663
|
volume.parentId = item.parentId;
|
|
664
|
+
if (typeof item.locked === "boolean")
|
|
665
|
+
volume.locked = item.locked;
|
|
666
|
+
if (typeof item.hidden === "boolean")
|
|
667
|
+
volume.hidden = item.hidden;
|
|
515
668
|
const meta = decodeMeta(item.meta, `${path}.meta`, errors);
|
|
516
669
|
if (meta !== undefined)
|
|
517
670
|
volume.meta = meta;
|
|
@@ -543,6 +696,10 @@ function decodePath(item, path, errors) {
|
|
|
543
696
|
decodedPath.label = item.label;
|
|
544
697
|
if (typeof item.parentId === "string")
|
|
545
698
|
decodedPath.parentId = item.parentId;
|
|
699
|
+
if (typeof item.locked === "boolean")
|
|
700
|
+
decodedPath.locked = item.locked;
|
|
701
|
+
if (typeof item.hidden === "boolean")
|
|
702
|
+
decodedPath.hidden = item.hidden;
|
|
546
703
|
const meta = decodeMeta(item.meta, `${path}.meta`, errors);
|
|
547
704
|
if (meta !== undefined)
|
|
548
705
|
decodedPath.meta = meta;
|
|
@@ -565,6 +722,10 @@ function decodeNote(item, path, errors) {
|
|
|
565
722
|
note.color = item.color;
|
|
566
723
|
if (typeof item.parentId === "string")
|
|
567
724
|
note.parentId = item.parentId;
|
|
725
|
+
if (typeof item.locked === "boolean")
|
|
726
|
+
note.locked = item.locked;
|
|
727
|
+
if (typeof item.hidden === "boolean")
|
|
728
|
+
note.hidden = item.hidden;
|
|
568
729
|
const meta = decodeMeta(item.meta, `${path}.meta`, errors);
|
|
569
730
|
if (meta !== undefined)
|
|
570
731
|
note.meta = meta;
|
|
@@ -645,6 +806,30 @@ function decodeCatalogEntry(item, path, errors) {
|
|
|
645
806
|
entry.meta = meta;
|
|
646
807
|
return entry;
|
|
647
808
|
}
|
|
809
|
+
/**
|
|
810
|
+
* Decodes a document-carried `ParamSchema` (`{ fields: ParamField[] }`) — modeled on
|
|
811
|
+
* `loadGameCatalogs`'s `decodeSchema`. Any field that is not a plain object with string `key` and
|
|
812
|
+
* `type` is dropped with a diagnostic instead of poisoning the whole schema; a non-array `fields`
|
|
813
|
+
* value drops the schema entirely (undefined). Structural but permissive: field-shape validation
|
|
814
|
+
* beyond key/type is left to `parseParams`, which defaults anything it cannot read.
|
|
815
|
+
*/
|
|
816
|
+
function decodeParamSchema(raw, path, errors) {
|
|
817
|
+
if (raw === undefined)
|
|
818
|
+
return undefined;
|
|
819
|
+
if (!isPlainObject(raw) || !Array.isArray(raw.fields)) {
|
|
820
|
+
errors.push({ path, message: "expected { fields: ParamField[] }" });
|
|
821
|
+
return undefined;
|
|
822
|
+
}
|
|
823
|
+
const fields = [];
|
|
824
|
+
raw.fields.forEach((field, index) => {
|
|
825
|
+
if (!isPlainObject(field) || typeof field.key !== "string" || typeof field.type !== "string") {
|
|
826
|
+
errors.push({ path: `${path}.fields[${index}]`, message: "expected a ParamField with string key + type" });
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
fields.push(field);
|
|
830
|
+
});
|
|
831
|
+
return { fields, ...(Array.isArray(raw.groups) ? { groups: raw.groups } : {}) };
|
|
832
|
+
}
|
|
648
833
|
function decodeCatalog(item, path, errors) {
|
|
649
834
|
if (!isPlainObject(item)) {
|
|
650
835
|
errors.push({ path, message: "expected an object" });
|
|
@@ -655,7 +840,203 @@ function decodeCatalog(item, path, errors) {
|
|
|
655
840
|
return null;
|
|
656
841
|
}
|
|
657
842
|
const entries = decodeArray(item.entries, `${path}.entries`, decodeCatalogEntry, errors);
|
|
658
|
-
|
|
843
|
+
const schema = decodeParamSchema(item.schema, `${path}.schema`, errors);
|
|
844
|
+
const catalog = { id: item.id, entries };
|
|
845
|
+
if (typeof item.label === "string")
|
|
846
|
+
catalog.label = item.label;
|
|
847
|
+
if (schema !== undefined)
|
|
848
|
+
catalog.schema = schema;
|
|
849
|
+
return catalog;
|
|
850
|
+
}
|
|
851
|
+
function decodeDirectiveArea(value, path, errors) {
|
|
852
|
+
if (value === undefined)
|
|
853
|
+
return undefined;
|
|
854
|
+
if (!isPlainObject(value)) {
|
|
855
|
+
errors.push({ path, message: "expected an object" });
|
|
856
|
+
return undefined;
|
|
857
|
+
}
|
|
858
|
+
const pair = (v, p) => {
|
|
859
|
+
if (!Array.isArray(v) || v.length !== 2 || typeof v[0] !== "number" || typeof v[1] !== "number") {
|
|
860
|
+
errors.push({ path: p, message: "expected [number, number]" });
|
|
861
|
+
return null;
|
|
862
|
+
}
|
|
863
|
+
return [v[0], v[1]];
|
|
864
|
+
};
|
|
865
|
+
const min = pair(value.min, `${path}.min`);
|
|
866
|
+
const max = pair(value.max, `${path}.max`);
|
|
867
|
+
if (min === null || max === null)
|
|
868
|
+
return undefined;
|
|
869
|
+
return { min, max };
|
|
870
|
+
}
|
|
871
|
+
function decodeSpecies(item, path, errors) {
|
|
872
|
+
if (!isPlainObject(item)) {
|
|
873
|
+
errors.push({ path, message: "expected an object" });
|
|
874
|
+
return null;
|
|
875
|
+
}
|
|
876
|
+
if (typeof item.id !== "string")
|
|
877
|
+
errors.push({ path: `${path}.id`, message: "expected a string" });
|
|
878
|
+
if (typeof item.cap !== "number")
|
|
879
|
+
errors.push({ path: `${path}.cap`, message: "expected a number" });
|
|
880
|
+
if (typeof item.id !== "string" || typeof item.cap !== "number")
|
|
881
|
+
return null;
|
|
882
|
+
const species = { id: item.id, cap: item.cap };
|
|
883
|
+
if (typeof item.weight === "number")
|
|
884
|
+
species.weight = item.weight;
|
|
885
|
+
return species;
|
|
886
|
+
}
|
|
887
|
+
function decodeDirective(item, path, errors) {
|
|
888
|
+
if (!isPlainObject(item)) {
|
|
889
|
+
errors.push({ path, message: "expected an object" });
|
|
890
|
+
return null;
|
|
891
|
+
}
|
|
892
|
+
if (typeof item.id !== "string")
|
|
893
|
+
errors.push({ path: `${path}.id`, message: "expected a string" });
|
|
894
|
+
const kindOk = item.kind === "scatter" || item.kind === "population";
|
|
895
|
+
if (!kindOk)
|
|
896
|
+
errors.push({ path: `${path}.kind`, message: "expected \"scatter\" | \"population\"" });
|
|
897
|
+
const area = decodeDirectiveArea(item.area, `${path}.area`, errors);
|
|
898
|
+
if (typeof item.id !== "string" || !kindOk)
|
|
899
|
+
return null;
|
|
900
|
+
const base = {
|
|
901
|
+
id: item.id,
|
|
902
|
+
...(typeof item.region === "string" ? { region: item.region } : {}),
|
|
903
|
+
...(area === undefined ? {} : { area }),
|
|
904
|
+
...(typeof item.seed === "string" || typeof item.seed === "number" ? { seed: item.seed } : {}),
|
|
905
|
+
...(isPlainObject(item.meta) ? { meta: item.meta } : {}),
|
|
906
|
+
};
|
|
907
|
+
if (item.kind === "scatter") {
|
|
908
|
+
if (typeof item.asset !== "string")
|
|
909
|
+
errors.push({ path: `${path}.asset`, message: "expected a string" });
|
|
910
|
+
if (typeof item.density !== "number")
|
|
911
|
+
errors.push({ path: `${path}.density`, message: "expected a number" });
|
|
912
|
+
if (typeof item.asset !== "string" || typeof item.density !== "number")
|
|
913
|
+
return null;
|
|
914
|
+
const scatter = { ...base, kind: "scatter", asset: item.asset, density: item.density };
|
|
915
|
+
for (const key of ["minSpacing", "jitter", "minScale", "maxScale", "minYaw", "maxYaw"]) {
|
|
916
|
+
if (typeof item[key] === "number")
|
|
917
|
+
scatter[key] = item[key];
|
|
918
|
+
}
|
|
919
|
+
return scatter;
|
|
920
|
+
}
|
|
921
|
+
const species = decodeArray(item.species, `${path}.species`, decodeSpecies, errors);
|
|
922
|
+
return { ...base, kind: "population", species };
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* Validates a baked minimap arriving from outside this process: `background` must be a
|
|
926
|
+
* `data:image/…` URI and `bounds` must be four finite numbers, so a malformed bake is rejected with a
|
|
927
|
+
* path-specific diagnostic instead of feeding a broken `<image href>` to the runtime minimap.
|
|
928
|
+
*/
|
|
929
|
+
function decodeMinimapBake(value, path, errors) {
|
|
930
|
+
if (!isPlainObject(value)) {
|
|
931
|
+
errors.push({ path, message: "expected an object" });
|
|
932
|
+
return undefined;
|
|
933
|
+
}
|
|
934
|
+
const backgroundOk = typeof value.background === "string" && value.background.startsWith("data:image/");
|
|
935
|
+
if (!backgroundOk) {
|
|
936
|
+
errors.push({ path: `${path}.background`, message: "expected a data:image/… string" });
|
|
937
|
+
}
|
|
938
|
+
const bounds = value.bounds;
|
|
939
|
+
const boundsOk = isPlainObject(bounds) &&
|
|
940
|
+
["minX", "minZ", "maxX", "maxZ"].every((key) => typeof bounds[key] === "number" && Number.isFinite(bounds[key]));
|
|
941
|
+
if (!boundsOk) {
|
|
942
|
+
errors.push({ path: `${path}.bounds`, message: "expected {minX,minZ,maxX,maxZ} finite numbers" });
|
|
943
|
+
}
|
|
944
|
+
if (!backgroundOk || !boundsOk)
|
|
945
|
+
return undefined;
|
|
946
|
+
const b = bounds;
|
|
947
|
+
return {
|
|
948
|
+
background: value.background,
|
|
949
|
+
bounds: { minX: b.minX, minZ: b.minZ, maxX: b.maxX, maxZ: b.maxZ },
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
const SKY_PRESETS = new Set(["day", "dusk", "night"]);
|
|
953
|
+
/**
|
|
954
|
+
* Validates scene-document environment/lighting. Unknown presets, non-finite intensities, and
|
|
955
|
+
* malformed fog objects produce path-specific diagnostics; empty valid objects are accepted so a
|
|
956
|
+
* game can seed `environment: {}` as "use engine defaults, ready to author".
|
|
957
|
+
*/
|
|
958
|
+
function decodeEnvironment(value, path, errors) {
|
|
959
|
+
if (!isPlainObject(value)) {
|
|
960
|
+
errors.push({ path, message: "expected an object" });
|
|
961
|
+
return undefined;
|
|
962
|
+
}
|
|
963
|
+
let failed = false;
|
|
964
|
+
const env = {};
|
|
965
|
+
if (value.preset !== undefined) {
|
|
966
|
+
if (typeof value.preset === "string" && SKY_PRESETS.has(value.preset)) {
|
|
967
|
+
env.preset = value.preset;
|
|
968
|
+
}
|
|
969
|
+
else {
|
|
970
|
+
errors.push({ path: `${path}.preset`, message: 'expected "day" | "dusk" | "night"' });
|
|
971
|
+
failed = true;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
if (value.timeOfDay !== undefined) {
|
|
975
|
+
if (typeof value.timeOfDay === "boolean")
|
|
976
|
+
env.timeOfDay = value.timeOfDay;
|
|
977
|
+
else {
|
|
978
|
+
errors.push({ path: `${path}.timeOfDay`, message: "expected a boolean" });
|
|
979
|
+
failed = true;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
if (value.horizonColor !== undefined) {
|
|
983
|
+
if (typeof value.horizonColor === "string")
|
|
984
|
+
env.horizonColor = value.horizonColor;
|
|
985
|
+
else {
|
|
986
|
+
errors.push({ path: `${path}.horizonColor`, message: "expected a string" });
|
|
987
|
+
failed = true;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
if (value.zenithColor !== undefined) {
|
|
991
|
+
if (typeof value.zenithColor === "string")
|
|
992
|
+
env.zenithColor = value.zenithColor;
|
|
993
|
+
else {
|
|
994
|
+
errors.push({ path: `${path}.zenithColor`, message: "expected a string" });
|
|
995
|
+
failed = true;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
for (const key of ["sunIntensity", "ambientIntensity"]) {
|
|
999
|
+
if (value[key] === undefined)
|
|
1000
|
+
continue;
|
|
1001
|
+
if (typeof value[key] === "number" && Number.isFinite(value[key]))
|
|
1002
|
+
env[key] = value[key];
|
|
1003
|
+
else {
|
|
1004
|
+
errors.push({ path: `${path}.${key}`, message: "expected a finite number" });
|
|
1005
|
+
failed = true;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
if (value.fog !== undefined) {
|
|
1009
|
+
if (!isPlainObject(value.fog)) {
|
|
1010
|
+
errors.push({ path: `${path}.fog`, message: "expected an object" });
|
|
1011
|
+
failed = true;
|
|
1012
|
+
}
|
|
1013
|
+
else {
|
|
1014
|
+
const fog = {};
|
|
1015
|
+
if (value.fog.color !== undefined) {
|
|
1016
|
+
if (typeof value.fog.color === "string")
|
|
1017
|
+
fog.color = value.fog.color;
|
|
1018
|
+
else {
|
|
1019
|
+
errors.push({ path: `${path}.fog.color`, message: "expected a string" });
|
|
1020
|
+
failed = true;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
for (const key of ["near", "far"]) {
|
|
1024
|
+
if (value.fog[key] === undefined)
|
|
1025
|
+
continue;
|
|
1026
|
+
if (typeof value.fog[key] === "number" && Number.isFinite(value.fog[key]))
|
|
1027
|
+
fog[key] = value.fog[key];
|
|
1028
|
+
else {
|
|
1029
|
+
errors.push({ path: `${path}.fog.${key}`, message: "expected a finite number" });
|
|
1030
|
+
failed = true;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
if (Object.keys(fog).length > 0)
|
|
1034
|
+
env.fog = fog;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
if (failed)
|
|
1038
|
+
return undefined;
|
|
1039
|
+
return env;
|
|
659
1040
|
}
|
|
660
1041
|
const GRID_AXES = new Set(["xz", "xy"]);
|
|
661
1042
|
function decodeGridPaletteEntry(item, path, errors) {
|
|
@@ -766,9 +1147,33 @@ export function decodeEditorDocument(raw) {
|
|
|
766
1147
|
const collections = decodeArray(raw.collections, "$.collections", decodeCollection, errors);
|
|
767
1148
|
const catalogs = decodeArray(raw.catalogs, "$.catalogs", decodeCatalog, errors);
|
|
768
1149
|
const grids = raw.grids === undefined ? undefined : decodeArray(raw.grids, "$.grids", decodeGridLayer, errors);
|
|
1150
|
+
const directives = raw.directives === undefined ? undefined : decodeArray(raw.directives, "$.directives", decodeDirective, errors);
|
|
769
1151
|
if (raw.terrain !== undefined && !isPlainObject(raw.terrain)) {
|
|
770
1152
|
errors.push({ path: "$.terrain", message: "expected an object" });
|
|
771
1153
|
}
|
|
1154
|
+
const minimap = raw.minimap === undefined ? undefined : decodeMinimapBake(raw.minimap, "$.minimap", errors);
|
|
1155
|
+
const environment = raw.environment === undefined ? undefined : decodeEnvironment(raw.environment, "$.environment", errors);
|
|
1156
|
+
// Placeable object ids form one document-global namespace (selection, parenting, and removal all
|
|
1157
|
+
// treat them that way), so a document that reuses an id — even across two different collections —
|
|
1158
|
+
// is malformed and rejected here with the offending path, rather than silently loading a scene
|
|
1159
|
+
// where one id shadows another. Combine paths (merge/duplicate/overlay) re-id instead; this is the
|
|
1160
|
+
// single-document decode boundary, where a collision is an authoring error, not something to paper over.
|
|
1161
|
+
const seenIds = new Set();
|
|
1162
|
+
for (const [collection, list] of [
|
|
1163
|
+
["markers", markers],
|
|
1164
|
+
["volumes", volumes],
|
|
1165
|
+
["paths", paths],
|
|
1166
|
+
["annotations", annotations],
|
|
1167
|
+
]) {
|
|
1168
|
+
list.forEach((item, index) => {
|
|
1169
|
+
if (seenIds.has(item.id)) {
|
|
1170
|
+
errors.push({ path: `$.${collection}[${index}].id`, message: `duplicate id "${item.id}"` });
|
|
1171
|
+
}
|
|
1172
|
+
else {
|
|
1173
|
+
seenIds.add(item.id);
|
|
1174
|
+
}
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
772
1177
|
if (errors.length > 0)
|
|
773
1178
|
return { ok: false, errors };
|
|
774
1179
|
const terrain = raw.terrain === undefined ? undefined : migrateTerrainSnapshot(raw.terrain);
|
|
@@ -787,6 +1192,9 @@ export function decodeEditorDocument(raw) {
|
|
|
787
1192
|
...(grids === undefined ? {} : { grids }),
|
|
788
1193
|
...(terrain === undefined ? {} : { terrain }),
|
|
789
1194
|
...(ui === undefined ? {} : { ui }),
|
|
1195
|
+
...(directives === undefined ? {} : { directives }),
|
|
1196
|
+
...(minimap === undefined ? {} : { minimap }),
|
|
1197
|
+
...(environment === undefined ? {} : { environment }),
|
|
790
1198
|
},
|
|
791
1199
|
};
|
|
792
1200
|
}
|
|
@@ -814,6 +1222,8 @@ function upsertById(base, overlay) {
|
|
|
814
1222
|
*/
|
|
815
1223
|
export function applyEditorDocumentOverlay(base, overlay) {
|
|
816
1224
|
const terrain = overlay.terrain ?? base.terrain;
|
|
1225
|
+
const minimap = overlay.minimap ?? base.minimap;
|
|
1226
|
+
const environment = overlay.environment ?? base.environment;
|
|
817
1227
|
const ui = overlay.ui === undefined
|
|
818
1228
|
? cloneEditorUiDocument(base.ui)
|
|
819
1229
|
: {
|
|
@@ -822,6 +1232,7 @@ export function applyEditorDocumentOverlay(base, overlay) {
|
|
|
822
1232
|
...Object.fromEntries(Object.entries(overlay.ui.panels).map(([id, panel]) => [id, { ...panel }])),
|
|
823
1233
|
},
|
|
824
1234
|
};
|
|
1235
|
+
const grids = upsertGrids(base.grids, overlay.grids);
|
|
825
1236
|
return {
|
|
826
1237
|
version: 1,
|
|
827
1238
|
markers: upsertById(base.markers, overlay.markers),
|
|
@@ -831,9 +1242,14 @@ export function applyEditorDocumentOverlay(base, overlay) {
|
|
|
831
1242
|
prefabs: upsertById(base.prefabs, overlay.prefabs),
|
|
832
1243
|
collections: upsertById(base.collections, overlay.collections),
|
|
833
1244
|
catalogs: upsertCatalogs(base.catalogs, overlay.catalogs),
|
|
834
|
-
...(
|
|
1245
|
+
...(grids === undefined ? {} : { grids }),
|
|
835
1246
|
...(terrain === undefined ? {} : { terrain }),
|
|
836
1247
|
...(ui === undefined ? {} : { ui }),
|
|
1248
|
+
...(minimap === undefined ? {} : { minimap: cloneMinimapBake(minimap) }),
|
|
1249
|
+
...(environment === undefined ? {} : { environment: cloneEnvironment(environment) }),
|
|
1250
|
+
...(base.directives === undefined && overlay.directives === undefined
|
|
1251
|
+
? {}
|
|
1252
|
+
: { directives: upsertById(base.directives ?? [], overlay.directives ?? []) }),
|
|
837
1253
|
};
|
|
838
1254
|
}
|
|
839
1255
|
/** Computes the world-space min/max bounds spanning every object in a document, or null if empty.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { GameContextEntityEntry } from "../runtime/gameContext.js";
|
|
2
|
+
import { type ParamSchema } from "../scene/sceneKinds.js";
|
|
3
|
+
import type { EditorCatalogData, EditorCatalogDefinition } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Well-known catalog id for authored entity definitions. A `mob`/`boss` marker's `catalogId`
|
|
6
|
+
* references an entry (row) in this catalog, and {@link entityEntryFromCatalog} turns that row into
|
|
7
|
+
* the runtime {@link GameContextEntityEntry} a spawn consumes — so an entity's stats and speed are
|
|
8
|
+
* authored in the editor Data tab, not hardcoded in game TS.
|
|
9
|
+
*/
|
|
10
|
+
export declare const ENTITY_CATALOG_ID = "entities";
|
|
11
|
+
/**
|
|
12
|
+
* The default {@link ParamSchema} for an authored entity definition — role, health, movement speed,
|
|
13
|
+
* and visual scale. Drives the editor Data-tab inspector for the `entities` catalog and is the shape
|
|
14
|
+
* {@link entityEntryFromCatalog} reads. A game exports it as an `editorCatalogs` schema; the scaffold
|
|
15
|
+
* ships a starter one so the tab is never empty.
|
|
16
|
+
* @capability editor-entities Author entity stats/movement per catalog row instead of hardcoding TS.
|
|
17
|
+
*/
|
|
18
|
+
export declare const entityDefinitionSchema: ParamSchema;
|
|
19
|
+
/** Minimal document shape {@link entityEntryFromCatalog} reads — any `EditorDocument` satisfies it. */
|
|
20
|
+
export interface EntityCatalogDocumentLike {
|
|
21
|
+
catalogs: readonly EditorCatalogData[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Turns an authored entity row into the runtime {@link GameContextEntityEntry} a spawn consumes:
|
|
25
|
+
* looks up `entityId` in the document's {@link ENTITY_CATALOG_ID} catalog (falling back to the
|
|
26
|
+
* game-exported `definitions` defaults), parses it against {@link entityDefinitionSchema}, and maps
|
|
27
|
+
* the fields onto stats/movement/role. Returns `null` when no row defines the entity, so the default
|
|
28
|
+
* `content.ts` path can chain other resolvers. The single seam that makes "place a mob, tune its
|
|
29
|
+
* stats/speed in the editor, save — it spawns with those stats" work with zero game code.
|
|
30
|
+
* @capability editor-entities Consume authored entity definitions from the scene document at runtime.
|
|
31
|
+
*/
|
|
32
|
+
export declare function entityEntryFromCatalog(document: EntityCatalogDocumentLike, entityId: string, definitions?: readonly EditorCatalogDefinition[]): GameContextEntityEntry | null;
|
|
33
|
+
/**
|
|
34
|
+
* Merges game-exported catalog definitions with editor-authored ones carried on the scene document.
|
|
35
|
+
* A document catalog that carries its own `schema` and has no matching game definition is synthesized
|
|
36
|
+
* into a full {@link EditorCatalogDefinition} (label falls back to the id) so the Data panel, the
|
|
37
|
+
* `list_catalogs` RPC, and the count all see it. A game definition always wins on id collision — its
|
|
38
|
+
* schema lives in code and is authoritative. Document catalogs without a schema (plain value bags for
|
|
39
|
+
* a game-declared catalog) are ignored here; they are addressed through their game definition.
|
|
40
|
+
* @capability editor-catalogs Merge game-exported and editor-authored catalogs into one addressable set.
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveCatalogDefinitions(document: EntityCatalogDocumentLike, gameDefinitions: readonly EditorCatalogDefinition[]): EditorCatalogDefinition[];
|