@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.
- package/CHANGELOG.md +52 -0
- package/README.md +12 -0
- package/dist/AssetBrowser.d.ts +33 -1
- package/dist/AssetBrowser.js +64 -2
- package/dist/AssetDropZone.d.ts +9 -0
- package/dist/AssetDropZone.js +65 -0
- package/dist/CatalogsPanel.d.ts +4 -2
- package/dist/CatalogsPanel.js +162 -9
- package/dist/CityPreview.d.ts +11 -0
- package/dist/CityPreview.js +45 -0
- package/dist/CollectionsPanel.js +5 -5
- package/dist/DebugDraw.d.ts +3 -1
- package/dist/DebugDraw.js +37 -19
- package/dist/EditorApp.d.ts +15 -1
- package/dist/EditorApp.js +147 -35
- package/dist/EditorChrome.d.ts +27 -6
- package/dist/EditorChrome.js +314 -130
- package/dist/InspectorPanel.d.ts +17 -7
- package/dist/InspectorPanel.js +173 -27
- package/dist/LightingPanel.d.ts +11 -0
- package/dist/LightingPanel.js +99 -0
- package/dist/MaterialsWorkspacePanel.d.ts +12 -0
- package/dist/MaterialsWorkspacePanel.js +86 -0
- package/dist/ParentPickerMenu.d.ts +13 -0
- package/dist/ParentPickerMenu.js +33 -0
- package/dist/PerfProbe.js +33 -0
- package/dist/PrefabsPanel.js +5 -5
- package/dist/RuntimePlayBridge.d.ts +5 -3
- package/dist/RuntimePlayBridge.js +6 -13
- package/dist/ScatterPreview.d.ts +3 -1
- package/dist/ScatterPreview.js +10 -7
- package/dist/SchemaInspector.js +21 -8
- package/dist/ScriptingPanel.d.ts +15 -0
- package/dist/ScriptingPanel.js +46 -0
- package/dist/SelectionGizmo.js +114 -30
- package/dist/StandaloneEditor.d.ts +16 -9
- package/dist/StandaloneEditor.js +19 -20
- package/dist/TerrainPanel.d.ts +3 -2
- package/dist/TerrainPanel.js +16 -5
- package/dist/TerrainSculpt.js +98 -59
- package/dist/agent/AgentPanel.d.ts +5 -2
- package/dist/agent/AgentPanel.js +11 -6
- package/dist/agent/toolBridge.js +5 -0
- package/dist/assetImport.d.ts +36 -0
- package/dist/assetImport.js +66 -0
- package/dist/authoredComponentMeta.d.ts +25 -0
- package/dist/authoredComponentMeta.js +65 -0
- package/dist/gizmoPivot.d.ts +4 -0
- package/dist/gizmoPivot.js +34 -0
- package/dist/handlers/catalogs.d.ts +3 -0
- package/dist/handlers/catalogs.js +137 -0
- package/dist/handlers/collections.d.ts +3 -0
- package/dist/handlers/collections.js +57 -0
- package/dist/handlers/context.d.ts +36 -0
- package/dist/handlers/context.js +1 -0
- package/dist/handlers/document.d.ts +3 -0
- package/dist/handlers/document.js +350 -0
- package/dist/handlers/grids.d.ts +3 -0
- package/dist/handlers/grids.js +142 -0
- package/dist/handlers/hierarchy.d.ts +3 -0
- package/dist/handlers/hierarchy.js +55 -0
- package/dist/handlers/index.d.ts +8 -0
- package/dist/handlers/index.js +25 -0
- package/dist/handlers/minimap.d.ts +9 -0
- package/dist/handlers/minimap.js +32 -0
- package/dist/handlers/runtime.d.ts +3 -0
- package/dist/handlers/runtime.js +191 -0
- package/dist/handlers/streets.d.ts +3 -0
- package/dist/handlers/streets.js +159 -0
- package/dist/handlers/terrain.d.ts +3 -0
- package/dist/handlers/terrain.js +204 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/listDocumentTriggers.d.ts +42 -0
- package/dist/listDocumentTriggers.js +49 -0
- package/dist/materialAssignments.d.ts +63 -0
- package/dist/materialAssignments.js +84 -0
- package/dist/mcp/fieldSpec.d.ts +32 -0
- package/dist/mcp/fieldSpec.js +174 -0
- package/dist/mcp/rpcRequest.d.ts +8 -5
- package/dist/mcp/rpcRequest.js +72 -93
- package/dist/mcp/tools.js +105 -912
- package/dist/networkSnapshot.d.ts +82 -0
- package/dist/networkSnapshot.js +73 -0
- package/dist/parentCandidates.d.ts +12 -0
- package/dist/parentCandidates.js +25 -0
- package/dist/session.d.ts +91 -4
- package/dist/session.js +36 -1017
- package/dist/shell/AnimationPanel.d.ts +17 -0
- package/dist/shell/AnimationPanel.js +134 -0
- package/dist/shell/AssetThumbnail.d.ts +12 -0
- package/dist/shell/AssetThumbnail.js +23 -0
- package/dist/shell/BottomDock.d.ts +25 -0
- package/dist/shell/BottomDock.js +40 -0
- package/dist/shell/CommandPalette.d.ts +12 -0
- package/dist/shell/CommandPalette.js +56 -0
- package/dist/shell/ConsolePanel.d.ts +8 -0
- package/dist/shell/ConsolePanel.js +45 -0
- package/dist/shell/ContentBrowser.d.ts +21 -0
- package/dist/shell/ContentBrowser.js +130 -0
- package/dist/shell/HierarchyPanel.d.ts +26 -0
- package/dist/shell/HierarchyPanel.js +387 -0
- package/dist/shell/NetworkWorkspacePanel.d.ts +11 -0
- package/dist/shell/NetworkWorkspacePanel.js +49 -0
- package/dist/shell/PlayModeBar.d.ts +12 -0
- package/dist/shell/PlayModeBar.js +27 -0
- package/dist/shell/ProfilerPanel.d.ts +10 -0
- package/dist/shell/ProfilerPanel.js +41 -0
- package/dist/shell/SceneToolbar.d.ts +43 -0
- package/dist/shell/SceneToolbar.js +122 -0
- package/dist/shell/StatusBar.d.ts +18 -0
- package/dist/shell/StatusBar.js +41 -0
- package/dist/shell/TopAppBar.d.ts +35 -0
- package/dist/shell/TopAppBar.js +64 -0
- package/dist/shell/ViewportOverlays.d.ts +23 -0
- package/dist/shell/ViewportOverlays.js +136 -0
- package/dist/shell/WorkspaceRail.d.ts +20 -0
- package/dist/shell/WorkspaceRail.js +31 -0
- package/dist/shell/cameraTelemetry.d.ts +27 -0
- package/dist/shell/cameraTelemetry.js +24 -0
- package/dist/shell/commandRegistry.d.ts +58 -0
- package/dist/shell/commandRegistry.js +133 -0
- package/dist/shell/consoleSink.d.ts +11 -0
- package/dist/shell/consoleSink.js +17 -0
- package/dist/shell/consoleStore.d.ts +27 -0
- package/dist/shell/consoleStore.js +43 -0
- package/dist/shell/fields.d.ts +39 -0
- package/dist/shell/fields.js +104 -0
- package/dist/shell/formatSavedRelative.d.ts +2 -0
- package/dist/shell/formatSavedRelative.js +14 -0
- package/dist/shell/glbThumbnailCache.d.ts +45 -0
- package/dist/shell/glbThumbnailCache.js +241 -0
- package/dist/shell/hierarchyA11y.d.ts +21 -0
- package/dist/shell/hierarchyA11y.js +35 -0
- package/dist/shell/icons.d.ts +13 -0
- package/dist/shell/icons.js +85 -0
- package/dist/shell/layoutStore.d.ts +68 -0
- package/dist/shell/layoutStore.js +156 -0
- package/dist/shell/pathFlythrough.d.ts +37 -0
- package/dist/shell/pathFlythrough.js +54 -0
- package/dist/shell/perfHistory.d.ts +70 -0
- package/dist/shell/perfHistory.js +130 -0
- package/dist/shell/renameObject.d.ts +3 -0
- package/dist/shell/renameObject.js +25 -0
- package/dist/shell/theme.d.ts +36 -0
- package/dist/shell/theme.js +37 -0
- package/dist/shell/ui.d.ts +84 -0
- package/dist/shell/ui.js +90 -0
- package/dist/shell/useGlbThumbnail.d.ts +8 -0
- package/dist/shell/useGlbThumbnail.js +19 -0
- package/dist/uiStore.d.ts +27 -0
- package/dist/uiStore.js +10 -0
- package/dist/viewportContextMenu.d.ts +4 -2
- package/dist/viewportContextMenu.js +5 -1
- package/package.json +4 -4
- package/skills/jgengine/SKILL.md +8 -4
- package/skills/jgengine/api.md +182 -81
- package/skills/jgengine/capabilities.md +104 -2
- package/skills/jgengine/recipes/minimal-game.md +88 -0
- package/skills/jgengine-editor/SKILL.md +18 -3
- package/skills/jgengine-editor/api.md +403 -20
- package/skills/jgengine-editor/capabilities.md +46 -0
- package/skills/jgengine-editor/reference.md +87 -0
- package/skills/level-design/SKILL.md +54 -0
- package/skills/level-design/agents/openai.yaml +4 -0
- package/skills/level-design/references/application-playbook.md +192 -0
- package/skills/level-design/references/field-guide.md +337 -0
- package/skills/level-design/references/live-editor-loop.md +179 -0
- package/dist/OutlinerPanel.d.ts +0 -14
- package/dist/OutlinerPanel.js +0 -84
- 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 {
|
|
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 {
|
|
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>;
|