@vgai/editor-sdk 0.5.11 → 0.5.13

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/README.md CHANGED
@@ -42,8 +42,9 @@ One export, `EditorClient`, plus its types (`EditorState`, `ProjectInfo`,
42
42
  - Panels: `showViewport('scene'|'game')`, `showInspector`, `openAsset`,
43
43
  `closeAsset`, `toggleConsole`, `toggleCommandPalette`, `showBuild`, and
44
44
  `present(EditorView)` for an atomic human-visible view plus share URL
45
- - Display: `setGrid`, `setHelpers`, `setStats`, `setShadingMode` (`solid`,
46
- `unlit`, `wireframe`, `normals`, or `overdraw`), `setHelperType` (including
45
+ - Display: `setGrid`, `setHelpers`, `setStats`, `setShadingMode` (`solid` for
46
+ authored materials, `clay` for neutral flat shading, `unlit`, `wireframe`,
47
+ `normals`, or `overdraw`), `setHelperType` (including
47
48
  the independent `bounds` category). Shading targets the active Scene/Game
48
49
  viewport and remains render-only, session-local state.
49
50
  - Transform tools: `setTransformMode`, `setTransformSpace`, `setSnap`
@@ -99,7 +100,7 @@ utilities. Badges are for short statuses and counts, not headings.
99
100
  ### Extension contract (`@vgai/editor-sdk/extension`)
100
101
 
101
102
  The typed contract for everything a game project contributes TO the editor —
102
- three surfaces, one degradation ladder (`ExtensionContributionTier`:
103
+ three surfaces, one outcome vocabulary (`ExtensionContributionState`:
103
104
  `active` / `absent` / `failed`). An absent contribution hides its surface
104
105
  (the editor never fabricates placeholder data); a failing one is contained
105
106
  per-contribution and reported loudly on the editor console — never a crashed
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@vgai/editor-sdk",
3
3
  "author": "Volter AI, Inc.",
4
4
  "license": "Apache-2.0",
5
- "version": "0.5.11",
5
+ "version": "0.5.13",
6
6
  "type": "module",
7
7
  "repository": {
8
8
  "type": "git",
@@ -18,13 +18,15 @@
18
18
  "exports": {
19
19
  ".": "./src/index.ts",
20
20
  "./contributions": "./src/contributions.ts",
21
+ "./document-probe": "./src/document-probe.ts",
21
22
  "./extension": "./src/extension.ts"
22
23
  },
23
24
  "dependencies": {
24
25
  "@types/three": "^0.180.0",
25
- "@vgai/sdk": "0.5.11"
26
+ "@vgai/sdk": "0.5.13"
26
27
  },
27
28
  "peerDependencies": {
29
+ "@vgai/engine": "*",
28
30
  "react": "^19.0.0",
29
31
  "three": "^0.180.0"
30
32
  }
package/src/client.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { GenerationJobsDocument } from '@vgai/sdk/generations';
2
+ import type { DocumentProbeResult, DocumentProbeStep } from './document-probe.js';
2
3
  import type {
3
4
  ActiveDocumentCapture,
4
5
  AssetCompareCapture,
@@ -12,6 +13,7 @@ import type {
12
13
  EditorView,
13
14
  GameCapture,
14
15
  HelperVisibility,
16
+ InspectedHierarchy,
15
17
  InspectedInspection,
16
18
  LabeledShotSetCapture,
17
19
  PresentedEditorView,
@@ -113,6 +115,16 @@ const COMMAND_DEADLINE_MS = 150_000;
113
115
  * `code: 'DEBUG_COMMAND_NOT_REGISTERED'`. A panel decides what to show for
114
116
  * those; the client never invents one.
115
117
  */
118
+ /** What one undo/redo step reports back — `moved` is false when there was
119
+ * nothing left in that direction, which is an answer, not an error. */
120
+ export interface HistoryStep {
121
+ readonly moved: boolean;
122
+ readonly canUndo: boolean;
123
+ readonly canRedo: boolean;
124
+ readonly undoLabel: string | null;
125
+ readonly redoLabel: string | null;
126
+ }
127
+
116
128
  export interface GameDebugDoor {
117
129
  /**
118
130
  * Read ONE registered state provider by name (`'bot.tester'`). `undefined`
@@ -533,6 +545,55 @@ export class EditorClient {
533
545
  return data.subject;
534
546
  }
535
547
 
548
+ /**
549
+ * The HIERARCHY PANEL's actual rendered row tree, as data.
550
+ *
551
+ * The same rows a human is looking at: the adapter's tree after the component
552
+ * marks fold implementation subtrees, after the internals reveal, after the
553
+ * document promotion, the child cap, the collapse state, the search filter
554
+ * and the selection scope. Works in play mode and edit mode alike — the
555
+ * answer reports which (`playState`, `activeViewportTab`), because a
556
+ * play-mode tree and an edit-mode tree come from different adapters.
557
+ *
558
+ * Deliberately NOT `status().entities`, which walks the raw adapter tree and
559
+ * therefore answers a different question: a panel defect is invisible in it.
560
+ *
561
+ * Each row carries `childCount` (what its caret opens), `internalChildCount`
562
+ * (what is folded behind "Reveal Internals") and `expandable` (whether the
563
+ * panel draws a caret at all) — so "this subtree exists but the UI offers no
564
+ * way to open it" is a readable fact rather than something only a human
565
+ * squinting at the panel can notice.
566
+ *
567
+ * Rejects, naming the panel, when no hierarchy panel is mounted: an empty
568
+ * tree would be a fabricated answer about a surface nobody is being shown.
569
+ */
570
+ async hierarchy(): Promise<InspectedHierarchy> {
571
+ const data = await this.command<{ hierarchy: InspectedHierarchy }>({ type: 'hierarchy' });
572
+ return data.hierarchy;
573
+ }
574
+
575
+ /** Write one editable path through the active Inspector's own IO. */
576
+ async setInspectionField(path: string, value: unknown): Promise<InspectedInspection> {
577
+ const data = await this.command<{ subject: InspectedInspection }>({
578
+ type: 'set-inspection-field',
579
+ path,
580
+ value,
581
+ });
582
+ return data.subject;
583
+ }
584
+
585
+ /**
586
+ * Undo / redo one project transaction — the same queue the keyboard shortcut
587
+ * drives. `moved` is false when there was nothing left in that direction.
588
+ */
589
+ async undo(): Promise<HistoryStep> {
590
+ return this.command<HistoryStep>({ type: 'undo' });
591
+ }
592
+
593
+ async redo(): Promise<HistoryStep> {
594
+ return this.command<HistoryStep>({ type: 'redo' });
595
+ }
596
+
536
597
  /** Read the editor's actual current durable projection. */
537
598
  async currentView(): Promise<EditorView> {
538
599
  const data = await this.command<{ view: EditorView }>({ type: 'current-view' });
@@ -547,8 +608,35 @@ export class EditorClient {
547
608
  });
548
609
  }
549
610
 
611
+ /**
612
+ * Read or drive the ACTIVE center document's own DOM — the scoped
613
+ * editor-chrome door, and the read/gesture half of the same subject
614
+ * {@link captureActiveDocument} photographs. NOT play-mode gated, and NOT
615
+ * page automation: a target outside the active document's container is
616
+ * refused by name. Design and scope contract:
617
+ * `packages/editor/src/editor-document-probe.ts`.
618
+ */
619
+ async documentProbe(step: DocumentProbeStep): Promise<DocumentProbeResult> {
620
+ return this.command<DocumentProbeResult>({ type: 'document-probe', step });
621
+ }
622
+
550
623
  // --- Display (set semantics) ---
551
624
 
625
+ /**
626
+ * The Game document's "Persist to game source" consent, over the relay —
627
+ * the same session-scoped switch the checkbox flips, refused for the same
628
+ * reasons (it answers with the server's own words when the base cannot be
629
+ * written). `recorder` names who accounts for the diff a write produces.
630
+ */
631
+ async setSourcePersistConsent(
632
+ enabled: boolean,
633
+ ): Promise<{ enabled: boolean; recorder: string | null }> {
634
+ return this.command<{ enabled: boolean; recorder: string | null }>({
635
+ type: 'set-source-persist-consent',
636
+ enabled,
637
+ });
638
+ }
639
+
552
640
  async setGrid(enabled: boolean): Promise<void> {
553
641
  await this.command({ type: 'set-grid', enabled });
554
642
  }
@@ -4,13 +4,17 @@
4
4
  * metadata names a module and the editor renders its default export.
5
5
  */
6
6
 
7
+ import type { AuthoringAdapter } from '@vgai/engine/adapter';
7
8
  import type { GenerationAccountProjection } from '@vgai/sdk/account';
8
9
  import type { GenerationJob } from '@vgai/sdk/generations';
9
10
  import type {
10
11
  AnimationClip,
11
12
  Camera,
12
13
  ColorRepresentation,
14
+ Group,
15
+ Intersection,
13
16
  Object3D,
17
+ Ray,
14
18
  Scene,
15
19
  WebGLRenderer,
16
20
  } from 'three';
@@ -128,9 +132,142 @@ export interface ToolObject3DAuthoringProps {
128
132
  * Without this binding the animation workspace remains honestly read-only.
129
133
  */
130
134
  readonly animationSource?: ToolAnimationSourceBinding;
135
+ /**
136
+ * Project-owned serialization for this authored document. Each serializer
137
+ * returns the exact bytes of one ordinary project file; the host writes all
138
+ * resources as one checksum-guarded, failure-atomic history transaction.
139
+ * The project owns the format and the host never interprets its contents.
140
+ */
141
+ readonly persistence?: ToolObject3DDocumentPersistence;
142
+ /** Replace the default native-tree projection with a semantic adapter. The
143
+ * default adapter is provided for delegation, so a project can add terrain
144
+ * layers, bones, or mesh elements without rebuilding Object3D projection. */
145
+ readonly authoring?: ToolObject3DDocumentAuthoringFactory;
146
+ /** Project-owned direct manipulation hosted by the editor's input and
147
+ * transient-overlay lifecycle. Completed gestures commit `persistence`; an
148
+ * Escape, unmount, thrown callback, or failed write calls `cancel`. */
149
+ readonly interaction?: ToolObject3DDocumentInteraction;
131
150
  readonly active?: boolean;
132
151
  }
133
152
 
153
+ /** The native document state handed back to project-owned serializers. */
154
+ export interface ToolObject3DDocumentState {
155
+ readonly root: Object3D;
156
+ readonly animations: readonly AnimationClip[];
157
+ }
158
+
159
+ /** One project-owned artifact participating in an Asset Lab commit. */
160
+ export interface ToolObject3DDocumentResource {
161
+ /** Project-root-relative destination. */
162
+ readonly path: string;
163
+ /** MIME type recorded in canonical history. */
164
+ readonly contentType?: string;
165
+ /** `null` removes the file in the same atomic transaction. */
166
+ readonly serialize: (
167
+ document: ToolObject3DDocumentState,
168
+ ) => string | Uint8Array | null | Promise<string | Uint8Array | null>;
169
+ }
170
+
171
+ export interface ToolObject3DDocumentPersistence {
172
+ /** Default undo/redo label. A completed gesture may supply a narrower one. */
173
+ readonly label?: string;
174
+ /**
175
+ * True only when these resources serialize mutations to `document.animations`.
176
+ * Sidecar-only bindings still make direct manipulation writable, but must
177
+ * leave animation keys read-only rather than pretending to save them.
178
+ */
179
+ readonly persistsAnimations?: boolean;
180
+ /** Non-empty, path-unique set of files owned by this document. */
181
+ readonly resources: readonly ToolObject3DDocumentResource[];
182
+ }
183
+
184
+ export interface ToolObject3DDocumentAuthoringContext extends ToolObject3DDocumentState {
185
+ readonly documentId: string;
186
+ readonly sourcePath: string;
187
+ readonly scene: Scene;
188
+ readonly defaultAdapter: AuthoringAdapter;
189
+ /**
190
+ * Serialize this project-owned document through its declared persistence
191
+ * resources and record the change in canonical history. Rejects when the
192
+ * document is read-only or the atomic write fails.
193
+ */
194
+ readonly commit: (label?: string) => Promise<boolean>;
195
+ }
196
+
197
+ export interface ToolObject3DDocumentAuthoring {
198
+ readonly adapter: AuthoringAdapter;
199
+ dispose?(): void;
200
+ }
201
+
202
+ export type ToolObject3DDocumentAuthoringFactory = (
203
+ context: ToolObject3DDocumentAuthoringContext,
204
+ ) => ToolObject3DDocumentAuthoring;
205
+
206
+ /** Adapter-native hit data for a project-owned Asset Lab gesture. */
207
+ export interface ToolObject3DPointerEvent {
208
+ readonly pointerId: number;
209
+ readonly button: number;
210
+ readonly buttons: number;
211
+ readonly clientX: number;
212
+ readonly clientY: number;
213
+ readonly altKey: boolean;
214
+ readonly ctrlKey: boolean;
215
+ readonly metaKey: boolean;
216
+ readonly shiftKey: boolean;
217
+ readonly ray: Ray;
218
+ readonly hits: readonly Intersection<Object3D>[];
219
+ }
220
+
221
+ /** One gesture owns exact rollback to its pre-begin document state. */
222
+ export interface ToolObject3DGesture {
223
+ readonly label?: string;
224
+ update(event: ToolObject3DPointerEvent): void;
225
+ /** Finalize the project-owned native state before the host serializes it. */
226
+ commit(event: ToolObject3DPointerEvent): void | Promise<void>;
227
+ /** Restore exact pre-begin native state. Must be safe after partial commit. */
228
+ cancel(): void | Promise<void>;
229
+ }
230
+
231
+ export interface ToolObject3DInteractionContext {
232
+ readonly root: Object3D;
233
+ readonly scene: Scene;
234
+ readonly renderer: WebGLRenderer;
235
+ /** Host-owned group removed on document teardown. Project code owns GPU
236
+ * resources it adds and releases them from the extension's `dispose`. */
237
+ readonly overlay: Group;
238
+ /** Live camera getter; projection can change while the document is open. */
239
+ readonly camera: () => Camera;
240
+ /** False means gestures will not begin because no persistence binding exists. */
241
+ readonly writable: boolean;
242
+ }
243
+
244
+ export interface ToolObject3DInteractionExtension {
245
+ /** Return null to yield this pointer to the ordinary viewport controls. */
246
+ begin(event: ToolObject3DPointerEvent): ToolObject3DGesture | null;
247
+ /**
248
+ * Optional pre-sample phase. Restore any transforms changed by the previous
249
+ * frame's refinement here; the host samples animation after this call and
250
+ * invokes {@link update} with the resulting native pose.
251
+ */
252
+ prepareFrame?(deltaSeconds: number, context: ToolObject3DFrameContext): void;
253
+ /**
254
+ * Optional per-frame refinement after the host samples the active animation
255
+ * clip. Use this for project-owned constraints and overlay presentation.
256
+ */
257
+ update?(deltaSeconds: number, context: ToolObject3DFrameContext): void;
258
+ dispose(): void;
259
+ }
260
+
261
+ export interface ToolObject3DFrameContext {
262
+ /** For `prepareFrame`, true when the host will sample animation next. For
263
+ * `update`, true when it sampled an animation clip immediately beforehand. */
264
+ readonly animationSampled: boolean;
265
+ }
266
+
267
+ export interface ToolObject3DDocumentInteraction {
268
+ setup(context: ToolObject3DInteractionContext): ToolObject3DInteractionExtension;
269
+ }
270
+
134
271
  export interface ToolAnimationSourceBinding {
135
272
  /** Project-relative TypeScript file below src/. */
136
273
  readonly path: string;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The wire vocabulary of the ACTIVE-DOCUMENT probe — the scoped editor-chrome
3
+ * door (`packages/editor/src/editor-document-probe.ts` implements it,
4
+ * `@vgai/live`'s `editor.document` binds it, and that module's header carries
5
+ * the design decision and the scope contract).
6
+ *
7
+ * Deliberately NOT a page-automation vocabulary: there is no navigation, no
8
+ * waiting, no frame/window addressing and no selector rooted anywhere but the
9
+ * active document's own container. Four actions, each one a gesture or a read
10
+ * an agent cannot otherwise perform through the product.
11
+ */
12
+
13
+ /** One element as the probe reports it — everything a caller needs to assert
14
+ * on, and nothing that requires a second round trip. */
15
+ export interface ProbedElement {
16
+ /** Position within the match list this element came from. */
17
+ index: number;
18
+ tag: string;
19
+ /** `innerText`, trimmed and capped. */
20
+ text: string;
21
+ attributes: Record<string, string>;
22
+ rect: { x: number; y: number; width: number; height: number };
23
+ /** Present for form controls. */
24
+ value?: string;
25
+ checked?: boolean;
26
+ disabled?: boolean;
27
+ }
28
+
29
+ export interface DocumentProbeResult {
30
+ /** The scope the step ran in — echoed so a transcript proves WHICH document
31
+ * was driven, not merely that something was. */
32
+ document: { id: string; title: string };
33
+ /** Total matches inside the scope, before any `limit`. */
34
+ matched: number;
35
+ elements: ProbedElement[];
36
+ }
37
+
38
+ /** Read what the active document rendered. */
39
+ export interface DocumentQueryStep {
40
+ action: 'query';
41
+ selector: string;
42
+ /** Cap on returned elements (default 25); `matched` still reports the total. */
43
+ limit?: number;
44
+ }
45
+
46
+ /** A real pointer gesture on one matched element. */
47
+ export interface DocumentClickStep {
48
+ action: 'click';
49
+ selector: string;
50
+ /** Which match (default 0). */
51
+ index?: number;
52
+ }
53
+
54
+ /** A real key on the explicit target, else whatever inside the document has focus. */
55
+ export interface DocumentKeyStep {
56
+ action: 'key';
57
+ key: string;
58
+ code?: string;
59
+ selector?: string;
60
+ index?: number;
61
+ ctrlKey?: boolean;
62
+ metaKey?: boolean;
63
+ shiftKey?: boolean;
64
+ altKey?: boolean;
65
+ }
66
+
67
+ /** A real `ClipboardEvent` carrying `text/plain` — the only way to exercise a
68
+ * paste handler, and the gesture the Sheets build could not verify. */
69
+ export interface DocumentPasteStep {
70
+ action: 'paste';
71
+ text: string;
72
+ selector?: string;
73
+ index?: number;
74
+ }
75
+
76
+ export type DocumentProbeStep =
77
+ | DocumentQueryStep
78
+ | DocumentClickStep
79
+ | DocumentKeyStep
80
+ | DocumentPasteStep;
@@ -8,9 +8,9 @@ const ASSET_KINDS = new Set<AssetKind>([
8
8
  'audio',
9
9
  'animation',
10
10
  'json',
11
- 'scene',
12
11
  'prefab',
13
12
  'material',
13
+ 'source',
14
14
  ]);
15
15
  const CAMERAS = new Set<ViewPreset | 'isometric'>([
16
16
  'top',
@@ -32,7 +32,12 @@ const DIAGNOSTICS = new Set<EditorViewDiagnostic>([
32
32
  'bounds',
33
33
  'skeleton',
34
34
  ]);
35
- const UTILITIES = new Set<EditorViewUtility>(['profiler', 'console', 'animation']);
35
+ const UTILITIES = new Set<EditorViewUtility>([
36
+ 'profiler',
37
+ 'console',
38
+ 'animation',
39
+ 'light-explorer',
40
+ ]);
36
41
 
37
42
  function nonEmpty(value: string | null): string | null {
38
43
  const trimmed = value?.trim();
@@ -47,7 +52,7 @@ function writeDocument(params: URLSearchParams, document: EditorView['document']
47
52
  params.set(`${PREFIX}assetSource`, 'entity');
48
53
  } else if (document.kind === 'asset') {
49
54
  params.set(`${PREFIX}doc`, document.path!);
50
- } else if (document.kind === 'scene' || document.kind === 'data') {
55
+ } else if (document.kind === 'scene') {
51
56
  params.set(`${PREFIX}doc`, document.path);
52
57
  } else if (document.kind === 'story') {
53
58
  params.set(`${PREFIX}doc`, document.modulePath);
@@ -139,7 +144,6 @@ function parseDocument(params: URLSearchParams): EditorView['document'] {
139
144
  if (kind === 'scene') return { kind, path: value };
140
145
  if (kind === 'tool') return { kind, id: value };
141
146
  if (kind === 'world') return { kind, id: value };
142
- if (kind === 'data') return { kind, path: value };
143
147
  if (kind === 'project-tool') return { kind, name: value };
144
148
  if (kind === 'generation') return { kind, id: value };
145
149
  if (kind === 'story') return parseStoryDocument(params, value);
@@ -148,7 +152,6 @@ function parseDocument(params: URLSearchParams): EditorView['document'] {
148
152
  [
149
153
  'workspace:scene',
150
154
  'workspace:game',
151
- 'workspace:data-assets',
152
155
  'workspace:build-profiles',
153
156
  'account',
154
157
  'project-tools',
package/src/extension.ts CHANGED
@@ -19,8 +19,8 @@
19
19
  * engine already publishes that seam and every consumer of it also
20
20
  * imports the engine — an alias would be a dead surface.
21
21
  *
22
- * Degradation ladder (the anti-shim rule, as API): every contribution
23
- * resolves to a {@link ExtensionContributionTier} —
22
+ * Outcomes (the anti-shim rule, as API): every contribution resolves to an
23
+ * {@link ExtensionContributionState} —
24
24
  *
25
25
  * - `'active'` — the contribution loaded and produced its surface.
26
26
  * - `'absent'` — nothing was contributed. The editor HIDES the surface
@@ -33,7 +33,8 @@
33
33
  */
34
34
 
35
35
  /**
36
- * Capability tier of a single extension contribution the degradation
37
- * ladder made API (see the module doc above for the exact semantics).
36
+ * The terminal state of a single extension contribution (see the module doc
37
+ * above for the exact semantics). Not a grade and not an ordering: `absent`
38
+ * and `failed` both hide the surface, and `failed` is reported loudly.
38
39
  */
39
- export type ExtensionContributionTier = 'active' | 'absent' | 'failed';
40
+ export type ExtensionContributionState = 'active' | 'absent' | 'failed';
package/src/index.ts CHANGED
@@ -4,7 +4,7 @@ export type {
4
4
  GenerationJobStatus,
5
5
  GenerationJobsDocument,
6
6
  } from '@vgai/sdk/generations';
7
- export type { GameDebugDoor } from './client.js';
7
+ export type { GameDebugDoor, HistoryStep } from './client.js';
8
8
  export { EditorClient, EditorCommandError } from './client.js';
9
9
  export type {
10
10
  ToolAssetInspectorContributionMatch,
@@ -16,8 +16,17 @@ export type {
16
16
  ToolInspectorContributionMatch,
17
17
  ToolInspectorContributionProps,
18
18
  } from './contributions.js';
19
+ export type {
20
+ DocumentClickStep,
21
+ DocumentKeyStep,
22
+ DocumentPasteStep,
23
+ DocumentProbeResult,
24
+ DocumentProbeStep,
25
+ DocumentQueryStep,
26
+ ProbedElement,
27
+ } from './document-probe.js';
19
28
  export { editorViewFromUrl, editorViewUrl } from './editor-view.js';
20
- export type { ExtensionContributionTier } from './extension.js';
29
+ export type { ExtensionContributionState } from './extension.js';
21
30
  export type {
22
31
  ActiveDocumentCapture,
23
32
  AssetCompareCapture,
@@ -41,6 +50,8 @@ export type {
41
50
  HelperVisibility,
42
51
  InspectedAction,
43
52
  InspectedField,
53
+ InspectedHierarchy,
54
+ InspectedHierarchyRow,
44
55
  InspectedInspection,
45
56
  InspectedNothing,
46
57
  InspectedSection,
package/src/types.ts CHANGED
@@ -15,9 +15,9 @@ export type AssetKind =
15
15
  | 'audio'
16
16
  | 'animation'
17
17
  | 'json'
18
- | 'scene'
19
18
  | 'prefab'
20
- | 'material';
19
+ | 'material'
20
+ | 'source';
21
21
 
22
22
  export interface HelperVisibility {
23
23
  bounds: boolean;
@@ -29,6 +29,8 @@ export interface HelperVisibility {
29
29
  audio: boolean;
30
30
  splines: boolean;
31
31
  navmesh: boolean;
32
+ constraints: boolean;
33
+ reflectionProbes: boolean;
32
34
  skeletons: boolean;
33
35
  }
34
36
 
@@ -397,7 +399,9 @@ export interface EditorState {
397
399
  * would otherwise leave unexplained. `heapUsedMB`/`heapLimitMB` are null off
398
400
  * Chromium (`performance.memory` is non-standard); the renderer counts are
399
401
  * absent, never zero, when no game has registered a render-debug adapter.
400
- * `censusAgeMs` says how stale the profile is a hidden tab is not sampled.
402
+ * `mountEpochs` counts the project module generations accumulated in this
403
+ * document; it is absent against an older server. `censusAgeMs` says how
404
+ * stale the profile is — a hidden tab is not sampled.
401
405
  */
402
406
  tabs?: Array<{
403
407
  tabId8: string;
@@ -413,6 +417,7 @@ export interface EditorState {
413
417
  census?: {
414
418
  heapUsedMB: number | null;
415
419
  heapLimitMB: number | null;
420
+ mountEpochs?: number;
416
421
  canvases: number;
417
422
  canvasMB: number;
418
423
  textures?: number;
@@ -513,7 +518,7 @@ export interface EditorState {
513
518
  }
514
519
 
515
520
  export type ViewPreset = 'top' | 'front' | 'right' | 'perspective';
516
- export type ShadingMode = 'solid' | 'unlit' | 'wireframe' | 'normals' | 'overdraw';
521
+ export type ShadingMode = 'solid' | 'clay' | 'unlit' | 'wireframe' | 'normals' | 'overdraw';
517
522
  export type TransformMode = 'translate' | 'rotate' | 'scale';
518
523
  export type TransformSpace = 'world' | 'local';
519
524
 
@@ -529,7 +534,6 @@ export type EditorViewDocument =
529
534
  | { kind: 'tool'; id: string }
530
535
  | { kind: 'world'; id: string }
531
536
  | { kind: 'story'; modulePath: string; storyName: string; mode?: 'preview' | 'docs' }
532
- | { kind: 'data'; path: string }
533
537
  | { kind: 'project-tool'; name: string }
534
538
  | { kind: 'generation'; id: string }
535
539
  | {
@@ -537,7 +541,6 @@ export type EditorViewDocument =
537
541
  id:
538
542
  | 'workspace:scene'
539
543
  | 'workspace:game'
540
- | 'workspace:data-assets'
541
544
  | 'workspace:build-profiles'
542
545
  | 'account'
543
546
  | 'project-tools';
@@ -553,7 +556,7 @@ export interface EditorView {
553
556
  frame?: 'document' | 'selection';
554
557
  grid?: boolean;
555
558
  };
556
- utility?: 'profiler' | 'console' | 'animation';
559
+ utility?: 'profiler' | 'console' | 'animation' | 'light-explorer';
557
560
  }
558
561
 
559
562
  export interface PresentedEditorView {
@@ -687,6 +690,11 @@ export interface InspectedAction {
687
690
  disabled?: boolean;
688
691
  }
689
692
 
693
+ export interface InspectedSubjectLink {
694
+ id: string;
695
+ title: string;
696
+ }
697
+
690
698
  /** The whole inspection subject, as data — what a human sees in the
691
699
  * inspector, for an agent (`vgai eval 'editor.inspect()'`). */
692
700
  export interface InspectedSubject {
@@ -701,6 +709,8 @@ export interface InspectedSubject {
701
709
  surface?: InspectionSurface;
702
710
  };
703
711
  quickActions: readonly InspectedAction[];
712
+ /** Agent-visible counterparts of the inspector's related-document buttons. */
713
+ related: readonly InspectedSubjectLink[];
704
714
  /** Already in display order. */
705
715
  sections: readonly InspectedSection[];
706
716
  }
@@ -716,3 +726,52 @@ export interface InspectedNothing {
716
726
  /** What `editor.inspect()` answers: the subject showing, or nothing at all.
717
727
  * Narrow with `'none' in result`. */
718
728
  export type InspectedInspection = InspectedSubject | InspectedNothing;
729
+
730
+ // ---------------------------------------------------------------- hierarchy
731
+ //
732
+ // The wire mirror of the editor's own `SerializedHierarchyPanel`
733
+ // (`packages/editor/src/hierarchy-panel-view.ts`, which owns the contract and
734
+ // carries the reasoning). `command-listener.ts` annotates its `hierarchy`
735
+ // payload with this type, so `tsc` checks the two sides against each other on
736
+ // every build.
737
+ //
738
+ // This is NOT `EditorState.entities`: that facet is the raw adapter tree, with
739
+ // no marks, no internals folding and no document promotion. This one is what
740
+ // the hierarchy PANEL rendered — the rows a human is looking at.
741
+
742
+ /** One row of the hierarchy panel, as data. */
743
+ export interface InspectedHierarchyRow {
744
+ id: string;
745
+ label: string;
746
+ /** The dim type suffix the row prints (`Coin1 ·Coin`). */
747
+ typeLabel?: string;
748
+ role?: string;
749
+ depth: number;
750
+ /** Children the row's view has — what opening the caret reveals. Folded
751
+ * implementation children are NOT counted here. */
752
+ childCount: number;
753
+ /** Children folded away as implementation, behind "Reveal Internals". */
754
+ internalChildCount: number;
755
+ /** Whether the panel renders a disclosure control. A row with children of
756
+ * any kind and `expandable: false` is a subtree the UI cannot reach. */
757
+ expandable: boolean;
758
+ expanded?: boolean;
759
+ internal?: true;
760
+ componentRoot?: true;
761
+ /** A synthetic "… N more" cap stub rather than a real node. */
762
+ more?: { hidden: number };
763
+ children?: readonly InspectedHierarchyRow[];
764
+ }
765
+
766
+ /** What `editor.hierarchy()` answers. */
767
+ export interface InspectedHierarchy {
768
+ rowCount: number;
769
+ /** The slice in the DOM; a smaller span than `rowCount` means the rest is
770
+ * scrolled out, not absent. */
771
+ window: { start: number; end: number };
772
+ search?: string;
773
+ scopeId?: string;
774
+ playState: string;
775
+ activeViewportTab: string;
776
+ roots: readonly InspectedHierarchyRow[];
777
+ }