@jgengine/editor 0.11.0 → 0.13.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.
Files changed (171) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +12 -0
  3. package/dist/AssetBrowser.d.ts +33 -1
  4. package/dist/AssetBrowser.js +64 -2
  5. package/dist/AssetDropZone.d.ts +9 -0
  6. package/dist/AssetDropZone.js +65 -0
  7. package/dist/CatalogsPanel.d.ts +4 -2
  8. package/dist/CatalogsPanel.js +162 -9
  9. package/dist/CityPreview.d.ts +11 -0
  10. package/dist/CityPreview.js +45 -0
  11. package/dist/CollectionsPanel.js +5 -5
  12. package/dist/DebugDraw.d.ts +3 -1
  13. package/dist/DebugDraw.js +37 -19
  14. package/dist/EditorApp.d.ts +15 -1
  15. package/dist/EditorApp.js +147 -35
  16. package/dist/EditorChrome.d.ts +27 -6
  17. package/dist/EditorChrome.js +314 -130
  18. package/dist/InspectorPanel.d.ts +17 -7
  19. package/dist/InspectorPanel.js +173 -27
  20. package/dist/LightingPanel.d.ts +11 -0
  21. package/dist/LightingPanel.js +99 -0
  22. package/dist/MaterialsWorkspacePanel.d.ts +12 -0
  23. package/dist/MaterialsWorkspacePanel.js +86 -0
  24. package/dist/ParentPickerMenu.d.ts +13 -0
  25. package/dist/ParentPickerMenu.js +33 -0
  26. package/dist/PerfProbe.js +33 -0
  27. package/dist/PrefabsPanel.js +5 -5
  28. package/dist/RuntimePlayBridge.d.ts +5 -3
  29. package/dist/RuntimePlayBridge.js +6 -13
  30. package/dist/ScatterPreview.d.ts +3 -1
  31. package/dist/ScatterPreview.js +10 -7
  32. package/dist/SchemaInspector.js +21 -8
  33. package/dist/ScriptingPanel.d.ts +15 -0
  34. package/dist/ScriptingPanel.js +46 -0
  35. package/dist/SelectionGizmo.js +114 -30
  36. package/dist/StandaloneEditor.d.ts +16 -9
  37. package/dist/StandaloneEditor.js +19 -20
  38. package/dist/TerrainPanel.d.ts +3 -2
  39. package/dist/TerrainPanel.js +16 -5
  40. package/dist/TerrainSculpt.js +98 -59
  41. package/dist/agent/AgentPanel.d.ts +5 -2
  42. package/dist/agent/AgentPanel.js +11 -6
  43. package/dist/agent/toolBridge.js +5 -0
  44. package/dist/assetImport.d.ts +36 -0
  45. package/dist/assetImport.js +66 -0
  46. package/dist/authoredComponentMeta.d.ts +25 -0
  47. package/dist/authoredComponentMeta.js +65 -0
  48. package/dist/gizmoPivot.d.ts +4 -0
  49. package/dist/gizmoPivot.js +34 -0
  50. package/dist/handlers/catalogs.d.ts +3 -0
  51. package/dist/handlers/catalogs.js +137 -0
  52. package/dist/handlers/collections.d.ts +3 -0
  53. package/dist/handlers/collections.js +57 -0
  54. package/dist/handlers/context.d.ts +36 -0
  55. package/dist/handlers/context.js +1 -0
  56. package/dist/handlers/document.d.ts +3 -0
  57. package/dist/handlers/document.js +350 -0
  58. package/dist/handlers/grids.d.ts +3 -0
  59. package/dist/handlers/grids.js +142 -0
  60. package/dist/handlers/hierarchy.d.ts +3 -0
  61. package/dist/handlers/hierarchy.js +55 -0
  62. package/dist/handlers/index.d.ts +8 -0
  63. package/dist/handlers/index.js +25 -0
  64. package/dist/handlers/minimap.d.ts +9 -0
  65. package/dist/handlers/minimap.js +32 -0
  66. package/dist/handlers/runtime.d.ts +3 -0
  67. package/dist/handlers/runtime.js +191 -0
  68. package/dist/handlers/streets.d.ts +3 -0
  69. package/dist/handlers/streets.js +159 -0
  70. package/dist/handlers/terrain.d.ts +3 -0
  71. package/dist/handlers/terrain.js +204 -0
  72. package/dist/index.d.ts +3 -2
  73. package/dist/index.js +3 -2
  74. package/dist/listDocumentTriggers.d.ts +42 -0
  75. package/dist/listDocumentTriggers.js +49 -0
  76. package/dist/materialAssignments.d.ts +63 -0
  77. package/dist/materialAssignments.js +84 -0
  78. package/dist/mcp/fieldSpec.d.ts +32 -0
  79. package/dist/mcp/fieldSpec.js +174 -0
  80. package/dist/mcp/rpcRequest.d.ts +8 -5
  81. package/dist/mcp/rpcRequest.js +72 -93
  82. package/dist/mcp/tools.js +105 -912
  83. package/dist/networkSnapshot.d.ts +82 -0
  84. package/dist/networkSnapshot.js +73 -0
  85. package/dist/parentCandidates.d.ts +12 -0
  86. package/dist/parentCandidates.js +25 -0
  87. package/dist/session.d.ts +91 -4
  88. package/dist/session.js +36 -1017
  89. package/dist/shell/AnimationPanel.d.ts +17 -0
  90. package/dist/shell/AnimationPanel.js +134 -0
  91. package/dist/shell/AssetThumbnail.d.ts +12 -0
  92. package/dist/shell/AssetThumbnail.js +23 -0
  93. package/dist/shell/BottomDock.d.ts +25 -0
  94. package/dist/shell/BottomDock.js +40 -0
  95. package/dist/shell/CommandPalette.d.ts +12 -0
  96. package/dist/shell/CommandPalette.js +56 -0
  97. package/dist/shell/ConsolePanel.d.ts +8 -0
  98. package/dist/shell/ConsolePanel.js +45 -0
  99. package/dist/shell/ContentBrowser.d.ts +21 -0
  100. package/dist/shell/ContentBrowser.js +130 -0
  101. package/dist/shell/HierarchyPanel.d.ts +26 -0
  102. package/dist/shell/HierarchyPanel.js +387 -0
  103. package/dist/shell/NetworkWorkspacePanel.d.ts +11 -0
  104. package/dist/shell/NetworkWorkspacePanel.js +49 -0
  105. package/dist/shell/PlayModeBar.d.ts +12 -0
  106. package/dist/shell/PlayModeBar.js +27 -0
  107. package/dist/shell/ProfilerPanel.d.ts +10 -0
  108. package/dist/shell/ProfilerPanel.js +41 -0
  109. package/dist/shell/SceneToolbar.d.ts +43 -0
  110. package/dist/shell/SceneToolbar.js +122 -0
  111. package/dist/shell/StatusBar.d.ts +18 -0
  112. package/dist/shell/StatusBar.js +41 -0
  113. package/dist/shell/TopAppBar.d.ts +35 -0
  114. package/dist/shell/TopAppBar.js +64 -0
  115. package/dist/shell/ViewportOverlays.d.ts +23 -0
  116. package/dist/shell/ViewportOverlays.js +136 -0
  117. package/dist/shell/WorkspaceRail.d.ts +20 -0
  118. package/dist/shell/WorkspaceRail.js +31 -0
  119. package/dist/shell/cameraTelemetry.d.ts +27 -0
  120. package/dist/shell/cameraTelemetry.js +24 -0
  121. package/dist/shell/commandRegistry.d.ts +58 -0
  122. package/dist/shell/commandRegistry.js +133 -0
  123. package/dist/shell/consoleSink.d.ts +11 -0
  124. package/dist/shell/consoleSink.js +17 -0
  125. package/dist/shell/consoleStore.d.ts +27 -0
  126. package/dist/shell/consoleStore.js +43 -0
  127. package/dist/shell/fields.d.ts +39 -0
  128. package/dist/shell/fields.js +104 -0
  129. package/dist/shell/formatSavedRelative.d.ts +2 -0
  130. package/dist/shell/formatSavedRelative.js +14 -0
  131. package/dist/shell/glbThumbnailCache.d.ts +45 -0
  132. package/dist/shell/glbThumbnailCache.js +241 -0
  133. package/dist/shell/hierarchyA11y.d.ts +21 -0
  134. package/dist/shell/hierarchyA11y.js +35 -0
  135. package/dist/shell/icons.d.ts +13 -0
  136. package/dist/shell/icons.js +85 -0
  137. package/dist/shell/layoutStore.d.ts +68 -0
  138. package/dist/shell/layoutStore.js +156 -0
  139. package/dist/shell/pathFlythrough.d.ts +37 -0
  140. package/dist/shell/pathFlythrough.js +54 -0
  141. package/dist/shell/perfHistory.d.ts +70 -0
  142. package/dist/shell/perfHistory.js +130 -0
  143. package/dist/shell/renameObject.d.ts +3 -0
  144. package/dist/shell/renameObject.js +25 -0
  145. package/dist/shell/theme.d.ts +36 -0
  146. package/dist/shell/theme.js +37 -0
  147. package/dist/shell/ui.d.ts +84 -0
  148. package/dist/shell/ui.js +90 -0
  149. package/dist/shell/useGlbThumbnail.d.ts +8 -0
  150. package/dist/shell/useGlbThumbnail.js +19 -0
  151. package/dist/uiStore.d.ts +27 -0
  152. package/dist/uiStore.js +10 -0
  153. package/dist/viewportContextMenu.d.ts +4 -2
  154. package/dist/viewportContextMenu.js +5 -1
  155. package/package.json +4 -4
  156. package/skills/jgengine/SKILL.md +8 -4
  157. package/skills/jgengine/api.md +182 -81
  158. package/skills/jgengine/capabilities.md +104 -2
  159. package/skills/jgengine/recipes/minimal-game.md +88 -0
  160. package/skills/jgengine-editor/SKILL.md +18 -3
  161. package/skills/jgengine-editor/api.md +403 -20
  162. package/skills/jgengine-editor/capabilities.md +46 -0
  163. package/skills/jgengine-editor/reference.md +87 -0
  164. package/skills/level-design/SKILL.md +54 -0
  165. package/skills/level-design/agents/openai.yaml +4 -0
  166. package/skills/level-design/references/application-playbook.md +192 -0
  167. package/skills/level-design/references/field-guide.md +337 -0
  168. package/skills/level-design/references/live-editor-loop.md +179 -0
  169. package/dist/OutlinerPanel.d.ts +0 -14
  170. package/dist/OutlinerPanel.js +0 -84
  171. package/skills/jgengine/reference-cartridge.md +0 -72
