@jgengine/core 0.19.0 → 0.22.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 +364 -0
- package/dist/ai/decisionGraph.d.ts +2 -0
- package/dist/ai/decisionGraph.js +6 -0
- package/dist/ai/flock.d.ts +1 -1
- package/dist/ai/flock.js +7 -4
- package/dist/anim/animGraph.d.ts +33 -2
- package/dist/anim/animGraph.js +200 -12
- package/dist/anim/locomotionGraph.d.ts +11 -3
- package/dist/anim/locomotionGraph.js +25 -15
- package/dist/audio/audioFalloff.d.ts +4 -0
- package/dist/audio/voiceAllocator.d.ts +46 -0
- package/dist/audio/voiceAllocator.js +76 -0
- package/dist/combat/abilityKit.d.ts +19 -0
- package/dist/combat/abilityKit.js +40 -4
- package/dist/combat/death.d.ts +4 -3
- package/dist/combat/death.js +3 -3
- package/dist/combat/magazine.d.ts +9 -0
- package/dist/combat/magazine.js +24 -2
- package/dist/combat/projectiles.d.ts +94 -2
- package/dist/combat/projectiles.js +282 -61
- package/dist/combat/regenShield.d.ts +14 -0
- package/dist/combat/regenShield.js +23 -7
- package/dist/crafting/recipeCatalog.d.ts +16 -0
- package/dist/crafting/recipeCatalog.js +55 -0
- package/dist/economy/auctionBook.d.ts +1 -1
- package/dist/economy/auctionBook.js +11 -1
- package/dist/economy/listingBook.d.ts +1 -1
- package/dist/economy/listingBook.js +11 -1
- package/dist/economy/resourceLedger.js +3 -3
- package/dist/economy/shopStock.js +7 -6
- package/dist/economy/techTree.js +7 -6
- package/dist/economy/unlockPoints.js +3 -6
- package/dist/editor/commandApply.js +89 -16
- package/dist/editor/commands.d.ts +38 -1
- package/dist/editor/commands.js +320 -3
- package/dist/editor/creatorStorage.d.ts +59 -0
- package/dist/editor/creatorStorage.js +164 -0
- package/dist/editor/document.d.ts +4 -0
- package/dist/editor/document.js +116 -17
- package/dist/editor/index.d.ts +3 -0
- package/dist/editor/index.js +1 -0
- package/dist/editor/liveSync.d.ts +4 -1
- package/dist/editor/liveSync.js +26 -10
- package/dist/editor/materialAuthoring.d.ts +14 -0
- package/dist/editor/materialAuthoring.js +38 -0
- package/dist/editor/modelNodeVisibility.d.ts +11 -0
- package/dist/editor/modelNodeVisibility.js +29 -0
- package/dist/editor/movementCatalog.d.ts +90 -0
- package/dist/editor/movementCatalog.js +144 -0
- package/dist/editor/simulation.d.ts +60 -0
- package/dist/editor/simulation.js +202 -0
- package/dist/editor/staticPrefab.d.ts +17 -0
- package/dist/editor/staticPrefab.js +51 -0
- package/dist/editor/terrainMaterials.d.ts +10 -0
- package/dist/editor/terrainMaterials.js +13 -0
- package/dist/editor/types.d.ts +11 -0
- package/dist/game/cameraConfig.d.ts +3 -1
- package/dist/game/clipRoles.d.ts +2 -1
- package/dist/game/clipRoles.js +7 -3
- package/dist/game/contentValidation.d.ts +69 -0
- package/dist/game/contentValidation.js +118 -0
- package/dist/game/controlGate.d.ts +8 -0
- package/dist/game/controlGate.js +43 -6
- package/dist/game/defineGame.d.ts +32 -3
- package/dist/game/dialogueGraph.d.ts +20 -0
- package/dist/game/dialogueGraph.js +107 -0
- package/dist/game/events.d.ts +5 -0
- package/dist/game/keyValueStore.d.ts +17 -4
- package/dist/game/keyValueStore.js +35 -23
- package/dist/game/lootModifiers.d.ts +17 -0
- package/dist/game/lootModifiers.js +26 -0
- package/dist/game/narrativeAuthoring.d.ts +67 -0
- package/dist/game/narrativeAuthoring.js +122 -0
- package/dist/game/playableGame.d.ts +52 -20
- package/dist/game/quest.d.ts +15 -0
- package/dist/game/quest.js +209 -63
- package/dist/game/questCatalog.d.ts +50 -0
- package/dist/game/questCatalog.js +205 -0
- package/dist/game/saveStore.d.ts +6 -5
- package/dist/game/saveStore.js +57 -57
- package/dist/game/systemRuntime.d.ts +2 -2
- package/dist/game/systemRuntime.js +84 -33
- package/dist/gameplay.d.ts +3 -0
- package/dist/gameplay.js +3 -0
- package/dist/interaction/promptRegistry.d.ts +5 -4
- package/dist/interaction/promptRegistry.js +8 -2
- package/dist/inventory/inventoryModel.js +9 -6
- package/dist/inventory/shapedGrid.d.ts +48 -9
- package/dist/inventory/shapedGrid.js +69 -10
- package/dist/item/durability.js +4 -0
- package/dist/item/itemInstanceRegistry.d.ts +32 -1
- package/dist/item/itemInstanceRegistry.js +111 -7
- package/dist/item/itemgen.d.ts +1 -1
- package/dist/item/itemgen.js +1 -1
- package/dist/item/modularItem.js +7 -1
- package/dist/material/appearanceSignals.d.ts +53 -0
- package/dist/material/appearanceSignals.js +47 -0
- package/dist/material/hairCards.d.ts +47 -0
- package/dist/material/hairCards.js +136 -0
- package/dist/material/materialAsset.d.ts +162 -0
- package/dist/material/materialAsset.js +382 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +348 -1
- package/dist/movement/characterController.d.ts +5 -2
- package/dist/movement/characterController.js +50 -23
- package/dist/movement/freeFlight.d.ts +2 -2
- package/dist/movement/freeFlight.js +3 -3
- package/dist/movement/movementModel.d.ts +24 -1
- package/dist/movement/movementModel.js +74 -7
- package/dist/movement/playerMovement.d.ts +28 -1
- package/dist/movement/playerMovement.js +425 -102
- package/dist/movement/poseState.d.ts +2 -0
- package/dist/movement/poseState.js +29 -1
- package/dist/movement/solidObstacles.d.ts +3 -1
- package/dist/movement/solidObstacles.js +8 -5
- package/dist/movement/terrainGrade.d.ts +13 -0
- package/dist/movement/terrainGrade.js +33 -0
- package/dist/movement/voxelController.d.ts +2 -2
- package/dist/movement/voxelController.js +3 -3
- package/dist/nav/navGrid.d.ts +19 -1
- package/dist/nav/navGrid.js +26 -10
- package/dist/nav/solidRoute.d.ts +50 -0
- package/dist/nav/solidRoute.js +144 -0
- package/dist/physics/ballisticSweep.d.ts +10 -3
- package/dist/physics/ballisticSweep.js +53 -29
- package/dist/physics/forceVolume.d.ts +41 -0
- package/dist/physics/forceVolume.js +88 -0
- package/dist/physics/kinematicVehicle.d.ts +8 -3
- package/dist/physics/kinematicVehicle.js +2 -2
- package/dist/physics/physicsWorldBackend.js +19 -3
- package/dist/physics/spatialGrid.d.ts +1 -1
- package/dist/physics/spatialGrid.js +8 -9
- package/dist/physics/vehicleDynamics.d.ts +3 -2
- package/dist/physics/vehicleDynamics.js +2 -2
- package/dist/physics/vehicleObstacles.d.ts +3 -2
- package/dist/physics/vehicleObstacles.js +29 -5
- package/dist/runtime/cameraDirector.d.ts +39 -2
- package/dist/runtime/cameraDirector.js +162 -0
- package/dist/runtime/commandDispatch.d.ts +14 -0
- package/dist/runtime/commandDispatch.js +19 -0
- package/dist/runtime/context/combat.d.ts +11 -4
- package/dist/runtime/context/combat.js +178 -11
- package/dist/runtime/context/deathLoot.d.ts +9 -8
- package/dist/runtime/context/deathLoot.js +16 -16
- package/dist/runtime/context/scene.d.ts +2 -0
- package/dist/runtime/context/scene.js +13 -21
- package/dist/runtime/descriptors/baseline.js +34 -3
- package/dist/runtime/descriptors/features.d.ts +2 -1
- package/dist/runtime/descriptors/features.js +18 -0
- package/dist/runtime/gameContext.js +79 -13
- package/dist/runtime/gameContextTypes.d.ts +14 -0
- package/dist/runtime/headlessRunner.d.ts +3 -1
- package/dist/runtime/headlessRunner.js +11 -11
- package/dist/runtime/hostedGameRunner.d.ts +24 -1
- package/dist/runtime/hostedGameRunner.js +156 -10
- package/dist/runtime/hostedWorldSession.d.ts +17 -5
- package/dist/runtime/hostedWorldSession.js +154 -16
- package/dist/runtime/hostedWorldStore.d.ts +2 -2
- package/dist/runtime/inputRecorder.d.ts +1 -1
- package/dist/runtime/inputRecorder.js +21 -1
- package/dist/runtime/inputSnapshot.d.ts +16 -4
- package/dist/runtime/inputSnapshot.js +31 -3
- package/dist/runtime/runtimeSave.d.ts +10 -6
- package/dist/runtime/runtimeSave.js +11 -5
- package/dist/runtime/simContext.d.ts +8 -3
- package/dist/runtime/simContext.js +7 -3
- package/dist/runtime/simSnapshot.js +2 -0
- package/dist/runtime/worldReplication.d.ts +2 -0
- package/dist/runtime/worldReplication.js +1 -1
- package/dist/scene/assetCatalog.d.ts +2 -0
- package/dist/scene/behaviorRuntime.d.ts +26 -5
- package/dist/scene/behaviorRuntime.js +263 -16
- package/dist/scene/behaviors.d.ts +30 -1
- package/dist/scene/behaviors.js +29 -0
- package/dist/scene/entityStore.d.ts +14 -3
- package/dist/scene/entityStore.js +45 -9
- package/dist/scene/mount.d.ts +9 -0
- package/dist/scene/mount.js +35 -0
- package/dist/scene/possession.d.ts +2 -0
- package/dist/scene/possession.js +21 -1
- package/dist/scene/sceneRaycast.d.ts +7 -0
- package/dist/scene/sceneRaycast.js +55 -0
- package/dist/scene/spatial.d.ts +20 -1
- package/dist/scene/spatial.js +94 -2
- package/dist/scene/vehicleSeat.d.ts +36 -2
- package/dist/scene/vehicleSeat.js +89 -2
- package/dist/stats/accumulatorMeter.d.ts +10 -0
- package/dist/stats/accumulatorMeter.js +6 -0
- package/dist/stats/eventMeter.d.ts +9 -2
- package/dist/stats/eventMeter.js +5 -2
- package/dist/store/defineStore.d.ts +3 -1
- package/dist/store/defineStore.js +3 -1
- package/dist/store/observableKeyedStore.d.ts +1 -1
- package/dist/store/observableKeyedStore.js +8 -6
- package/dist/time/simClock.d.ts +6 -2
- package/dist/time/simClock.js +5 -0
- package/dist/vfx/particleDirector.d.ts +58 -6
- package/dist/vfx/particleDirector.js +113 -22
- package/dist/vfx/particles.d.ts +68 -5
- package/dist/vfx/particles.js +284 -32
- package/dist/visibility/assetStreaming.d.ts +21 -3
- package/dist/visibility/assetStreaming.js +108 -24
- package/dist/visibility/settings.d.ts +8 -2
- package/dist/visibility/settings.js +3 -0
- package/dist/visibility/visibilitySystem.js +1 -0
- package/dist/work/expedition.d.ts +110 -0
- package/dist/work/expedition.js +195 -0
- package/dist/work/staffedStation.d.ts +29 -0
- package/dist/work/staffedStation.js +27 -0
- package/dist/world/authoredAnimation.d.ts +38 -0
- package/dist/world/authoredAnimation.js +99 -0
- package/dist/world/authoredObjects.d.ts +24 -4
- package/dist/world/authoredObjects.js +43 -16
- package/dist/world/authoredSimulation.d.ts +74 -0
- package/dist/world/authoredSimulation.js +302 -0
- package/dist/world/authoredWeather.d.ts +120 -0
- package/dist/world/authoredWeather.js +314 -0
- package/dist/world/buildingKit.d.ts +8 -3
- package/dist/world/buildingKit.js +21 -8
- package/dist/world/environmentForces.d.ts +21 -0
- package/dist/world/environmentForces.js +42 -0
- package/dist/world/features.d.ts +3 -3
- package/dist/world/footprintGrid.d.ts +55 -0
- package/dist/world/footprintGrid.js +201 -0
- package/dist/world/grassKind.d.ts +10 -0
- package/dist/world/grassKind.js +22 -0
- package/dist/world/pathTerrain.d.ts +1 -1
- package/dist/world/terraform.d.ts +2 -2
- package/dist/world/terraform.js +46 -18
- package/dist/world/weather.d.ts +17 -0
- package/dist/world/weather.js +116 -20
- package/dist/world/worldSolids.d.ts +11 -0
- package/dist/world/worldSolids.js +112 -1
- package/dist/world.d.ts +5 -5
- package/dist/world.js +4 -4
- package/package.json +1 -1
- package/skills/ce-handoff/SKILL.md +1 -1
- package/skills/game-design/SKILL.md +2 -0
- package/skills/jgengine/SKILL.md +23 -2
- package/skills/jgengine/api.md +28 -17
- package/skills/jgengine/recipes/minimal-game.md +7 -2
- package/skills/jgengine-combat/SKILL.md +8 -2
- package/skills/jgengine-combat/api.md +22 -10
- package/skills/jgengine-combat/recipes/portable-runtime-state.md +60 -6
- package/skills/jgengine-combat/recipes/portable-weapon-plumbing.md +37 -3
- package/skills/jgengine-combat/reference.md +24 -2
- package/skills/jgengine-gameplay/SKILL.md +63 -0
- package/skills/jgengine-gameplay/api.md +131 -37
- package/skills/jgengine-gameplay/capabilities.md +68 -0
- package/skills/jgengine-gameplay/recipes/device-save-status.md +46 -0
- package/skills/jgengine-gameplay/recipes/expeditions.md +108 -0
- package/skills/jgengine-gameplay/recipes/narrative-authoring.md +76 -0
- package/skills/jgengine-gameplay/recipes/paged-item-storage.md +101 -0
- package/skills/jgengine-gameplay/recipes/quest-authoring.md +63 -0
- package/skills/jgengine-gameplay/recipes/shaped-inventory.md +50 -0
- package/skills/jgengine-gameplay/reference-content-validation.md +74 -0
- package/skills/jgengine-gameplay/reference-systems.md +119 -0
- package/skills/jgengine-world/SKILL.md +12 -3
- package/skills/jgengine-world/api.md +201 -58
- package/skills/jgengine-world/capabilities.md +116 -3
- package/skills/jgengine-world/recipes/character-animation.md +31 -8
- package/skills/jgengine-world/recipes/controls.md +4 -0
- package/skills/jgengine-world/recipes/npc-senses.md +12 -0
- package/skills/jgengine-world/recipes/vehicle-feel.md +3 -1
- package/skills/jgengine-world/reference.md +167 -5
- package/skills/jgengine-world/visibility.md +28 -1
package/dist/editor/document.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { decodeEditorSimulation } from "./simulation.js";
|
|
2
|
+
import { parseMaterialAssignments, validateMaterialAsset, validateMaterialAssignments } from "../material/materialAsset.js";
|
|
3
|
+
import { authoredHiddenNodes } from "./modelNodeVisibility.js";
|
|
4
|
+
import { parseStaticPrefabBake } from "./staticPrefab.js";
|
|
1
5
|
import { cloneEditorUiDocument, decodeEditorUiDocument } from "../ui/hudDocument.js";
|
|
2
6
|
import { migrateTerrainSnapshot } from "../world/terraform.js";
|
|
3
7
|
import { cloneGridLayer, migrateGridLayer, } from "./grid.js";
|
|
@@ -36,7 +40,7 @@ function cloneDirective(directive) {
|
|
|
36
40
|
...(directive.region === undefined ? {} : { region: directive.region }),
|
|
37
41
|
...(area === undefined ? {} : { area }),
|
|
38
42
|
...(directive.seed === undefined ? {} : { seed: directive.seed }),
|
|
39
|
-
...(directive.meta === undefined ? {} : { meta:
|
|
43
|
+
...(directive.meta === undefined ? {} : { meta: structuredClone(directive.meta) }),
|
|
40
44
|
};
|
|
41
45
|
if (directive.kind === "scatter") {
|
|
42
46
|
return { ...base, kind: "scatter", asset: directive.asset, density: directive.density,
|
|
@@ -68,6 +72,8 @@ function upsertGrids(base, overlay) {
|
|
|
68
72
|
/** Spread-ready non-placeable fields preserved across document rebuilds. @internal */
|
|
69
73
|
export function editorDocumentExtras(doc) {
|
|
70
74
|
return {
|
|
75
|
+
...(doc.simulation === undefined ? {} : { simulation: structuredClone(doc.simulation) }),
|
|
76
|
+
...(doc.materialAssets === undefined ? {} : { materialAssets: doc.materialAssets }),
|
|
71
77
|
prefabs: doc.prefabs,
|
|
72
78
|
collections: doc.collections,
|
|
73
79
|
catalogs: doc.catalogs,
|
|
@@ -99,14 +105,14 @@ function cloneCatalogEntry(entry) {
|
|
|
99
105
|
return {
|
|
100
106
|
id: entry.id,
|
|
101
107
|
...(entry.label === undefined ? {} : { label: entry.label }),
|
|
102
|
-
...(entry.meta === undefined ? {} : { meta:
|
|
108
|
+
...(entry.meta === undefined ? {} : { meta: structuredClone(entry.meta) }),
|
|
103
109
|
};
|
|
104
110
|
}
|
|
105
111
|
function cloneCatalogs(catalogs) {
|
|
106
112
|
return (catalogs ?? []).map((catalog) => ({
|
|
107
113
|
id: catalog.id,
|
|
108
114
|
...(catalog.label === undefined ? {} : { label: catalog.label }),
|
|
109
|
-
...(catalog.schema === undefined ? {} : { schema: catalog.schema }),
|
|
115
|
+
...(catalog.schema === undefined ? {} : { schema: structuredClone(catalog.schema) }),
|
|
110
116
|
entries: catalog.entries.map(cloneCatalogEntry),
|
|
111
117
|
}));
|
|
112
118
|
}
|
|
@@ -121,38 +127,36 @@ export function cloneEditorDocument(doc) {
|
|
|
121
127
|
const directives = cloneDirectives(doc.directives);
|
|
122
128
|
return {
|
|
123
129
|
version: 1,
|
|
130
|
+
...(doc.simulation === undefined ? {} : { simulation: structuredClone(doc.simulation) }),
|
|
124
131
|
markers: doc.markers.map((marker) => ({
|
|
125
132
|
...marker,
|
|
126
133
|
position: { ...marker.position },
|
|
127
|
-
...(marker.meta === undefined ? {} : { meta:
|
|
134
|
+
...(marker.meta === undefined ? {} : { meta: structuredClone(marker.meta) }),
|
|
128
135
|
})),
|
|
129
136
|
volumes: doc.volumes.map((volume) => ({
|
|
130
137
|
...volume,
|
|
131
138
|
center: { ...volume.center },
|
|
132
139
|
...(volume.halfExtents === undefined ? {} : { halfExtents: { ...volume.halfExtents } }),
|
|
133
|
-
...(volume.meta === undefined ? {} : { meta:
|
|
140
|
+
...(volume.meta === undefined ? {} : { meta: structuredClone(volume.meta) }),
|
|
134
141
|
})),
|
|
135
142
|
paths: doc.paths.map((path) => ({
|
|
136
143
|
...path,
|
|
137
144
|
points: path.points.map((point) => ({ ...point })),
|
|
138
|
-
...(path.meta === undefined ? {} : { meta:
|
|
145
|
+
...(path.meta === undefined ? {} : { meta: structuredClone(path.meta) }),
|
|
139
146
|
})),
|
|
140
147
|
annotations: doc.annotations.map((note) => ({
|
|
141
148
|
...note,
|
|
142
149
|
position: { ...note.position },
|
|
143
|
-
...(note.meta === undefined ? {} : { meta:
|
|
150
|
+
...(note.meta === undefined ? {} : { meta: structuredClone(note.meta) }),
|
|
144
151
|
})),
|
|
145
152
|
prefabs: doc.prefabs.map((prefab) => ({
|
|
146
153
|
...prefab,
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
volumes: prefab.fragment.volumes.map((volume) => ({ ...volume, center: { ...volume.center } })),
|
|
150
|
-
paths: prefab.fragment.paths.map((path) => ({ ...path, points: path.points.map((point) => ({ ...point })) })),
|
|
151
|
-
annotations: prefab.fragment.annotations.map((note) => ({ ...note, position: { ...note.position } })),
|
|
152
|
-
},
|
|
154
|
+
...(prefab.staticBake === undefined ? {} : { staticBake: parseStaticPrefabBake(prefab.staticBake) }),
|
|
155
|
+
fragment: structuredClone(prefab.fragment),
|
|
153
156
|
})),
|
|
154
157
|
collections: doc.collections.map((collection) => ({ ...collection, memberIds: [...collection.memberIds] })),
|
|
155
158
|
catalogs: cloneCatalogs(doc.catalogs),
|
|
159
|
+
...(doc.materialAssets === undefined ? {} : { materialAssets: structuredClone(doc.materialAssets) }),
|
|
156
160
|
...(doc.grids === undefined ? {} : { grids: doc.grids.map(cloneGridLayer) }),
|
|
157
161
|
...(doc.terrain === undefined ? {} : { terrain: doc.terrain }),
|
|
158
162
|
...(ui === undefined ? {} : { ui }),
|
|
@@ -174,7 +178,7 @@ function upsertCatalogs(base, overlay) {
|
|
|
174
178
|
{
|
|
175
179
|
id: catalog.id,
|
|
176
180
|
...(catalog.label === undefined ? {} : { label: catalog.label }),
|
|
177
|
-
...(catalog.schema === undefined ? {} : { schema: catalog.schema }),
|
|
181
|
+
...(catalog.schema === undefined ? {} : { schema: structuredClone(catalog.schema) }),
|
|
178
182
|
entries: catalog.entries.map(cloneCatalogEntry),
|
|
179
183
|
},
|
|
180
184
|
]));
|
|
@@ -204,6 +208,7 @@ export function normalizeEditorLayers(input) {
|
|
|
204
208
|
const directives = cloneDirectives(resolved.directives);
|
|
205
209
|
return {
|
|
206
210
|
version: 1,
|
|
211
|
+
...(resolved.simulation === undefined ? {} : { simulation: decodeEditorSimulation(resolved.simulation, { paths: asArray(resolved.paths) }) }),
|
|
207
212
|
markers: asArray(resolved.markers),
|
|
208
213
|
volumes: asArray(resolved.volumes),
|
|
209
214
|
paths: asArray(resolved.paths),
|
|
@@ -211,6 +216,7 @@ export function normalizeEditorLayers(input) {
|
|
|
211
216
|
prefabs: asArray(resolved.prefabs),
|
|
212
217
|
collections: asArray(resolved.collections),
|
|
213
218
|
catalogs: cloneCatalogs(resolved.catalogs),
|
|
219
|
+
...(resolved.materialAssets === undefined ? {} : { materialAssets: structuredClone(resolved.materialAssets) }),
|
|
214
220
|
...(grids === undefined ? {} : { grids }),
|
|
215
221
|
...(resolved.terrain === undefined ? {} : { terrain: migrateTerrainSnapshot(resolved.terrain) }),
|
|
216
222
|
...(ui === undefined ? {} : { ui }),
|
|
@@ -262,6 +268,8 @@ export function mergeEditorDocuments(...docs) {
|
|
|
262
268
|
out.prefabs.push(...doc.prefabs);
|
|
263
269
|
out.collections.push(...doc.collections);
|
|
264
270
|
out.catalogs = upsertCatalogs(out.catalogs, doc.catalogs);
|
|
271
|
+
if (doc.materialAssets !== undefined)
|
|
272
|
+
out.materialAssets = structuredClone(upsertById(out.materialAssets ?? [], doc.materialAssets));
|
|
265
273
|
const mergedGrids = upsertGrids(out.grids, doc.grids);
|
|
266
274
|
if (mergedGrids !== undefined)
|
|
267
275
|
out.grids = mergedGrids;
|
|
@@ -271,6 +279,8 @@ export function mergeEditorDocuments(...docs) {
|
|
|
271
279
|
out.minimap = cloneMinimapBake(doc.minimap);
|
|
272
280
|
if (doc.environment !== undefined)
|
|
273
281
|
out.environment = cloneEnvironment(doc.environment);
|
|
282
|
+
if (doc.simulation !== undefined)
|
|
283
|
+
out.simulation = structuredClone(doc.simulation);
|
|
274
284
|
if (doc.directives !== undefined) {
|
|
275
285
|
out.directives = upsertById(out.directives ?? [], doc.directives);
|
|
276
286
|
}
|
|
@@ -292,6 +302,7 @@ export function extractEditorFragment(doc, ids) {
|
|
|
292
302
|
const wanted = new Set(ids);
|
|
293
303
|
return cloneEditorDocument({
|
|
294
304
|
version: 1,
|
|
305
|
+
...(doc.materialAssets === undefined ? {} : { materialAssets: doc.materialAssets.filter((asset) => doc.markers.some((marker) => wanted.has(marker.id) && parseMaterialAssignments(marker.meta?.materialAssignments ?? []).some((item) => item.materialId === asset.id))) }),
|
|
295
306
|
markers: doc.markers.filter((marker) => wanted.has(marker.id)),
|
|
296
307
|
volumes: doc.volumes.filter((volume) => wanted.has(volume.id)),
|
|
297
308
|
paths: doc.paths.filter((path) => wanted.has(path.id)),
|
|
@@ -435,6 +446,7 @@ export function createPrefabFragment(doc, ids) {
|
|
|
435
446
|
z: point.z - origin.z,
|
|
436
447
|
});
|
|
437
448
|
return {
|
|
449
|
+
...(extracted.materialAssets === undefined ? {} : { materialAssets: extracted.materialAssets }),
|
|
438
450
|
markers: extracted.markers.map((marker) => ({ ...marker, position: shift(marker.position) })),
|
|
439
451
|
volumes: extracted.volumes.map((volume) => ({ ...volume, center: shift(volume.center) })),
|
|
440
452
|
paths: extracted.paths.map((path) => ({ ...path, points: path.points.map(shift) })),
|
|
@@ -608,7 +620,21 @@ function decodeMeta(value, path, errors) {
|
|
|
608
620
|
errors.push({ path, message: "expected an object" });
|
|
609
621
|
return undefined;
|
|
610
622
|
}
|
|
611
|
-
|
|
623
|
+
if (value.materialAssignments !== undefined) {
|
|
624
|
+
try {
|
|
625
|
+
parseMaterialAssignments(value.materialAssignments);
|
|
626
|
+
}
|
|
627
|
+
catch (error) {
|
|
628
|
+
errors.push({ path: `${path}.materialAssignments`, message: String(error) });
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
try {
|
|
632
|
+
authoredHiddenNodes(value);
|
|
633
|
+
}
|
|
634
|
+
catch (error) {
|
|
635
|
+
errors.push({ path: `${path}.hiddenNodes`, message: String(error) });
|
|
636
|
+
}
|
|
637
|
+
return structuredClone(value);
|
|
612
638
|
}
|
|
613
639
|
function decodeMarker(item, path, errors) {
|
|
614
640
|
if (!isPlainObject(item)) {
|
|
@@ -744,18 +770,55 @@ function decodeNote(item, path, errors) {
|
|
|
744
770
|
note.meta = meta;
|
|
745
771
|
return note;
|
|
746
772
|
}
|
|
773
|
+
function decodeMaterialAssets(value, path, errors) {
|
|
774
|
+
if (!Array.isArray(value)) {
|
|
775
|
+
errors.push({ path, message: "expected material assets" });
|
|
776
|
+
return [];
|
|
777
|
+
}
|
|
778
|
+
const ids = new Set();
|
|
779
|
+
const assets = [];
|
|
780
|
+
value.forEach((asset, index) => {
|
|
781
|
+
const diagnostics = validateMaterialAsset(asset).filter((diagnostic) => diagnostic.severity === "error");
|
|
782
|
+
for (const diagnostic of diagnostics)
|
|
783
|
+
errors.push({ path: `${path}[${index}].${diagnostic.path}`, message: diagnostic.message });
|
|
784
|
+
if (diagnostics.length === 0) {
|
|
785
|
+
if (ids.has(asset.id))
|
|
786
|
+
errors.push({ path: `${path}[${index}].id`, message: "duplicate material ID" });
|
|
787
|
+
ids.add(asset.id);
|
|
788
|
+
assets.push(structuredClone(asset));
|
|
789
|
+
}
|
|
790
|
+
});
|
|
791
|
+
return assets;
|
|
792
|
+
}
|
|
747
793
|
function decodeFragmentContent(value, path, errors) {
|
|
748
794
|
if (!isPlainObject(value)) {
|
|
749
795
|
errors.push({ path, message: "expected an object" });
|
|
750
796
|
return null;
|
|
751
797
|
}
|
|
798
|
+
const materialAssets = value.materialAssets === undefined ? undefined : decodeMaterialAssets(value.materialAssets, `${path}.materialAssets`, errors);
|
|
799
|
+
const markers = decodeArray(value.markers, `${path}.markers`, decodeMarker, errors);
|
|
800
|
+
validateMarkerMaterialReferences(markers, materialAssets ?? [], path, errors);
|
|
752
801
|
return {
|
|
753
|
-
|
|
802
|
+
...(materialAssets === undefined ? {} : { materialAssets }),
|
|
803
|
+
markers,
|
|
754
804
|
volumes: decodeArray(value.volumes, `${path}.volumes`, decodeVolume, errors),
|
|
755
805
|
paths: decodeArray(value.paths, `${path}.paths`, decodePath, errors),
|
|
756
806
|
annotations: decodeArray(value.annotations, `${path}.annotations`, decodeNote, errors),
|
|
757
807
|
};
|
|
758
808
|
}
|
|
809
|
+
function validateMarkerMaterialReferences(markers, assets, path, errors) {
|
|
810
|
+
for (const [index, marker] of markers.entries()) {
|
|
811
|
+
if (marker.meta?.materialAssignments === undefined)
|
|
812
|
+
continue;
|
|
813
|
+
try {
|
|
814
|
+
for (const diagnostic of validateMaterialAssignments(parseMaterialAssignments(marker.meta.materialAssignments), assets)) {
|
|
815
|
+
if (diagnostic.severity === "error")
|
|
816
|
+
errors.push({ path: `${path}.markers[${index}].meta.${diagnostic.path}`, message: diagnostic.message });
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
catch { }
|
|
820
|
+
}
|
|
821
|
+
}
|
|
759
822
|
function decodePrefab(item, path, errors) {
|
|
760
823
|
if (!isPlainObject(item)) {
|
|
761
824
|
errors.push({ path, message: "expected an object" });
|
|
@@ -768,7 +831,16 @@ function decodePrefab(item, path, errors) {
|
|
|
768
831
|
const fragment = decodeFragmentContent(item.fragment, `${path}.fragment`, errors);
|
|
769
832
|
if (typeof item.id !== "string" || typeof item.name !== "string" || fragment === null)
|
|
770
833
|
return null;
|
|
771
|
-
|
|
834
|
+
const prefab = { id: item.id, name: item.name, fragment };
|
|
835
|
+
if (item.staticBake !== undefined) {
|
|
836
|
+
try {
|
|
837
|
+
prefab.staticBake = parseStaticPrefabBake(item.staticBake);
|
|
838
|
+
}
|
|
839
|
+
catch (error) {
|
|
840
|
+
errors.push({ path: `${path}.staticBake`, message: error instanceof Error ? error.message : String(error) });
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
return prefab;
|
|
772
844
|
}
|
|
773
845
|
function decodeCollection(item, path, errors) {
|
|
774
846
|
if (!isPlainObject(item)) {
|
|
@@ -1228,6 +1300,7 @@ export function decodeEditorDocument(raw) {
|
|
|
1228
1300
|
return { ok: false, errors: [{ path: "$", message: "editor document must be an object" }] };
|
|
1229
1301
|
}
|
|
1230
1302
|
const errors = [];
|
|
1303
|
+
const materialAssets = raw.materialAssets === undefined ? undefined : decodeMaterialAssets(raw.materialAssets, "$.materialAssets", errors);
|
|
1231
1304
|
const markers = decodeArray(raw.markers, "$.markers", decodeMarker, errors);
|
|
1232
1305
|
const volumes = decodeArray(raw.volumes, "$.volumes", decodeVolume, errors);
|
|
1233
1306
|
const paths = decodeArray(raw.paths, "$.paths", decodePath, errors);
|
|
@@ -1243,6 +1316,15 @@ export function decodeEditorDocument(raw) {
|
|
|
1243
1316
|
const minimap = raw.minimap === undefined ? undefined : decodeMinimapBake(raw.minimap, "$.minimap", errors);
|
|
1244
1317
|
const bakes = raw.bakes === undefined ? undefined : decodeBakes(raw.bakes, "$.bakes", errors);
|
|
1245
1318
|
const environment = raw.environment === undefined ? undefined : decodeEnvironment(raw.environment, "$.environment", errors);
|
|
1319
|
+
let simulation;
|
|
1320
|
+
if (raw.simulation !== undefined) {
|
|
1321
|
+
try {
|
|
1322
|
+
simulation = decodeEditorSimulation(raw.simulation, { paths });
|
|
1323
|
+
}
|
|
1324
|
+
catch (error) {
|
|
1325
|
+
errors.push({ path: "$.simulation", message: error instanceof Error ? error.message : String(error) });
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1246
1328
|
// Placeable object ids form one document-global namespace (selection, parenting, and removal all
|
|
1247
1329
|
// treat them that way), so a document that reuses an id — even across two different collections —
|
|
1248
1330
|
// is malformed and rejected here with the offending path, rather than silently loading a scene
|
|
@@ -1264,6 +1346,7 @@ export function decodeEditorDocument(raw) {
|
|
|
1264
1346
|
}
|
|
1265
1347
|
});
|
|
1266
1348
|
}
|
|
1349
|
+
validateMarkerMaterialReferences(markers, materialAssets ?? [], "$", errors);
|
|
1267
1350
|
if (errors.length > 0)
|
|
1268
1351
|
return { ok: false, errors };
|
|
1269
1352
|
const terrain = raw.terrain === undefined ? undefined : migrateTerrainSnapshot(raw.terrain);
|
|
@@ -1272,6 +1355,8 @@ export function decodeEditorDocument(raw) {
|
|
|
1272
1355
|
ok: true,
|
|
1273
1356
|
document: {
|
|
1274
1357
|
version: 1,
|
|
1358
|
+
...(simulation === undefined ? {} : { simulation }),
|
|
1359
|
+
...(materialAssets === undefined ? {} : { materialAssets }),
|
|
1275
1360
|
markers,
|
|
1276
1361
|
volumes,
|
|
1277
1362
|
paths,
|
|
@@ -1315,6 +1400,7 @@ export function applyEditorDocumentOverlay(base, overlay) {
|
|
|
1315
1400
|
const terrain = overlay.terrain ?? base.terrain;
|
|
1316
1401
|
const minimap = overlay.minimap ?? base.minimap;
|
|
1317
1402
|
const environment = overlay.environment ?? base.environment;
|
|
1403
|
+
const simulation = overlay.simulation ?? base.simulation;
|
|
1318
1404
|
const ui = overlay.ui === undefined
|
|
1319
1405
|
? cloneEditorUiDocument(base.ui)
|
|
1320
1406
|
: {
|
|
@@ -1326,6 +1412,7 @@ export function applyEditorDocumentOverlay(base, overlay) {
|
|
|
1326
1412
|
const grids = upsertGrids(base.grids, overlay.grids);
|
|
1327
1413
|
return {
|
|
1328
1414
|
version: 1,
|
|
1415
|
+
...(simulation === undefined ? {} : { simulation: structuredClone(simulation) }),
|
|
1329
1416
|
markers: upsertById(base.markers, overlay.markers),
|
|
1330
1417
|
volumes: upsertById(base.volumes, overlay.volumes),
|
|
1331
1418
|
paths: upsertById(base.paths, overlay.paths),
|
|
@@ -1333,6 +1420,7 @@ export function applyEditorDocumentOverlay(base, overlay) {
|
|
|
1333
1420
|
prefabs: upsertById(base.prefabs, overlay.prefabs),
|
|
1334
1421
|
collections: upsertById(base.collections, overlay.collections),
|
|
1335
1422
|
catalogs: upsertCatalogs(base.catalogs, overlay.catalogs),
|
|
1423
|
+
...(base.materialAssets === undefined && overlay.materialAssets === undefined ? {} : { materialAssets: structuredClone(upsertById(base.materialAssets ?? [], overlay.materialAssets ?? [])) }),
|
|
1336
1424
|
...(grids === undefined ? {} : { grids }),
|
|
1337
1425
|
...(terrain === undefined ? {} : { terrain }),
|
|
1338
1426
|
...(ui === undefined ? {} : { ui }),
|
|
@@ -1382,6 +1470,17 @@ export function editorDocumentBounds(doc) {
|
|
|
1382
1470
|
for (const note of doc.annotations) {
|
|
1383
1471
|
include(note.position.x, note.position.y, note.position.z);
|
|
1384
1472
|
}
|
|
1473
|
+
if (doc.terrain !== undefined) {
|
|
1474
|
+
const terrain = doc.terrain;
|
|
1475
|
+
let low = 0;
|
|
1476
|
+
let high = 0;
|
|
1477
|
+
for (const offset of terrain.offsets) {
|
|
1478
|
+
low = Math.min(low, offset);
|
|
1479
|
+
high = Math.max(high, offset);
|
|
1480
|
+
}
|
|
1481
|
+
include(terrain.bounds.minX, low, terrain.bounds.minZ);
|
|
1482
|
+
include(terrain.bounds.maxX, high, terrain.bounds.maxZ);
|
|
1483
|
+
}
|
|
1385
1484
|
if (!any)
|
|
1386
1485
|
return null;
|
|
1387
1486
|
return {
|
package/dist/editor/index.d.ts
CHANGED
|
@@ -90,6 +90,8 @@ export type { EditorDispatchOptions } from "./commands.js";
|
|
|
90
90
|
export type { EditorSession } from "./commands.js";
|
|
91
91
|
/** The document plus current selection at a point in editor history. */
|
|
92
92
|
export type { EditorSessionState } from "./commands.js";
|
|
93
|
+
/** Atomic command-batch outcome; a failure identifies the zero-based command index. */
|
|
94
|
+
export type { EditorTransactionResult } from "./commands.js";
|
|
93
95
|
export { applyDocumentPatch, applyRuntimeStateDelta, createDocumentLiveSync, getDocumentLiveSync, installDocumentLiveSync, runtimeEntityWriteBackCommand, subscribeDocumentLiveSyncInstall, } from "./liveSync.js";
|
|
94
96
|
/** Versioned document mutation on the editorΓåöruntime live-sync stream. */
|
|
95
97
|
export type { DocumentPatch, ApplyDocumentPatchResult } from "./liveSync.js";
|
|
@@ -122,3 +124,4 @@ export type { AsciiGridExportOptions } from "./gridAdapters.js";
|
|
|
122
124
|
export type { CsvGridImportOptions } from "./gridAdapters.js";
|
|
123
125
|
/** Options for exporting a grid layer as CSV. */
|
|
124
126
|
export type { CsvGridExportOptions } from "./gridAdapters.js";
|
|
127
|
+
export { TERRAIN_MATERIALS, TERRAIN_MATERIAL_COLORS, type TerrainMaterial } from "./terrainMaterials.js";
|
package/dist/editor/index.js
CHANGED
|
@@ -15,3 +15,4 @@ export { applyDocumentPatch, applyRuntimeStateDelta, createDocumentLiveSync, get
|
|
|
15
15
|
export { consumeRuntimePlayStep, createRuntimePlayControl, getRuntimeInspectorValue, planRuntimeInspectorSet, runtimeEntityMetaWriteBackCommand, summarizeRuntimeInspector, } from "./runtimeInspector.js";
|
|
16
16
|
export { CURRENT_GRID_SCHEMA_VERSION, cloneGridLayer, createGridLayer, eraseGridCell, eyedropGridCell, fillGridRect, findGridPaletteEntry, floodFillGrid, forEachGridCell, getGridCell, getGridCellAtWorld, gridCellCount, gridCellEntries, gridCellKey, gridCellToWorld, gridCellsOfValue, gridEmptyValue, gridGlyphMap, inGridBounds, migrateGridLayer, paintGridCells, parseGridCellKey, resizeGridLayer, setGridCell, worldToGridCell, } from "./grid.js";
|
|
17
17
|
export { exportAsciiGrid, exportCsvGrid, importAsciiGrid, importCsvGrid, } from "./gridAdapters.js";
|
|
18
|
+
export { TERRAIN_MATERIALS, TERRAIN_MATERIAL_COLORS } from "./terrainMaterials.js";
|
|
@@ -4,7 +4,8 @@ import type { EditorDocument, EditorVec3 } from "./types.js";
|
|
|
4
4
|
* One versioned document mutation on the live-sync stream. `snapshot` replaces the whole
|
|
5
5
|
* document; `commands` replays structural editor commands onto the current document.
|
|
6
6
|
* `baseRevision` must match the receiver's current revision unless `force` is set (document
|
|
7
|
-
* authority from the editor).
|
|
7
|
+
* authority from the editor). `revision` preserves the authority cursor when replaying: snapshots
|
|
8
|
+
* may jump forward for reconnect recovery; commands must be the next revision.
|
|
8
9
|
*/
|
|
9
10
|
export type DocumentPatch = {
|
|
10
11
|
type: "snapshot";
|
|
@@ -26,6 +27,7 @@ export type ApplyDocumentPatchResult = {
|
|
|
26
27
|
} | {
|
|
27
28
|
ok: false;
|
|
28
29
|
error: string;
|
|
30
|
+
commandIndex?: number;
|
|
29
31
|
};
|
|
30
32
|
/** One live entity row the runtime may stream to the editor (play-mode inspector feed). */
|
|
31
33
|
export interface RuntimeEntityState {
|
|
@@ -92,6 +94,7 @@ export interface DocumentLiveSync {
|
|
|
92
94
|
}): ApplyDocumentPatchResult;
|
|
93
95
|
/** Document-authoritative full replace (editor session mirror). */
|
|
94
96
|
replaceDocument(document: EditorDocument): DocumentLiveEvent;
|
|
97
|
+
/** Returns a current snapshot when the requested revision predates retained history. */
|
|
95
98
|
pullPatches(sinceRevision: number): DocumentPatch[];
|
|
96
99
|
subscribeDocument(listener: (event: DocumentLiveEvent) => void): () => void;
|
|
97
100
|
getRuntimeState(): RuntimeStateSnapshot;
|
package/dist/editor/liveSync.js
CHANGED
|
@@ -8,6 +8,15 @@ const MAX_RUNTIME_LOG = 128;
|
|
|
8
8
|
* @capability editor-live-sync apply a versioned document patch onto a live scene document
|
|
9
9
|
*/
|
|
10
10
|
export function applyDocumentPatch(document, revision, patch, options) {
|
|
11
|
+
if (!Number.isSafeInteger(patch.baseRevision) || patch.baseRevision < 0) {
|
|
12
|
+
return { ok: false, error: "baseRevision must be a nonnegative safe integer" };
|
|
13
|
+
}
|
|
14
|
+
if (patch.revision !== undefined && (!Number.isSafeInteger(patch.revision) || patch.revision <= revision)) {
|
|
15
|
+
return { ok: false, error: "revision must be a safe integer newer than the current revision" };
|
|
16
|
+
}
|
|
17
|
+
if (patch.type === "commands" && patch.revision !== undefined && patch.revision !== revision + 1) {
|
|
18
|
+
return { ok: false, error: "commands revision must immediately follow the current revision" };
|
|
19
|
+
}
|
|
11
20
|
if (!options?.force && patch.baseRevision !== revision) {
|
|
12
21
|
return {
|
|
13
22
|
ok: false,
|
|
@@ -16,7 +25,7 @@ export function applyDocumentPatch(document, revision, patch, options) {
|
|
|
16
25
|
}
|
|
17
26
|
if (patch.type === "snapshot") {
|
|
18
27
|
const nextDoc = cloneEditorDocument(patch.document);
|
|
19
|
-
const nextRevision = revision + 1;
|
|
28
|
+
const nextRevision = patch.revision ?? revision + 1;
|
|
20
29
|
return {
|
|
21
30
|
ok: true,
|
|
22
31
|
document: nextDoc,
|
|
@@ -24,15 +33,17 @@ export function applyDocumentPatch(document, revision, patch, options) {
|
|
|
24
33
|
patch: { ...patch, revision: nextRevision, document: nextDoc },
|
|
25
34
|
};
|
|
26
35
|
}
|
|
27
|
-
if (patch.commands.length === 0) {
|
|
36
|
+
if (!Array.isArray(patch.commands) || patch.commands.length === 0) {
|
|
28
37
|
return { ok: false, error: "commands patch is empty" };
|
|
29
38
|
}
|
|
30
|
-
const session = createEditorSession(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const
|
|
39
|
+
const session = createEditorSession(document);
|
|
40
|
+
const before = session.getState().document;
|
|
41
|
+
const result = session.transaction(patch.commands);
|
|
42
|
+
if (!result.ok)
|
|
43
|
+
return result;
|
|
44
|
+
const changed = result.state.document !== before;
|
|
45
|
+
const nextDoc = changed ? result.state.document : document;
|
|
46
|
+
const nextRevision = changed ? revision + 1 : revision;
|
|
36
47
|
return {
|
|
37
48
|
ok: true,
|
|
38
49
|
document: nextDoc,
|
|
@@ -146,7 +157,8 @@ export function createDocumentLiveSync(initial) {
|
|
|
146
157
|
const result = applyDocumentPatch(document, revision, patch, options);
|
|
147
158
|
if (!result.ok)
|
|
148
159
|
return result;
|
|
149
|
-
|
|
160
|
+
if (result.revision !== revision)
|
|
161
|
+
commit(result);
|
|
150
162
|
return result;
|
|
151
163
|
},
|
|
152
164
|
replaceDocument(next) {
|
|
@@ -157,7 +169,11 @@ export function createDocumentLiveSync(initial) {
|
|
|
157
169
|
return commit(result);
|
|
158
170
|
},
|
|
159
171
|
pullPatches(sinceRevision) {
|
|
160
|
-
|
|
172
|
+
const patches = patchLog.filter((patch) => (patch.revision ?? 0) > sinceRevision);
|
|
173
|
+
if (patches.length > 0 && patches[0].baseRevision !== sinceRevision) {
|
|
174
|
+
return [{ type: "snapshot", baseRevision: sinceRevision, revision, document: cloneEditorDocument(document) }];
|
|
175
|
+
}
|
|
176
|
+
return patches;
|
|
161
177
|
},
|
|
162
178
|
subscribeDocument(listener) {
|
|
163
179
|
documentListeners.add(listener);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ModelConfig } from "../game/playableGame.js";
|
|
2
|
+
import { type MaterialAsset, type MaterialAssignment, type MaterialSelector } from "../material/materialAsset.js";
|
|
3
|
+
import type { EditorDocument } from "./types.js";
|
|
4
|
+
/** Validate and copy a material asset at the editor boundary. */
|
|
5
|
+
export declare function parseEditorMaterialAsset(value: unknown): MaterialAsset;
|
|
6
|
+
/**
|
|
7
|
+
* Read named assignments from authored marker metadata without inferring whole-model overrides.
|
|
8
|
+
* @capability authored-material-slots read validated material assignments from editor marker metadata
|
|
9
|
+
*/
|
|
10
|
+
export declare function authoredMaterialAssignments(meta: Record<string, unknown> | undefined): MaterialAssignment[];
|
|
11
|
+
/** Compare selector meaning independently of serialized property order. @internal */
|
|
12
|
+
export declare function sameMaterialSelector(a: MaterialSelector, b: MaterialSelector): boolean;
|
|
13
|
+
/** Apply authored references; document assets and selectors override model defaults of the same identity. */
|
|
14
|
+
export declare function modelWithAuthoredMaterials(model: ModelConfig | undefined, document: Pick<EditorDocument, "markers" | "materialAssets">, instanceId: string): ModelConfig | undefined;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { parseMaterialAssignments, validateMaterialAsset, validateMaterialAssignments } from "../material/materialAsset.js";
|
|
2
|
+
/** Validate and copy a material asset at the editor boundary. */
|
|
3
|
+
export function parseEditorMaterialAsset(value) {
|
|
4
|
+
const errors = validateMaterialAsset(value).filter((item) => item.severity === "error");
|
|
5
|
+
if (errors.length > 0)
|
|
6
|
+
throw new TypeError(errors.map((item) => `${item.path}: ${item.message}`).join("; "));
|
|
7
|
+
return structuredClone(value);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Read named assignments from authored marker metadata without inferring whole-model overrides.
|
|
11
|
+
* @capability authored-material-slots read validated material assignments from editor marker metadata
|
|
12
|
+
*/
|
|
13
|
+
export function authoredMaterialAssignments(meta) {
|
|
14
|
+
return meta?.materialAssignments === undefined ? [] : parseMaterialAssignments(meta.materialAssignments);
|
|
15
|
+
}
|
|
16
|
+
/** Compare selector meaning independently of serialized property order. @internal */
|
|
17
|
+
export function sameMaterialSelector(a, b) {
|
|
18
|
+
return a.mesh === b.mesh && a.slot === b.slot && a.slotIndex === b.slotIndex;
|
|
19
|
+
}
|
|
20
|
+
/** Apply authored references; document assets and selectors override model defaults of the same identity. */
|
|
21
|
+
export function modelWithAuthoredMaterials(model, document, instanceId) {
|
|
22
|
+
if (model === undefined)
|
|
23
|
+
return model;
|
|
24
|
+
const marker = document.markers.find((item) => item.id === instanceId);
|
|
25
|
+
const assignments = authoredMaterialAssignments(marker?.meta);
|
|
26
|
+
const modelAssignments = model.materialAssignments ?? [];
|
|
27
|
+
if (assignments.length === 0 && modelAssignments.length === 0)
|
|
28
|
+
return model;
|
|
29
|
+
const assetsById = new Map((model.materialAssets ?? []).map((asset) => [asset.id, asset]));
|
|
30
|
+
for (const asset of document.materialAssets ?? [])
|
|
31
|
+
assetsById.set(asset.id, asset);
|
|
32
|
+
const assets = [...assetsById.values()];
|
|
33
|
+
const resolvedAssignments = [...modelAssignments.filter((item) => !assignments.some((authored) => sameMaterialSelector(item.selector, authored.selector))), ...assignments];
|
|
34
|
+
const errors = validateMaterialAssignments(resolvedAssignments, assets).filter((item) => item.severity === "error");
|
|
35
|
+
if (errors.length > 0)
|
|
36
|
+
throw new TypeError(errors.map((item) => `${item.path}: ${item.message}`).join("; "));
|
|
37
|
+
return { ...model, materialAssets: assets, materialAssignments: resolvedAssignments };
|
|
38
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ModelConfig } from "../game/playableGame.js";
|
|
2
|
+
import type { EditorMarker } from "./types.js";
|
|
3
|
+
/** Validate and copy exact node names; an empty list selects no hidden subtrees. */
|
|
4
|
+
export declare function parseModelHiddenNodes(value: unknown): readonly string[] | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* Read marker visibility metadata; an empty list clears model defaults.
|
|
7
|
+
* @capability model-node-visibility read authored per-placement model subtree selections
|
|
8
|
+
*/
|
|
9
|
+
export declare function authoredHiddenNodes(meta: Record<string, unknown> | undefined): readonly string[] | undefined;
|
|
10
|
+
/** Apply a placement's authored visibility without erasing omitted game model defaults. */
|
|
11
|
+
export declare function modelWithAuthoredNodeVisibility(model: ModelConfig | undefined, marker: Pick<EditorMarker, "meta"> | undefined): ModelConfig | undefined;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Validate and copy exact node names; an empty list selects no hidden subtrees. */
|
|
2
|
+
export function parseModelHiddenNodes(value) {
|
|
3
|
+
if (value === undefined)
|
|
4
|
+
return undefined;
|
|
5
|
+
if (!Array.isArray(value)) {
|
|
6
|
+
throw new TypeError("hiddenNodes must be an array of nonblank node names");
|
|
7
|
+
}
|
|
8
|
+
for (const name of value) {
|
|
9
|
+
if (typeof name !== "string" || name.trim().length === 0)
|
|
10
|
+
throw new TypeError("hiddenNodes must be an array of nonblank node names");
|
|
11
|
+
}
|
|
12
|
+
return [...value];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Read marker visibility metadata; an empty list clears model defaults.
|
|
16
|
+
* @capability model-node-visibility read authored per-placement model subtree selections
|
|
17
|
+
*/
|
|
18
|
+
export function authoredHiddenNodes(meta) {
|
|
19
|
+
return parseModelHiddenNodes(meta?.hiddenNodes);
|
|
20
|
+
}
|
|
21
|
+
/** Apply a placement's authored visibility without erasing omitted game model defaults. */
|
|
22
|
+
export function modelWithAuthoredNodeVisibility(model, marker) {
|
|
23
|
+
if (model === undefined)
|
|
24
|
+
return undefined;
|
|
25
|
+
const hiddenNodes = authoredHiddenNodes(marker?.meta);
|
|
26
|
+
if (hiddenNodes === undefined || (model.hiddenNodes?.length === hiddenNodes.length && hiddenNodes.every((name, index) => model.hiddenNodes[index] === name)))
|
|
27
|
+
return model;
|
|
28
|
+
return { ...model, hiddenNodes };
|
|
29
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { PlayerMovementConfig } from "../game/playableGame.js";
|
|
2
|
+
import type { PhysicsConfig } from "../game/defineGame.js";
|
|
3
|
+
import type { ParamSchema } from "../scene/sceneKinds.js";
|
|
4
|
+
import type { EditorCatalogData } from "./types.js";
|
|
5
|
+
/** Numeric character controls a game elects to author through its own named catalog rows. */
|
|
6
|
+
export interface AuthoredMovementValues {
|
|
7
|
+
walkSpeed?: number;
|
|
8
|
+
gravity?: number;
|
|
9
|
+
jumpVelocity?: number;
|
|
10
|
+
stepHeight?: number;
|
|
11
|
+
/** Optional positive heightfield collision span from feet; independent of model and camera scale. */
|
|
12
|
+
collisionHeight?: number;
|
|
13
|
+
/** Optional heightfield rise/run limit; omitted controls retain the game's movement policy. */
|
|
14
|
+
maxClimbGrade?: number;
|
|
15
|
+
groundAcceleration?: number;
|
|
16
|
+
airAcceleration?: number;
|
|
17
|
+
groundFriction?: number;
|
|
18
|
+
runMultiplier?: number;
|
|
19
|
+
crouchMultiplier?: number;
|
|
20
|
+
jumpBufferMs?: number;
|
|
21
|
+
coyoteMs?: number;
|
|
22
|
+
/** Rising speed retained on early jump release, from 0 to 1. */
|
|
23
|
+
jumpCutFactor?: number;
|
|
24
|
+
apexGravityScale?: number;
|
|
25
|
+
apexSpeed?: number;
|
|
26
|
+
fallGravityScale?: number;
|
|
27
|
+
landingRecoveryMs?: number;
|
|
28
|
+
/** Initial landing speed multiplier; values above 1 retain the existing runtime contract. */
|
|
29
|
+
landingSpeedScale?: number;
|
|
30
|
+
}
|
|
31
|
+
/** Only authored values, ready to overlay on a game's existing movement and physics policy. */
|
|
32
|
+
export interface AuthoredMovementConfig {
|
|
33
|
+
readonly walkSpeed?: number;
|
|
34
|
+
readonly physics?: {
|
|
35
|
+
readonly gravity?: number;
|
|
36
|
+
readonly jumpVelocity?: number;
|
|
37
|
+
};
|
|
38
|
+
readonly movement?: Pick<PlayerMovementConfig, "stepHeight" | "collisionHeight" | "maxClimbGrade" | "feel">;
|
|
39
|
+
}
|
|
40
|
+
/** A persisted movement value rejected before it can replace the game's current setting. */
|
|
41
|
+
export interface AuthoredMovementDiagnostic {
|
|
42
|
+
readonly path: string;
|
|
43
|
+
readonly message: string;
|
|
44
|
+
readonly repair: string;
|
|
45
|
+
}
|
|
46
|
+
/** Valid partial tuning plus located rejected values; absent rows leave game defaults intact. */
|
|
47
|
+
export interface AuthoredMovementResult {
|
|
48
|
+
readonly config: AuthoredMovementConfig;
|
|
49
|
+
readonly diagnostics: readonly AuthoredMovementDiagnostic[];
|
|
50
|
+
}
|
|
51
|
+
/** Structural authored catalog input; an immutable EditorDocument satisfies it. */
|
|
52
|
+
export interface MovementCatalogDocumentLike {
|
|
53
|
+
readonly catalogs: readonly EditorCatalogData[];
|
|
54
|
+
}
|
|
55
|
+
/** Game-owned callbacks, collision and physics settings retained beneath authored numeric controls. */
|
|
56
|
+
export interface AuthoredMovementDefaults {
|
|
57
|
+
readonly movement?: PlayerMovementConfig;
|
|
58
|
+
readonly physics?: PhysicsConfig;
|
|
59
|
+
}
|
|
60
|
+
/** Stable runtime objects whose numeric getters resolve the current authored document. */
|
|
61
|
+
export interface AuthoredMovementBinding {
|
|
62
|
+
readonly movement: PlayerMovementConfig;
|
|
63
|
+
readonly physics: PhysicsConfig;
|
|
64
|
+
readonly diagnostics: readonly AuthoredMovementDiagnostic[];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Creates editor/RPC controls only for the values a game supplies, with those game's defaults.
|
|
68
|
+
* Gravity retains the game's sign convention; collision height is positive, other controls nonnegative.
|
|
69
|
+
* Jump release is bounded to 0..1; supplied deeper jump controls share a collapsed response group.
|
|
70
|
+
* @capability editor-movement author game-chosen character movement controls through ordinary catalog rows
|
|
71
|
+
*/
|
|
72
|
+
export declare function createMovementSchema(defaults: AuthoredMovementValues): ParamSchema;
|
|
73
|
+
/**
|
|
74
|
+
* Reads one game-named row as partial runtime tuning, retaining game defaults for omitted or invalid
|
|
75
|
+
* values. Rejected values include their document path and repair; no policy callbacks are replaced.
|
|
76
|
+
* @capability editor-movement resolve saved movement controls without replacing a game's character rules
|
|
77
|
+
*/
|
|
78
|
+
export declare function readAuthoredMovement(document: MovementCatalogDocumentLike, catalogId: string, entryId: string): AuthoredMovementResult;
|
|
79
|
+
/**
|
|
80
|
+
* Memoizes movement resolution by immutable document identity; reads the latest document on every
|
|
81
|
+
* call while catalog lookup and diagnostics run only after authoring changes.
|
|
82
|
+
* @capability editor-movement consume live saved movement tuning with bounded per-frame work
|
|
83
|
+
*/
|
|
84
|
+
export declare function createAuthoredMovementReader(getDocument: () => MovementCatalogDocumentLike, catalogId: string, entryId: string): () => AuthoredMovementResult;
|
|
85
|
+
/**
|
|
86
|
+
* Binds live authored numeric controls onto stable runtime objects, preserving the game's callbacks,
|
|
87
|
+
* collision, backend and other policy. The movement controller reads these getters each step.
|
|
88
|
+
* @capability editor-movement bind live character tuning while preserving distinctive game policy
|
|
89
|
+
*/
|
|
90
|
+
export declare function bindAuthoredMovement(read: () => AuthoredMovementResult, defaults: AuthoredMovementDefaults): AuthoredMovementBinding;
|