@@ -0,0 +1,204 @@
1
+ import { createTerrainSnapshot, editableTerrainFromSnapshot, } from "@jgengine/core/world/terraform";
2
+ import { resolveScatter, resolveScatterRegion, scatterRegionEstimate, scatterRegionFromPath, SCATTER_PATH_KIND, } from "@jgengine/core/world/scatterRegion";
3
+ import { TERRAIN_MATERIALS } from "../uiStore.js";
4
+ /** Sculpt heightfield, material painting, terrain layers, and foliage/scatter verbs. */
5
+ export const terrainHandlers = {
6
+ create_terrain: (ctx, request) => {
7
+ const width = request.width ?? 200;
8
+ const depth = request.depth ?? 200;
9
+ const cx = request.centerX ?? 0;
10
+ const cz = request.centerZ ?? 0;
11
+ const terrain = createTerrainSnapshot({
12
+ bounds: { minX: cx - width / 2, minZ: cz - depth / 2, maxX: cx + width / 2, maxZ: cz + depth / 2 },
13
+ cellSize: request.cellSize ?? 2,
14
+ });
15
+ ctx.session.dispatch({ type: "setTerrain", terrain });
16
+ return { ok: true, result: { cols: terrain.cols, rows: terrain.rows, cellSize: terrain.cellSize } };
17
+ },
18
+ sculpt_terrain: (ctx, request) => {
19
+ const terrain = ctx.session.getState().document.terrain;
20
+ if (terrain === undefined)
21
+ return { ok: false, error: "no terrain — call create_terrain first" };
22
+ const live = editableTerrainFromSnapshot(terrain);
23
+ const edit = {
24
+ mode: request.mode,
25
+ center: [request.x, request.z],
26
+ radius: request.radius ?? 8,
27
+ strength: request.strength ?? 1,
28
+ ...(request.target === undefined ? {} : { target: request.target }),
29
+ ...(request.toX === undefined || request.toZ === undefined ? {} : { to: [request.toX, request.toZ] }),
30
+ ...(request.seed === undefined ? {} : { seed: request.seed }),
31
+ ...(request.shape === undefined ? {} : { shape: request.shape }),
32
+ };
33
+ const delta = live.editDelta(edit);
34
+ if (delta.indices.length === 0)
35
+ return { ok: false, error: "brush touched no vertices (check radius/position)" };
36
+ ctx.session.dispatch({ type: "sculptTerrain", delta });
37
+ return { ok: true, result: { changed: delta.indices.length, canUndo: ctx.session.canUndo() } };
38
+ },
39
+ terrain_summary: (ctx) => {
40
+ const terrain = ctx.session.getState().document.terrain;
41
+ if (terrain === undefined)
42
+ return { ok: true, result: { terrain: null } };
43
+ let min = Infinity;
44
+ let max = -Infinity;
45
+ let nonZero = 0;
46
+ for (const value of terrain.offsets) {
47
+ if (value < min)
48
+ min = value;
49
+ if (value > max)
50
+ max = value;
51
+ if (value !== 0)
52
+ nonZero += 1;
53
+ }
54
+ return {
55
+ ok: true,
56
+ result: {
57
+ cols: terrain.cols,
58
+ rows: terrain.rows,
59
+ cellSize: terrain.cellSize,
60
+ bounds: terrain.bounds,
61
+ minOffset: terrain.offsets.length === 0 ? 0 : min,
62
+ maxOffset: terrain.offsets.length === 0 ? 0 : max,
63
+ editedVertices: nonZero,
64
+ paintedCells: terrain.surfaces.filter((surface) => surface !== null).length,
65
+ },
66
+ };
67
+ },
68
+ paint_terrain: (ctx, request) => {
69
+ const terrain = ctx.session.getState().document.terrain;
70
+ if (terrain === undefined)
71
+ return { ok: false, error: "no terrain — call create_terrain first" };
72
+ const live = editableTerrainFromSnapshot(terrain);
73
+ const delta = live.paintDelta({
74
+ mode: "paint",
75
+ center: [request.x, request.z],
76
+ radius: request.radius ?? 8,
77
+ surface: request.surface,
78
+ ...(request.shape === undefined ? {} : { shape: request.shape }),
79
+ });
80
+ if (delta.indices.length === 0)
81
+ return { ok: false, error: "paint touched no cells (check radius/position)" };
82
+ ctx.session.dispatch({ type: "paintTerrain", delta });
83
+ return { ok: true, result: { changed: delta.indices.length, canUndo: ctx.session.canUndo() } };
84
+ },
85
+ fill_terrain: (ctx, request) => {
86
+ const terrain = ctx.session.getState().document.terrain;
87
+ if (terrain === undefined)
88
+ return { ok: false, error: "no terrain — call create_terrain first" };
89
+ const live = editableTerrainFromSnapshot(terrain);
90
+ const delta = live.fillSurfaceDelta(request.surface);
91
+ if (delta.indices.length === 0)
92
+ return { ok: true, result: { changed: 0 } };
93
+ ctx.session.dispatch({ type: "paintTerrain", delta });
94
+ return { ok: true, result: { changed: delta.indices.length } };
95
+ },
96
+ auto_paint: (ctx, request) => {
97
+ const terrain = ctx.session.getState().document.terrain;
98
+ if (terrain === undefined)
99
+ return { ok: false, error: "no terrain — call create_terrain first" };
100
+ const live = editableTerrainFromSnapshot(terrain);
101
+ const rule = {
102
+ surface: request.surface,
103
+ ...(request.minSlope === undefined ? {} : { minSlope: request.minSlope }),
104
+ ...(request.maxSlope === undefined ? {} : { maxSlope: request.maxSlope }),
105
+ ...(request.minHeight === undefined ? {} : { minHeight: request.minHeight }),
106
+ ...(request.maxHeight === undefined ? {} : { maxHeight: request.maxHeight }),
107
+ };
108
+ const delta = live.autoPaintDelta(rule);
109
+ if (delta.indices.length === 0)
110
+ return { ok: true, result: { changed: 0 } };
111
+ ctx.session.dispatch({ type: "paintTerrain", delta });
112
+ return { ok: true, result: { changed: delta.indices.length } };
113
+ },
114
+ terrain_materials: () => ({ ok: true, result: { materials: TERRAIN_MATERIALS } }),
115
+ terrain_layers: (ctx) => {
116
+ const terrain = ctx.session.getState().document.terrain;
117
+ return { ok: true, result: { layers: terrain?.layers ?? [] } };
118
+ },
119
+ set_terrain_layers: (ctx, request) => {
120
+ if (ctx.session.getState().document.terrain === undefined) {
121
+ return { ok: false, error: "no terrain — call create_terrain first" };
122
+ }
123
+ ctx.session.dispatch({ type: "setTerrainLayers", layers: request.layers });
124
+ return { ok: true, result: { layers: ctx.session.getState().document.terrain?.layers ?? [] } };
125
+ },
126
+ blend_terrain: (ctx, request) => {
127
+ const terrain = ctx.session.getState().document.terrain;
128
+ if (terrain === undefined)
129
+ return { ok: false, error: "no terrain — call create_terrain first" };
130
+ // Auto-add the surface as a layer if the stack does not carry it yet.
131
+ const layers = terrain.layers ?? [];
132
+ if (!layers.some((layer) => layer.surface === request.surface)) {
133
+ const next = [...layers, { id: request.surface, surface: request.surface }];
134
+ ctx.session.dispatch({ type: "setTerrainLayers", layers: next });
135
+ }
136
+ const snapshot = ctx.session.getState().document.terrain;
137
+ if (snapshot === undefined)
138
+ return { ok: false, error: "no terrain — call create_terrain first" };
139
+ const live = editableTerrainFromSnapshot(snapshot);
140
+ const delta = live.blendPaintDelta({
141
+ mode: "paint",
142
+ center: [request.x, request.z],
143
+ radius: request.radius ?? 8,
144
+ surface: request.surface,
145
+ strength: request.strength ?? 1,
146
+ ...(request.shape === undefined ? {} : { shape: request.shape }),
147
+ });
148
+ if (delta.indices.length === 0)
149
+ return { ok: false, error: "blend touched no cells (check radius/position)" };
150
+ ctx.session.dispatch({ type: "blendTerrain", delta });
151
+ return { ok: true, result: { changed: delta.indices.length, layers: ctx.session.getState().document.terrain?.layers ?? [] } };
152
+ },
153
+ convert_scatter: (ctx, request) => {
154
+ const doc = ctx.session.getState().document;
155
+ const path = doc.paths.find((entry) => entry.id === request.pathId);
156
+ if (path === undefined)
157
+ return { ok: false, error: `path not found: ${request.pathId}` };
158
+ const region = scatterRegionFromPath(path);
159
+ if (region === null)
160
+ return { ok: false, error: `not a scatter region: ${request.pathId}` };
161
+ const terrainSnapshot = doc.terrain;
162
+ const terrain = terrainSnapshot === undefined ? undefined : editableTerrainFromSnapshot(terrainSnapshot);
163
+ const instances = resolveScatterRegion(region, terrain);
164
+ const markers = instances.map((instance) => ({
165
+ id: instance.id.replace(/[^a-zA-Z0-9_]/g, "_"),
166
+ kind: "prop",
167
+ position: { x: instance.x, y: instance.y, z: instance.z },
168
+ rotationY: instance.rotationY,
169
+ meta: { item: instance.item, scale: instance.scale, fromScatter: request.pathId },
170
+ }));
171
+ ctx.session.dispatch({ type: "convertScatterToObjects", pathId: request.pathId, markers });
172
+ return { ok: true, result: { created: markers.length, removedPath: request.pathId } };
173
+ },
174
+ add_foliage: (ctx, request) => {
175
+ if (request.points.length < 3)
176
+ return { ok: false, error: "add_foliage needs at least 3 polygon points" };
177
+ const id = `foliage_${Date.now().toString(36)}`;
178
+ ctx.session.dispatch({
179
+ type: "addPath",
180
+ path: {
181
+ id,
182
+ kind: SCATTER_PATH_KIND,
183
+ points: request.points.map((point) => ({ x: point.x, y: 0, z: point.z })),
184
+ label: "foliage",
185
+ meta: {
186
+ density: request.density ?? 0.15,
187
+ ...(request.item === undefined ? {} : { item: request.item }),
188
+ ...(request.seed === undefined ? {} : { seed: request.seed }),
189
+ ...(request.minSpacing === undefined ? {} : { minSpacing: request.minSpacing }),
190
+ },
191
+ },
192
+ });
193
+ const path = ctx.session.getState().document.paths.find((p) => p.id === id);
194
+ if (path === undefined)
195
+ return { ok: false, error: `add_foliage failed to create region: ${id}` };
196
+ return { ok: true, result: { id, estimate: scatterRegionEstimate(path) } };
197
+ },
198
+ scatter_summary: (ctx) => {
199
+ const doc = ctx.session.getState().document;
200
+ const regions = doc.paths.filter((path) => path.kind === SCATTER_PATH_KIND).length;
201
+ const instances = resolveScatter(doc).length;
202
+ return { ok: true, result: { regions, instances } };
203
+ },
204
+ };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { EditorApp, type EditorAppProps, type EditorSaveFn } from "./EditorApp.js";
2
- export { StandaloneEditor, createBlankPlayable, blankWorld, downloadSaver, type StandaloneEditorProps, type StandaloneAsset, type BlankPlayableOptions, } from "./StandaloneEditor.js";
2
+ export { buildEditorNetworkSnapshot, isNetworkMultiplayerConfigured, type EditorNetworkAdapterKind, type EditorNetworkPresenceActor, type EditorNetworkPresenceInput, type EditorNetworkSession, type EditorNetworkSnapshot, } from "./networkSnapshot.js";
3
+ export { StandaloneEditor, createBlankPlayable, blankWorld, downloadSaver, importAssetToHost, loadDroppedAssets, mergeStandaloneAssets, type StandaloneEditorProps, type StandaloneAsset, type AssetImporter, type BlankPlayableOptions, } from "./StandaloneEditor.js";
3
4
  export { EditorLayerOverlays, PathDraftPreview } from "./DebugDraw.js";
4
5
  export { EditorChrome } from "./EditorChrome.js";
5
6
  export { SelectionGizmo, ViewportSelect } from "./SelectionGizmo.js";
@@ -7,7 +8,7 @@ export { createEditorUiStore, newPlacementId, DEFAULT_SCULPT_SETTINGS, DEFAULT_P
7
8
  export { TerrainSculpt } from "./TerrainSculpt.js";
8
9
  export { ScatterPreview } from "./ScatterPreview.js";
9
10
  export { EditorCameraDriver } from "./EditorCameraDriver.js";
10
- export { AssetBrowser, assetsFromCatalog, type EditorAssetEntry } from "./AssetBrowser.js";
11
+ export { AssetBrowser, assetsFromCatalog, editorAssetFromImport, mergeEditorAssets, type EditorAssetEntry, } from "./AssetBrowser.js";
11
12
  export { createEditorHost, getEditorHost, installEditorHost, type EditorAssetInfo, type EditorBridgeRequest, type EditorBridgeResponse, type EditorHostApi, type EditorPerfSample, type EditorRunMode, } from "./session.js";
12
13
  export { PerfProbe } from "./PerfProbe.js";
13
14
  export { useStoreSelector, shallowArrayEqual, virtualWindow, type SubscribableStore, type VirtualWindow, } from "./useStoreSelector.js";
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { EditorApp } from "./EditorApp.js";
2
- export { StandaloneEditor, createBlankPlayable, blankWorld, downloadSaver, } from "./StandaloneEditor.js";
2
+ export { buildEditorNetworkSnapshot, isNetworkMultiplayerConfigured, } from "./networkSnapshot.js";
3
+ export { StandaloneEditor, createBlankPlayable, blankWorld, downloadSaver, importAssetToHost, loadDroppedAssets, mergeStandaloneAssets, } from "./StandaloneEditor.js";
3
4
  export { EditorLayerOverlays, PathDraftPreview } from "./DebugDraw.js";
4
5
  export { EditorChrome } from "./EditorChrome.js";
5
6
  export { SelectionGizmo, ViewportSelect } from "./SelectionGizmo.js";
@@ -7,7 +8,7 @@ export { createEditorUiStore, newPlacementId, DEFAULT_SCULPT_SETTINGS, DEFAULT_P
7
8
  export { TerrainSculpt } from "./TerrainSculpt.js";
8
9
  export { ScatterPreview } from "./ScatterPreview.js";
9
10
  export { EditorCameraDriver } from "./EditorCameraDriver.js";
10
- export { AssetBrowser, assetsFromCatalog } from "./AssetBrowser.js";
11
+ export { AssetBrowser, assetsFromCatalog, editorAssetFromImport, mergeEditorAssets, } from "./AssetBrowser.js";
11
12
  export { createEditorHost, getEditorHost, installEditorHost, } from "./session.js";
12
13
  export { PerfProbe } from "./PerfProbe.js";
13
14
  export { useStoreSelector, shallowArrayEqual, virtualWindow, } from "./useStoreSelector.js";
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Pure collect of authored behavior triggers from a scene document for the scripting workspace.
3
+ * Object-centric: one row per marker/volume that carries at least one valid on/action binding.
4
+ * Runtime dispatch still uses {@link collectAuthoredTriggers}; this is the editor list model.
5
+ */
6
+ import { type TriggerEvent, type TriggerSourceKind } from "@jgengine/core/scene/authoredTriggers";
7
+ import type { SceneDocumentLike, SceneMarkerLike, SceneVolumeLike } from "@jgengine/core/world/sceneShapes";
8
+ /** One on/action binding shown on a document trigger row. */
9
+ export interface DocumentTriggerBinding {
10
+ on: TriggerEvent;
11
+ action: string;
12
+ }
13
+ /**
14
+ * A marker or volume that owns one or more authored triggers, with a display label for the
15
+ * scripting workspace list.
16
+ */
17
+ export interface DocumentTriggerEntry {
18
+ sourceId: string;
19
+ sourceKind: TriggerSourceKind;
20
+ objectKind: string;
21
+ /** Marker/volume label when present; otherwise the object id. */
22
+ label: string;
23
+ bindings: readonly DocumentTriggerBinding[];
24
+ }
25
+ /** Document slice the list helper needs — `EditorDocument` satisfies this structurally. */
26
+ export interface DocumentTriggersSource {
27
+ markers: readonly (SceneMarkerLike & {
28
+ label?: string;
29
+ })[];
30
+ volumes: readonly (SceneVolumeLike & {
31
+ label?: string;
32
+ })[];
33
+ }
34
+ /**
35
+ * List every marker and volume on `document` that has at least one valid authored trigger.
36
+ * Pure — no session, registry, or runtime state. Markers are listed before volumes; order within
37
+ * each collection matches the document. Prefer this over {@link collectAuthoredTriggers} when the
38
+ * UI wants one row per object rather than one row per binding.
39
+ *
40
+ * @internal
41
+ */
42
+ export declare function listDocumentTriggers(document: DocumentTriggersSource | SceneDocumentLike): DocumentTriggerEntry[];
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Pure collect of authored behavior triggers from a scene document for the scripting workspace.
3
+ * Object-centric: one row per marker/volume that carries at least one valid on/action binding.
4
+ * Runtime dispatch still uses {@link collectAuthoredTriggers}; this is the editor list model.
5
+ */
6
+ import { readTriggerSpecs, } from "@jgengine/core/scene/authoredTriggers";
7
+ function labelOf(object) {
8
+ const label = object.label;
9
+ return typeof label === "string" && label.length > 0 ? label : object.id;
10
+ }
11
+ function bindingsFromMeta(meta) {
12
+ return readTriggerSpecs(meta).map((spec) => ({ on: spec.on, action: spec.action }));
13
+ }
14
+ /**
15
+ * List every marker and volume on `document` that has at least one valid authored trigger.
16
+ * Pure — no session, registry, or runtime state. Markers are listed before volumes; order within
17
+ * each collection matches the document. Prefer this over {@link collectAuthoredTriggers} when the
18
+ * UI wants one row per object rather than one row per binding.
19
+ *
20
+ * @internal
21
+ */
22
+ export function listDocumentTriggers(document) {
23
+ const out = [];
24
+ for (const marker of document.markers) {
25
+ const bindings = bindingsFromMeta(marker.meta);
26
+ if (bindings.length === 0)
27
+ continue;
28
+ out.push({
29
+ sourceId: marker.id,
30
+ sourceKind: "marker",
31
+ objectKind: marker.kind,
32
+ label: labelOf(marker),
33
+ bindings,
34
+ });
35
+ }
36
+ for (const volume of document.volumes) {
37
+ const bindings = bindingsFromMeta(volume.meta);
38
+ if (bindings.length === 0)
39
+ continue;
40
+ out.push({
41
+ sourceId: volume.id,
42
+ sourceKind: "volume",
43
+ objectKind: volume.kind,
44
+ label: labelOf(volume),
45
+ bindings,
46
+ });
47
+ }
48
+ return out;
49
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Document-wide material assignment inventory for the Materials workspace.
3
+ * Reads real `meta.materialId` stamps from placeables — no thumbnails or faked previews.
4
+ */
5
+ /** Placeable families that carry `meta.materialId` under assign_material. */
6
+ export type MaterialObjectKind = "marker" | "volume" | "path";
7
+ /** One object row in the materials workspace browser. */
8
+ export interface MaterialAssignmentRow {
9
+ id: string;
10
+ label: string;
11
+ kind: string;
12
+ objectKind: MaterialObjectKind;
13
+ /** Present material id, or null when none is assigned. */
14
+ materialId: string | null;
15
+ }
16
+ /** Minimal document slice the materials inventory needs. */
17
+ export interface MaterialDocumentSlice {
18
+ markers: readonly {
19
+ id: string;
20
+ kind: string;
21
+ label?: string;
22
+ meta?: Record<string, unknown>;
23
+ }[];
24
+ volumes: readonly {
25
+ id: string;
26
+ kind: string;
27
+ label?: string;
28
+ meta?: Record<string, unknown>;
29
+ }[];
30
+ paths: readonly {
31
+ id: string;
32
+ kind: string;
33
+ label?: string;
34
+ meta?: Record<string, unknown>;
35
+ }[];
36
+ }
37
+ /** Filter for {@link filterMaterialAssignments}. */
38
+ export type MaterialAssignmentFilter = "all" | "assigned" | "unassigned" | {
39
+ materialId: string;
40
+ };
41
+ /**
42
+ * Lists every marker/volume/path with its current material assignment (or null).
43
+ * Order is document order: markers, then volumes, then paths.
44
+ *
45
+ * @internal
46
+ */
47
+ export declare function listMaterialAssignments(document: MaterialDocumentSlice): MaterialAssignmentRow[];
48
+ /**
49
+ * Filters material rows by text query (id/label/kind/materialId) and optional assignment filter.
50
+ * Case-insensitive; empty query is a no-op on text.
51
+ *
52
+ * @internal
53
+ */
54
+ export declare function filterMaterialAssignments(rows: readonly MaterialAssignmentRow[], query: string, filter?: MaterialAssignmentFilter): MaterialAssignmentRow[];
55
+ /**
56
+ * Aggregates how many placeables use each material id (assigned only). Sorted by count desc, then id.
57
+ *
58
+ * @internal
59
+ */
60
+ export declare function summarizeMaterialUsage(rows: readonly MaterialAssignmentRow[]): readonly {
61
+ materialId: string;
62
+ count: number;
63
+ }[];
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Document-wide material assignment inventory for the Materials workspace.
3
+ * Reads real `meta.materialId` stamps from placeables — no thumbnails or faked previews.
4
+ */
5
+ function readMaterialId(meta) {
6
+ const value = meta?.["materialId"];
7
+ return typeof value === "string" && value.length > 0 ? value : null;
8
+ }
9
+ /**
10
+ * Lists every marker/volume/path with its current material assignment (or null).
11
+ * Order is document order: markers, then volumes, then paths.
12
+ *
13
+ * @internal
14
+ */
15
+ export function listMaterialAssignments(document) {
16
+ const rows = [];
17
+ for (const marker of document.markers) {
18
+ rows.push({
19
+ id: marker.id,
20
+ label: marker.label ?? marker.id,
21
+ kind: marker.kind,
22
+ objectKind: "marker",
23
+ materialId: readMaterialId(marker.meta),
24
+ });
25
+ }
26
+ for (const volume of document.volumes) {
27
+ rows.push({
28
+ id: volume.id,
29
+ label: volume.label ?? volume.id,
30
+ kind: volume.kind,
31
+ objectKind: "volume",
32
+ materialId: readMaterialId(volume.meta),
33
+ });
34
+ }
35
+ for (const path of document.paths) {
36
+ rows.push({
37
+ id: path.id,
38
+ label: path.label ?? path.id,
39
+ kind: path.kind,
40
+ objectKind: "path",
41
+ materialId: readMaterialId(path.meta),
42
+ });
43
+ }
44
+ return rows;
45
+ }
46
+ /**
47
+ * Filters material rows by text query (id/label/kind/materialId) and optional assignment filter.
48
+ * Case-insensitive; empty query is a no-op on text.
49
+ *
50
+ * @internal
51
+ */
52
+ export function filterMaterialAssignments(rows, query, filter = "all") {
53
+ const needle = query.trim().toLowerCase();
54
+ return rows.filter((row) => {
55
+ if (filter === "assigned" && row.materialId === null)
56
+ return false;
57
+ if (filter === "unassigned" && row.materialId !== null)
58
+ return false;
59
+ if (typeof filter === "object" && row.materialId !== filter.materialId)
60
+ return false;
61
+ if (needle.length === 0)
62
+ return true;
63
+ return (row.id.toLowerCase().includes(needle) ||
64
+ row.label.toLowerCase().includes(needle) ||
65
+ row.kind.toLowerCase().includes(needle) ||
66
+ (row.materialId !== null && row.materialId.toLowerCase().includes(needle)));
67
+ });
68
+ }
69
+ /**
70
+ * Aggregates how many placeables use each material id (assigned only). Sorted by count desc, then id.
71
+ *
72
+ * @internal
73
+ */
74
+ export function summarizeMaterialUsage(rows) {
75
+ const counts = new Map();
76
+ for (const row of rows) {
77
+ if (row.materialId === null)
78
+ continue;
79
+ counts.set(row.materialId, (counts.get(row.materialId) ?? 0) + 1);
80
+ }
81
+ return [...counts.entries()]
82
+ .map(([materialId, count]) => ({ materialId, count }))
83
+ .sort((a, b) => b.count - a.count || a.materialId.localeCompare(b.materialId));
84
+ }
@@ -0,0 +1,32 @@
1
+ import type { EditorBridgeRequest } from "../session.js";
2
+ /** The value type a request field is expected to carry. */
3
+ export type RpcFieldKind = "string" | "number" | "boolean" | "string[]" | "object" | "object[]" | "vec3" | "value";
4
+ /** One field's expected shape in a request; `oneOf`/`nullable`/`required`/`of` refine the check. */
5
+ export interface RpcFieldSpec {
6
+ name: string;
7
+ kind: RpcFieldKind;
8
+ nullable?: boolean;
9
+ oneOf?: readonly string[];
10
+ /** True when the field is mandatory — surfaced in the generated MCP `required` array. */
11
+ required?: boolean;
12
+ /** For `object`/`object[]`: the element/object's own field specs, so nested schemas stay expressive. */
13
+ of?: readonly RpcFieldSpec[];
14
+ }
15
+ /**
16
+ * Per-method field type table — the single source of truth for the RPC boundary. A field listed here
17
+ * is type-checked when present (see `decodeEditorBridgeRequest`); existence is left to
18
+ * `EditorHostApi.handle` (whose per-method guards return honest `ok:false` diagnostics for
19
+ * missing/unknown targets). Type-checking presented fields is what keeps a fuzzed value — a string
20
+ * where a number belongs, a scalar where an object belongs — from reaching a live session uncoerced.
21
+ * Unknown extra fields are ignored so a superset payload stays forward-compatible.
22
+ *
23
+ * The same table generates the MCP tool `inputSchema`s (see `mcp/tools.ts`), so `required`/`oneOf`/
24
+ * `of` here also shape what agents are told a tool accepts. The `Record<EditorBridgeRequest["method"],
25
+ * …>` type is the lockstep guarantee: adding a method to the union in `session.ts` fails to compile
26
+ * here until it gets a schema, and a stale entry is a compile error too.
27
+ */
28
+ export declare const RPC_FIELD_SCHEMAS: Record<EditorBridgeRequest["method"], readonly RpcFieldSpec[]>;
29
+ /** The set of known RPC method names — exported for meta/manifest tooling and tests. @internal */
30
+ export declare const EDITOR_BRIDGE_METHOD_NAMES: readonly string[];
31
+ /** Generates the MCP tool `inputSchema` for a method from its {@link RPC_FIELD_SCHEMAS} entry. */
32
+ export declare function buildInputSchema(method: EditorBridgeRequest["method"]): Record<string, unknown>;