@vgai/engine 0.5.12 → 0.5.14

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 (192) hide show
  1. package/dist/adapter/adapter-module.d.ts +447 -0
  2. package/dist/adapter/adapter-module.d.ts.map +1 -0
  3. package/dist/adapter/adapter-module.js +308 -0
  4. package/dist/adapter/authoring.d.ts +357 -10
  5. package/dist/adapter/authoring.d.ts.map +1 -1
  6. package/dist/adapter/authoring.js +87 -1
  7. package/dist/adapter/constraint.d.ts +9 -3
  8. package/dist/adapter/constraint.d.ts.map +1 -1
  9. package/dist/adapter/finders/finder-result.d.ts +28 -0
  10. package/dist/adapter/finders/finder-result.d.ts.map +1 -0
  11. package/dist/adapter/finders/finder-result.js +10 -0
  12. package/dist/adapter/finders/index.d.ts +38 -0
  13. package/dist/adapter/finders/index.d.ts.map +1 -0
  14. package/dist/adapter/finders/index.js +41 -0
  15. package/dist/adapter/finders/prefabs-from-stories.d.ts +64 -0
  16. package/dist/adapter/finders/prefabs-from-stories.d.ts.map +1 -0
  17. package/dist/adapter/finders/prefabs-from-stories.js +92 -0
  18. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts +67 -0
  19. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -0
  20. package/dist/adapter/finders/scenes-from-entrypoint-selection.js +226 -0
  21. package/dist/adapter/first-party-systems.d.ts.map +1 -1
  22. package/dist/adapter/first-party-systems.js +29 -0
  23. package/dist/adapter/index.d.ts +7 -3
  24. package/dist/adapter/index.d.ts.map +1 -1
  25. package/dist/adapter/index.js +5 -2
  26. package/dist/adapter/ingest/contract-system-adapters.d.ts +24 -1
  27. package/dist/adapter/ingest/contract-system-adapters.d.ts.map +1 -1
  28. package/dist/adapter/ingest/contract-system-adapters.js +100 -34
  29. package/dist/adapter/ingest/game-contract.d.ts +188 -2
  30. package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
  31. package/dist/adapter/ingest/game-contract.js +107 -0
  32. package/dist/adapter/ingest/mount-readiness.d.ts +84 -0
  33. package/dist/adapter/ingest/mount-readiness.d.ts.map +1 -0
  34. package/dist/adapter/ingest/mount-readiness.js +81 -0
  35. package/dist/adapter/ingest/scene-capture.d.ts +91 -2
  36. package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
  37. package/dist/adapter/ingest/scene-capture.js +110 -18
  38. package/dist/adapter/ingest/structural-ids.d.ts +11 -5
  39. package/dist/adapter/ingest/structural-ids.d.ts.map +1 -1
  40. package/dist/adapter/ingest/structural-ids.js +18 -9
  41. package/dist/adapter/ingest/upstream-pin.d.ts +4 -2
  42. package/dist/adapter/ingest/upstream-pin.d.ts.map +1 -1
  43. package/dist/adapter/ingest/upstream-pin.js +4 -2
  44. package/dist/adapter/ingest/visible-capture-window.d.ts +116 -0
  45. package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -0
  46. package/dist/adapter/ingest/visible-capture-window.js +141 -0
  47. package/dist/adapter/loop-gate-report.d.ts +3 -4
  48. package/dist/adapter/loop-gate-report.d.ts.map +1 -1
  49. package/dist/adapter/loop-gate-report.js +3 -4
  50. package/dist/adapter/root-adapter.d.ts +22 -2
  51. package/dist/adapter/root-adapter.d.ts.map +1 -1
  52. package/dist/adapter/setup-three-root-adapter.d.ts.map +1 -1
  53. package/dist/adapter/setup-three-root-adapter.js +15 -0
  54. package/dist/adapter/system-adapter.d.ts +161 -2
  55. package/dist/adapter/system-adapter.d.ts.map +1 -1
  56. package/dist/adapter/system-adapter.js +18 -1
  57. package/dist/animation/camera-ownership.d.ts +4 -2
  58. package/dist/animation/camera-ownership.d.ts.map +1 -1
  59. package/dist/animation/camera-ownership.js +58 -1
  60. package/dist/animation/runtime-inspection.d.ts +70 -0
  61. package/dist/animation/runtime-inspection.d.ts.map +1 -0
  62. package/dist/animation/runtime-inspection.js +25 -0
  63. package/dist/animation/xstate-animation-binding.d.ts +9 -1
  64. package/dist/animation/xstate-animation-binding.d.ts.map +1 -1
  65. package/dist/animation/xstate-animation-binding.js +48 -1
  66. package/dist/asset-formats/material.d.ts +1 -1
  67. package/dist/asset-formats/mesh.d.ts +1 -1
  68. package/dist/asset-formats/particles.d.ts +13 -0
  69. package/dist/asset-formats/particles.d.ts.map +1 -1
  70. package/dist/asset-formats/particles.js +28 -0
  71. package/dist/behavior/xstate-inspection.d.ts +24 -0
  72. package/dist/behavior/xstate-inspection.d.ts.map +1 -0
  73. package/dist/behavior/xstate-inspection.js +25 -0
  74. package/dist/core/game-loop.d.ts +7 -5
  75. package/dist/core/game-loop.d.ts.map +1 -1
  76. package/dist/core/game-loop.js +39 -18
  77. package/dist/core/types.d.ts +11 -12
  78. package/dist/core/types.d.ts.map +1 -1
  79. package/dist/data/data-asset.js +1 -1
  80. package/dist/dev/instruments.d.ts.map +1 -1
  81. package/dist/dev/instruments.js +5 -1
  82. package/dist/dev/render-debug-adapter.d.ts +19 -2
  83. package/dist/dev/render-debug-adapter.d.ts.map +1 -1
  84. package/dist/dev/render-debug-adapter.js +10 -0
  85. package/dist/ecs/user-data.d.ts +10 -2
  86. package/dist/ecs/user-data.d.ts.map +1 -1
  87. package/dist/ecs/user-data.js +6 -2
  88. package/dist/input/binding-identity.d.ts +10 -0
  89. package/dist/input/binding-identity.d.ts.map +1 -0
  90. package/dist/input/binding-identity.js +36 -0
  91. package/dist/input/input-manager.d.ts +18 -185
  92. package/dist/input/input-manager.d.ts.map +1 -1
  93. package/dist/input/input-manager.js +78 -68
  94. package/dist/manifest/load.d.ts +14 -0
  95. package/dist/manifest/load.d.ts.map +1 -1
  96. package/dist/manifest/load.js +6 -1
  97. package/dist/manifest/schema.d.ts +18 -0
  98. package/dist/manifest/schema.d.ts.map +1 -1
  99. package/dist/manifest/schema.js +64 -7
  100. package/dist/pixi/authoring.d.ts +27 -0
  101. package/dist/pixi/authoring.d.ts.map +1 -1
  102. package/dist/pixi/authoring.js +10 -3
  103. package/dist/pixi/index.d.ts +1 -1
  104. package/dist/pixi/index.d.ts.map +1 -1
  105. package/dist/pixi/index.js +1 -1
  106. package/dist/pixi/ingest.d.ts +8 -0
  107. package/dist/pixi/ingest.d.ts.map +1 -1
  108. package/dist/pixi/ingest.js +10 -1
  109. package/dist/pixi/render-pass-bracket.d.ts +56 -0
  110. package/dist/pixi/render-pass-bracket.d.ts.map +1 -0
  111. package/dist/pixi/render-pass-bracket.js +98 -0
  112. package/dist/pixi/scene-capture.d.ts +32 -1
  113. package/dist/pixi/scene-capture.d.ts.map +1 -1
  114. package/dist/pixi/scene-capture.js +21 -9
  115. package/dist/pixi/system-adapters.d.ts +29 -0
  116. package/dist/pixi/system-adapters.d.ts.map +1 -1
  117. package/dist/pixi/system-adapters.js +27 -0
  118. package/dist/render/particles-factory.d.ts +16 -2
  119. package/dist/render/particles-factory.d.ts.map +1 -1
  120. package/dist/render/particles-factory.js +88 -5
  121. package/dist/render/soft-particle-depth.d.ts +149 -0
  122. package/dist/render/soft-particle-depth.d.ts.map +1 -0
  123. package/dist/render/soft-particle-depth.js +305 -0
  124. package/dist/render/voxel-surface.d.ts +117 -0
  125. package/dist/render/voxel-surface.d.ts.map +1 -0
  126. package/dist/render/voxel-surface.js +263 -0
  127. package/dist/runtime/create-runtime.js +7 -0
  128. package/dist/runtime/debug-bridge.d.ts +15 -15
  129. package/dist/runtime/debug-bridge.d.ts.map +1 -1
  130. package/dist/runtime/debug-bridge.js +27 -26
  131. package/dist/runtime/game.d.ts.map +1 -1
  132. package/dist/runtime/game.js +1 -2
  133. package/dist/setup/setup-particles.d.ts.map +1 -1
  134. package/dist/setup/setup-particles.js +9 -0
  135. package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
  136. package/dist/world3d-react/r3f-adapter.js +42 -3
  137. package/dist/world3d-react/rapier-physics-bridge.d.ts.map +1 -1
  138. package/dist/world3d-react/rapier-physics-bridge.js +201 -11
  139. package/dist-config/config.js.map +1 -1
  140. package/package.json +4 -2
  141. package/schemas/engine-capabilities.json +12 -12
  142. package/schemas/vgai-project.schema.json +41 -2
  143. package/src/adapter/adapter-module.ts +607 -0
  144. package/src/adapter/authoring.ts +440 -10
  145. package/src/adapter/constraint.ts +10 -3
  146. package/src/adapter/finders/finder-result.ts +29 -0
  147. package/src/adapter/finders/index.ts +62 -0
  148. package/src/adapter/finders/prefabs-from-stories.ts +142 -0
  149. package/src/adapter/finders/scenes-from-entrypoint-selection.ts +304 -0
  150. package/src/adapter/first-party-systems.ts +30 -0
  151. package/src/adapter/index.ts +32 -1
  152. package/src/adapter/ingest/contract-system-adapters.ts +116 -32
  153. package/src/adapter/ingest/game-contract.ts +265 -2
  154. package/src/adapter/ingest/mount-readiness.ts +140 -0
  155. package/src/adapter/ingest/scene-capture.ts +199 -22
  156. package/src/adapter/ingest/structural-ids.ts +21 -9
  157. package/src/adapter/ingest/upstream-pin.ts +4 -2
  158. package/src/adapter/ingest/visible-capture-window.ts +213 -0
  159. package/src/adapter/loop-gate-report.ts +3 -4
  160. package/src/adapter/root-adapter.ts +22 -2
  161. package/src/adapter/setup-three-root-adapter.ts +16 -0
  162. package/src/adapter/system-adapter.ts +188 -2
  163. package/src/animation/camera-ownership.ts +68 -3
  164. package/src/animation/runtime-inspection.ts +104 -0
  165. package/src/animation/xstate-animation-binding.ts +53 -1
  166. package/src/asset-formats/particles.ts +31 -0
  167. package/src/behavior/xstate-inspection.ts +51 -0
  168. package/src/core/game-loop.ts +39 -17
  169. package/src/core/types.ts +11 -12
  170. package/src/data/data-asset.ts +1 -1
  171. package/src/dev/instruments.ts +5 -1
  172. package/src/dev/render-debug-adapter.ts +22 -2
  173. package/src/ecs/user-data.ts +10 -2
  174. package/src/input/binding-identity.ts +37 -0
  175. package/src/input/input-manager.ts +93 -68
  176. package/src/manifest/load.ts +23 -2
  177. package/src/manifest/schema.ts +76 -7
  178. package/src/pixi/authoring.ts +37 -5
  179. package/src/pixi/index.ts +2 -0
  180. package/src/pixi/ingest.ts +15 -2
  181. package/src/pixi/render-pass-bracket.ts +117 -0
  182. package/src/pixi/scene-capture.ts +63 -13
  183. package/src/pixi/system-adapters.ts +41 -0
  184. package/src/render/particles-factory.ts +89 -6
  185. package/src/render/soft-particle-depth.ts +336 -0
  186. package/src/render/voxel-surface.ts +358 -0
  187. package/src/runtime/create-runtime.ts +7 -0
  188. package/src/runtime/debug-bridge.ts +32 -31
  189. package/src/runtime/game.ts +1 -2
  190. package/src/setup/setup-particles.ts +9 -0
  191. package/src/world3d-react/r3f-adapter.tsx +48 -2
  192. package/src/world3d-react/rapier-physics-bridge.tsx +249 -11
@@ -182,7 +182,10 @@ export interface HierarchyProvider {
182
182
 
183
183
  export interface SelectionProvider {
184
184
  get(): string[];
185
- set(ids: string[]): void;
185
+ /** Set the selected authoring subjects. `exact` is for projections that
186
+ * already name a real node (for example a folded-internal light inventory);
187
+ * the default keeps the adapter's normal semantic component resolution. */
188
+ set(ids: string[], options?: { intent?: 'semantic' | 'exact' }): void;
186
189
  /** Resolve a raw native/render hit to the semantic authoring subject. The
187
190
  * shell must not infer ownership from ancestry or substrate-specific kinds.
188
191
  * Older adapters may omit this; raw ids then remain the selection ids. */
@@ -220,7 +223,14 @@ export interface TransformProvider {
220
223
  /** Pause whatever controller fights the gizmo (physics/script) for editing. */
221
224
  beginEdit(id: string): void;
222
225
  apply(id: string, t: Transform): void;
223
- endEdit(id: string): void;
226
+ /**
227
+ * Close the gesture — and, for an adapter that persists, ANSWER FOR IT: the
228
+ * returned {@link WriteAck} is this edit's own outcome, produced after the
229
+ * write it triggered has settled. Returning nothing is the honest report of
230
+ * an adapter that performed no write, and the shell then reports the
231
+ * live-only floor rather than borrowing an answer from somewhere else.
232
+ */
233
+ endEdit(id: string): void | WriteAck | Promise<void | WriteAck>;
224
234
  }
225
235
 
226
236
  /**
@@ -238,7 +248,18 @@ export interface InspectorProvider {
238
248
  /** Schema-driven — NOT fixed to `SceneEntity`. */
239
249
  properties(id: string): PropertyDescriptor[];
240
250
  get(id: string, path: string): unknown;
241
- set(id: string, path: string, value: unknown): void | Promise<void>;
251
+ /**
252
+ * Write one property — and, for an adapter that persists, ANSWER FOR IT: the
253
+ * returned {@link WriteAck} is this edit's own outcome (same contract as
254
+ * {@link TransformProvider.endEdit}). Returning nothing means no write was
255
+ * performed by this provider, and the shell reports the live-only floor.
256
+ */
257
+ set(id: string, path: string, value: unknown): void | WriteAck | Promise<void | WriteAck>;
258
+ /** Optional per-subject write preflight for shell affordances outside the
259
+ * Inspector field grid (notably hierarchy eye/lock controls). The property
260
+ * descriptor remains the Inspector's source of `readonly`; this gives other
261
+ * generic surfaces the same answer and its adapter-authored reason. */
262
+ editability?(id: string, path: string): { writable: boolean; reason?: string };
242
263
  /**
243
264
  * Optional — REMOVE a property's authored override entirely (not "set to a
244
265
  * value"), letting whatever governs it in its absence take over. The one
@@ -294,6 +315,47 @@ export interface RelatedSubjectsProvider {
294
315
  links(id: string): readonly RelatedSubjectLink[];
295
316
  }
296
317
 
318
+ /** One source-owned prop that differs from its component's declared default. */
319
+ export interface ComponentInstanceOverride {
320
+ /** The owning Inspector field path; stable and agent-addressable. */
321
+ readonly path: string;
322
+ readonly label: string;
323
+ readonly value: unknown;
324
+ /** Source text of the component default, when the declaration exposes one. */
325
+ readonly defaultText?: string;
326
+ /** Applying requires a literal callsite value and a literal declared default. */
327
+ readonly canApplyToComponent: boolean;
328
+ readonly applyUnavailableReason?: string;
329
+ /** Placed instances that will inherit this default after the apply. */
330
+ readonly affectedInstanceCount: number;
331
+ }
332
+
333
+ /** Derived prefab-instance facts. Nothing in this shape is persisted separately. */
334
+ export interface ComponentInstanceDescription {
335
+ readonly componentName: string;
336
+ readonly sourcePath?: string;
337
+ readonly overrides: readonly ComponentInstanceOverride[];
338
+ }
339
+
340
+ export interface ComponentInstanceApplyResult {
341
+ readonly changed: boolean;
342
+ readonly message: string;
343
+ }
344
+
345
+ /**
346
+ * Source-derived component-instance operations. A provider exists only when an
347
+ * adapter can prove component identity and write the component's native source.
348
+ * No override store is implied: describe is a diff, revert removes callsite
349
+ * props, and apply edits the component declaration itself.
350
+ */
351
+ export interface ComponentInstancesProvider {
352
+ describe(id: string): ComponentInstanceDescription | null;
353
+ /** Open the native component/prefab board for this instance's surface. */
354
+ openComponent?(id: string): void;
355
+ revert(id: string, paths: readonly string[]): Promise<void>;
356
+ applyToComponent(id: string, path: string): Promise<ComponentInstanceApplyResult>;
357
+ }
358
+
297
359
  export interface StructureProvider {
298
360
  create(kind: string, parentId?: string): string;
299
361
  /**
@@ -357,6 +419,23 @@ export interface StructureProvider {
357
419
  * is a harmless no-op.
358
420
  */
359
421
  removeMany?(ids: readonly string[]): void | Promise<void>;
422
+ /** Copy authored entities through the host clipboard. The adapter owns the
423
+ * native payload (JSX for a source-backed Three/Canvas world, native records
424
+ * for a data-backed adapter); the shell never serializes an engine-private
425
+ * shape. */
426
+ copy?(ids: readonly string[]): boolean | Promise<boolean>;
427
+ /** Honest affordance preflight. Omit when copy is never offered. */
428
+ canCopy?(ids: readonly string[]): boolean;
429
+ /** Copy, then remove the same authored entities only after the clipboard
430
+ * write succeeds. One adapter operation owns that ordering. */
431
+ cut?(ids: readonly string[]): boolean | Promise<boolean>;
432
+ /** Paste as children of `parentId`; `null` means the adapter document root.
433
+ * The shell derives the current selection's parent so ordinary Paste creates
434
+ * siblings, matching scene-hierarchy editors. */
435
+ paste?(parentId: string | null): boolean | Promise<boolean>;
436
+ /** Honest affordance preflight. It need only cover adapter/session state;
437
+ * the actual paste still validates the live system clipboard. */
438
+ canPaste?(parentId: string | null): boolean;
360
439
  }
361
440
 
362
441
  /**
@@ -367,6 +446,10 @@ export interface StructureProvider {
367
446
  */
368
447
  export interface PersistenceProvider {
369
448
  isDirty(): boolean;
449
+ /** The last persistence failure that still requires attention. Successful
450
+ * subsequent persistence clears it. Auto-saving source adapters use this to
451
+ * report a rolled-back source write without inventing a dirty document. */
452
+ lastError?(): string | null;
370
453
  /** Save to the adapter's OWN source of truth (.vscn / overlay file / …). */
371
454
  save(): Promise<void>;
372
455
  /**
@@ -400,6 +483,13 @@ export interface PersistenceProvider {
400
483
  /** D12 — per-layer viewport picking. Coordinates are client (browser) px. */
401
484
  export interface PickProvider {
402
485
  pick(clientX: number, clientY: number): string | null;
486
+ /**
487
+ * Every authorable subject below the point, frontmost first. The shell uses
488
+ * this for the ordinary scene-editor "pick from overlap" menu; adapters
489
+ * that cannot enumerate an overlap may omit it and still provide the
490
+ * single-hit floor through {@link pick}.
491
+ */
492
+ candidates?(clientX: number, clientY: number): readonly string[];
403
493
  }
404
494
 
405
495
  /** D4 — storybook stories. */
@@ -413,6 +503,14 @@ export interface StoriesProvider {
413
503
  apply(nodeId: string, storyId: string | null): void; // null clears
414
504
  /** Render ONLY this node against the story (storybook canvas); null exits. */
415
505
  isolate?(nodeId: string | null, storyId?: string): void;
506
+ /**
507
+ * Display vocabulary for this provider's section — e.g. an ingested game's
508
+ * contract scenes say "Scenes"; absent means the default "Stories". The
509
+ * states are the same seam either way; only the word the human reads
510
+ * changes, so a game's own screens are never labelled with a design-time
511
+ * term the game never uses.
512
+ */
513
+ readonly title?: string;
416
514
  }
417
515
 
418
516
  /**
@@ -424,11 +522,22 @@ export interface StoriesProvider {
424
522
  */
425
523
  export interface AssetDropContext {
426
524
  readonly position?: readonly [number, number, number];
525
+ /** Transient drag metadata. The project's native source remains truth; this
526
+ * only tells an adapter whether the path names a file or a source component. */
527
+ readonly item?:
528
+ | { readonly kind: 'file'; readonly name: string }
529
+ | {
530
+ readonly kind: 'component';
531
+ readonly name: string;
532
+ readonly sourcePath: string;
533
+ readonly exportKind: 'default' | 'named';
534
+ readonly surface: 'three' | 'canvas' | 'dom' | 'unknown';
535
+ };
427
536
  }
428
537
 
429
538
  /** Asset drop (hierarchy + viewport). */
430
539
  export interface AssetDropProvider {
431
- accepts(nodeId: string, assetPath: string): boolean;
540
+ accepts(nodeId: string, assetPath: string, context?: AssetDropContext): boolean;
432
541
  drop(nodeId: string, assetPath: string, context?: AssetDropContext): void | Promise<void>;
433
542
  }
434
543
 
@@ -488,8 +597,9 @@ export interface BoxEditReferencePoint {
488
597
  y: number;
489
598
  }
490
599
 
491
- /** Spatial gesture → source/data write, for non-Object3D (DOM) nodes. Distinct from the
492
- * 3D TransformProvider. begin/apply/end bracket a single undo step; apply is live-preview. */
600
+ /** Spatial gesture → source/data write for native 2D boxes (DOM or canvas).
601
+ * Distinct from the 3D TransformProvider. begin/apply/end bracket a single
602
+ * undo step; apply is live-preview. */
493
603
  export interface BoxEditProvider {
494
604
  begin(id: string): void;
495
605
  /** patch: any of x,y,width,height,marginTop… paddingLeft… — px deltas or absolutes.
@@ -499,6 +609,11 @@ export interface BoxEditProvider {
499
609
  * source units and any placement-preserving compensation. */
500
610
  apply(id: string, patch: Record<string, number>): void;
501
611
  end(id: string): void;
612
+ /** Native transform origin for the viewport gizmo, in the same host-space
613
+ * frame as {@link RectProvider}. A 2D scene uses this instead of guessing
614
+ * from the visual bounds: sprites can have anchors, containers can have
615
+ * pivots, and neither necessarily sits at the box center. */
616
+ gizmoOrigin?(id: string): { x: number; y: number } | null;
502
617
  /** Optional single native anchor/pivot affordance for this node. */
503
618
  referencePoint?(id: string): BoxEditReferencePoint | null;
504
619
  }
@@ -547,10 +662,10 @@ export interface ColorSampleProvider {
547
662
  * - `source` is the original: a `new` expression at a `file:line:col` in the
548
663
  * game's own code, which is where an edit to a constructor literal has to be
549
664
  * written.
550
- * - `data` is the sibling a level-based game needs. Descent's placed cargo —
551
- * the robots, the powerups, the hostages — reaches no source literal at all;
552
- * it is binary records inside `descent.hog` that the game parses at load. An
553
- * authored move of a robot is therefore an edit to THE GAME'S OWN DATA, and
665
+ * - `data` is the sibling a level-based game needs. Its placed cargo — the
666
+ * enemies, the pickups, the objectives — reaches no source literal at all;
667
+ * it is binary records inside a level file that the game parses at load. An
668
+ * authored move of one is therefore an edit to THE GAME'S OWN DATA, and
554
669
  * the honest anchor is the data file plus the index of the record that
555
670
  * addresses the object inside it.
556
671
  *
@@ -576,6 +691,134 @@ export type NodeCreationSite =
576
691
  }
577
692
  | { anchored: false; reason: string };
578
693
 
694
+ /**
695
+ * WHERE AN AUTHORED WRITE LANDS — the vocabulary of anchor KINDS, and the
696
+ * closed set of them.
697
+ *
698
+ * {@link NodeCreationSite} answers "where did this object come from"; a kind
699
+ * answers the write-side question beside it: WHICH LANE would carry an edit to
700
+ * this node, and therefore WHAT has to be true for that edit to be correct. The
701
+ * two are not the same fact — two nodes can both be `{ kind: 'source' }` at a
702
+ * `file:line` and still be written by different lanes with different gates.
703
+ *
704
+ * Each kind's CORRECTNESS CONTRACT, and the named test that proves it:
705
+ *
706
+ * - `source-prop` — the value is a literal PROP on an element of the game's own
707
+ * source (a JSX attribute the serve-time authoring stamp addresses). Lands at
708
+ * the anchor; survives a cold remount, because the file is truth and the next
709
+ * mount re-derives from it.
710
+ * Proof: `packages/editor/test/oid-source-persistence.test.ts`.
711
+ * - `construction-literal` — the value is a literal in the CONSTRUCTION
712
+ * EXPRESSION that built the object (`new THREE.Mesh(…)` in the game's own
713
+ * served module). Same landing/remount contract, plus one gate the prop lane
714
+ * does not have: a site that constructed more than one object is refused,
715
+ * because rewriting its literal would move all of them.
716
+ * Proof: `packages/editor/test/ingest-source-persistence.test.ts`.
717
+ * - `physics-binding` — the node's own transform props are DEAD: a simulated
718
+ * body writes its matrix every frame from a spawn it reads elsewhere, so the
719
+ * write lands at the literal that binding reads (the component callsite whose
720
+ * props the `useBox(…)`/`<RigidBody>` hook takes). It carries the two
721
+ * contracts above AND ONE MORE: the authored value must SURVIVE RE-SETTLE —
722
+ * after the world re-derives from source and the design-time settle runs, the
723
+ * body must have re-spawned at the AUTHORED pose, not at the old one. Being
724
+ * silently re-posed by the owner is this kind's whole failure mode, and the
725
+ * reason it is a kind rather than a flavour of `source-prop`.
726
+ * Proof: `packages/editor/test/r3f-physics-binding.test.ts`.
727
+ * - `data-record` — the value is a field of a RECORD in the game's own data
728
+ * file, addressed by index; no source literal mentions it. Lands in that
729
+ * file through the game's own declared writer; survives a cold remount for
730
+ * the same reason source does.
731
+ * Proof: `packages/editor/test/ingest-source-persistence.test.ts`
732
+ * ("a level-data record is an anchor too").
733
+ * - `live-only` — there is no anchor. The edit lands on the running object for
734
+ * the session and NOTHING else, and the contract is that the surface SAYS SO:
735
+ * a write ack that claims a destination it did not reach is the lie this kind
736
+ * exists to keep nameable.
737
+ * Proof: `packages/editor/test/oid-source-persistence.test.ts`
738
+ * ("an object with no source stamp is refused by name")
739
+ * + `packages/editor/test/inspection-write-destination.test.ts`.
740
+ */
741
+ export type WriteAnchorKind =
742
+ | 'source-prop'
743
+ | 'construction-literal'
744
+ | 'physics-binding'
745
+ | 'data-record'
746
+ | 'live-only';
747
+
748
+ /**
749
+ * WHAT ONE EDIT DID — the ack the persistence pipe returns for THAT edit, from
750
+ * the component that actually performed the write.
751
+ *
752
+ * It is deliberately NOT a property of an adapter, a session or a surface. The
753
+ * fact it carries is per-edit, because that is the only granularity at which it
754
+ * can be true: one composite may hold a source-backed three root beside a
755
+ * live-only DOM root, and one three root holds body-placed nodes beside
756
+ * ordinary JSX props. An answer computed from the SURFACE rather than from the
757
+ * edit reports whichever child happened to be persist-capable — measured on the
758
+ * vendored racing game, where a three-root edit acked the other root's
759
+ * destination with `persisted: true`.
760
+ *
761
+ * `persisted: false` is an ANSWER, never a failure: `live-only` is a real
762
+ * anchor kind and the contract of that kind is that the surface SAYS SO. What
763
+ * is forbidden is a `destination` no byte reached being reported with
764
+ * `persisted: true`.
765
+ *
766
+ * A provider that performs a write returns this; one that has no write to
767
+ * perform returns nothing and the shell reports the live-only floor. There is
768
+ * no third answer, and in particular there is no adapter-wide blanket
769
+ * destination standing in for one — {@link PersistenceProvider.destination}
770
+ * answers the different question "where do this surface's SAVES go", for the
771
+ * save-status chrome.
772
+ */
773
+ export interface WriteAck {
774
+ /** Where THIS edit's bytes landed, in the writer's own words — or the
775
+ * live-only/ephemeral floor when none did. */
776
+ readonly destination: string;
777
+ /** Whether a byte actually moved for THIS edit. */
778
+ readonly persisted: boolean;
779
+ }
780
+
781
+ /**
782
+ * The exhaustiveness pin, the same shape and for the same reason as
783
+ * {@link AUTHORING_PROVIDER_PRESENCE}: a `Record` over the union, so a lane that
784
+ * starts producing a new kind of anchor cannot ship without joining the
785
+ * vocabulary (the `Record` would be missing a key) and cannot invent a key the
786
+ * union does not have (excess-property error). The values are `true` and carry
787
+ * no meaning — the KEYS are the payload.
788
+ *
789
+ * What this buys downstream: the per-kind write-reach counts and `vgai doctor`'s
790
+ * edit-write walk both enumerate {@link WRITE_ANCHOR_KINDS} rather than a
791
+ * hand-written list, so a new kind arrives already measured and already walked
792
+ * instead of silently untested.
793
+ */
794
+ const WRITE_ANCHOR_KIND_PRESENCE: Readonly<Record<WriteAnchorKind, true>> = {
795
+ 'source-prop': true,
796
+ 'construction-literal': true,
797
+ 'physics-binding': true,
798
+ 'data-record': true,
799
+ 'live-only': true,
800
+ };
801
+
802
+ /**
803
+ * The vocabulary as an ordered list — most-addressable first, `live-only` last,
804
+ * so two reports of the same world are diffable line for line.
805
+ *
806
+ * Derived from {@link WRITE_ANCHOR_KIND_PRESENCE} rather than spelled a second
807
+ * time: one list the compiler pins to the union, and nothing downstream can
808
+ * enumerate a different set.
809
+ */
810
+ export const WRITE_ANCHOR_KINDS: readonly WriteAnchorKind[] = Object.keys(
811
+ WRITE_ANCHOR_KIND_PRESENCE,
812
+ ) as WriteAnchorKind[];
813
+
814
+ /** Zero counts for every kind — the starting point of any per-kind tally, so a
815
+ * tally can never omit a kind by forgetting to initialize it. */
816
+ export function emptyWriteAnchorKindCounts(): Record<WriteAnchorKind, number> {
817
+ const counts = {} as Record<WriteAnchorKind, number>;
818
+ for (const kind of WRITE_ANCHOR_KINDS) counts[kind] = 0;
819
+ return counts;
820
+ }
821
+
579
822
  /**
580
823
  * Absent ⇒ this adapter's substrate has no creation-site index at all (a
581
824
  * document-backed world's truth is the document, not a construction site).
@@ -583,6 +826,112 @@ export type NodeCreationSite =
583
826
  */
584
827
  export interface CreationSiteProvider {
585
828
  anchor(id: string): NodeCreationSite;
829
+ /**
830
+ * The write-side sibling of {@link anchor}: which LANE would carry an edit to
831
+ * this node's transform, in the closed vocabulary above. Exposed beside the
832
+ * anchor because that is where every reader already looks, and because a
833
+ * caller sweeping a hierarchy needs it to find a representative node per kind
834
+ * (`vgai doctor`'s edit-write walk is that caller).
835
+ *
836
+ * Absent ⇒ this adapter plans no lanes it can distinguish; the reader has only
837
+ * the anchor's own shape, which cannot tell two source lanes apart.
838
+ * `undefined` for a particular id ⇒ this adapter plans no write for THAT id
839
+ * (a synthetic hierarchy row, an id no child of a composite owns) — which is
840
+ * not the same as `live-only`, a lane that really does land on a live object.
841
+ */
842
+ writeAnchorKind?(id: string): WriteAnchorKind | undefined;
843
+ }
844
+
845
+ /** A plain world-space point. Component handles cross the authoring seam as
846
+ * data; a substrate object or renderer handle never does. */
847
+ export type SpatialPoint3 = readonly [x: number, y: number, z: number];
848
+
849
+ /** Non-interactive geometry an adapter asks the viewport to draw for one
850
+ * spatial component. The vocabulary is intentionally geometric rather than
851
+ * domain-shaped: audio attenuation, light range and a collider radius can all
852
+ * use the same sphere without teaching the shell any of those concepts. */
853
+ export type SpatialHandleGuide =
854
+ | {
855
+ readonly kind: 'line';
856
+ readonly points: readonly SpatialPoint3[];
857
+ readonly color: string;
858
+ readonly opacity?: number;
859
+ readonly dashed?: boolean;
860
+ }
861
+ | {
862
+ readonly kind: 'sphere';
863
+ readonly center: SpatialPoint3;
864
+ readonly radius: number;
865
+ readonly color: string;
866
+ readonly opacity?: number;
867
+ readonly dashed?: boolean;
868
+ }
869
+ | {
870
+ readonly kind: 'cone';
871
+ readonly origin: SpatialPoint3;
872
+ readonly direction: SpatialPoint3;
873
+ readonly length: number;
874
+ /** Full opening angle in degrees, matching Web Audio and authoring UIs. */
875
+ readonly angle: number;
876
+ readonly color: string;
877
+ readonly opacity?: number;
878
+ readonly dashed?: boolean;
879
+ }
880
+ | {
881
+ readonly kind: 'box';
882
+ readonly center: SpatialPoint3;
883
+ readonly rotation: readonly [x: number, y: number, z: number, w: number];
884
+ readonly halfExtents: SpatialPoint3;
885
+ readonly color: string;
886
+ readonly opacity?: number;
887
+ readonly dashed?: boolean;
888
+ }
889
+ | {
890
+ readonly kind: 'capsule';
891
+ readonly center: SpatialPoint3;
892
+ readonly rotation: readonly [x: number, y: number, z: number, w: number];
893
+ readonly radius: number;
894
+ readonly halfHeight: number;
895
+ readonly color: string;
896
+ readonly opacity?: number;
897
+ readonly dashed?: boolean;
898
+ };
899
+
900
+ /** One draggable point. `id` is stable only within its owning node; `label`
901
+ * feeds accessible/editor hints rather than becoming a persisted name. */
902
+ export interface SpatialDragHandle {
903
+ readonly id: string;
904
+ readonly label: string;
905
+ readonly position: SpatialPoint3;
906
+ readonly color: string;
907
+ readonly writable: boolean;
908
+ readonly reason?: string;
909
+ }
910
+
911
+ /** One adapter-owned component projection. `category` is a viewport overlay
912
+ * channel (for example `audio`), not a game-object kind; unknown categories
913
+ * obey the master Helpers toggle instead of being rejected. */
914
+ export interface SpatialHandleLayer {
915
+ readonly id: string;
916
+ readonly category: string;
917
+ readonly guides: readonly SpatialHandleGuide[];
918
+ readonly handles: readonly SpatialDragHandle[];
919
+ }
920
+
921
+ /**
922
+ * Format-neutral direct-manipulation seam for component-owned spatial values.
923
+ *
924
+ * The viewport draws only the returned geometry. During a drag it returns a
925
+ * world-space point to the adapter; the adapter alone knows whether that means
926
+ * an AudioSource distance, a light cone, a collider extent, or something else,
927
+ * and the adapter alone owns live preview + source persistence. `commit` runs
928
+ * once at gesture end, so source/HMR/history are never hammered per pointer
929
+ * move. Absent means this adapter has no spatial component handles.
930
+ */
931
+ export interface SpatialHandlesProvider {
932
+ layers(id: string): readonly SpatialHandleLayer[];
933
+ preview(id: string, handleId: string, worldPosition: SpatialPoint3): void;
934
+ commit(id: string, handleId: string, worldPosition: SpatialPoint3): void | Promise<void>;
586
935
  }
587
936
 
588
937
  export interface AuthoringAdapter {
@@ -598,6 +947,8 @@ export interface AuthoringAdapter {
598
947
  /** The selected node is an atomic asset that can open in the Asset Editor. */
599
948
  readonly assetSubject?: AssetSubjectProvider;
600
949
  readonly related?: RelatedSubjectsProvider;
950
+ /** Source-derived prefab/component instance overrides and native writes. */
951
+ readonly instances?: ComponentInstancesProvider;
601
952
  readonly structure?: StructureProvider;
602
953
  readonly persistence?: PersistenceProvider;
603
954
  /** D12 — per-layer viewport picking. Absent ⇒ this adapter is not pickable. */
@@ -619,6 +970,8 @@ export interface AuthoringAdapter {
619
970
  /** WO-IDX1 — the source location that constructed a node's live object.
620
971
  * Absent ⇒ this adapter indexes no creation sites. */
621
972
  readonly creationSite?: CreationSiteProvider;
973
+ /** Adapter-owned component guides and direct-manipulation points. */
974
+ readonly spatialHandles?: SpatialHandlesProvider;
622
975
  /** Asset drop (hierarchy + viewport). Absent ⇒ this adapter accepts no drops. */
623
976
  readonly assetDrop?: AssetDropProvider;
624
977
  /** Change notification → UI refresh. */
@@ -634,3 +987,80 @@ export interface AuthoringAdapter {
634
987
  */
635
988
  observeTransforms?(listener: (event: TransformObservation) => void): () => void;
636
989
  }
990
+
991
+ /**
992
+ * THE PROVIDER VOCABULARY — every member of {@link AuthoringAdapter} beyond the
993
+ * two an adapter cannot exist without.
994
+ *
995
+ * Why this exists: the editor's coverage warnings are supposed to make stopping
996
+ * short of a native root IMPOSSIBLE, and they cannot do that while the row
997
+ * vocabulary is a hand-written list frozen at what its author happened to know.
998
+ * A list like that goes quiet at "all warnings closed" while whole capabilities
999
+ * — stories, asset drop, related subjects, structure — were never enumerated at
1000
+ * all, so nothing ever warned about them. So the vocabulary IS the type: adding
1001
+ * a provider to `AuthoringAdapter` and forgetting it here is a COMPILE ERROR
1002
+ * (the `Record` below is missing a key), and inventing a key the type does not
1003
+ * have is an excess-property error. There is no third way to add a provider.
1004
+ *
1005
+ * `capabilities` and `hierarchy` are excluded because they are required members
1006
+ * — every adapter has them by construction, so "absent" is not a state they can
1007
+ * be in and a row about them could never say anything.
1008
+ */
1009
+ export type AuthoringProviderKey = Exclude<keyof AuthoringAdapter, 'capabilities' | 'hierarchy'>;
1010
+
1011
+ /**
1012
+ * The exhaustiveness pin. A `Record` over the key union, so the compiler — not
1013
+ * a scan, not a reviewer — is what refuses a vocabulary that has drifted from
1014
+ * the interface above. The values are `true` and carry no meaning: the KEYS are
1015
+ * the payload.
1016
+ */
1017
+ const AUTHORING_PROVIDER_PRESENCE: Readonly<Record<AuthoringProviderKey, true>> = {
1018
+ provenance: true,
1019
+ selection: true,
1020
+ transforms: true,
1021
+ inspector: true,
1022
+ assetSubject: true,
1023
+ related: true,
1024
+ instances: true,
1025
+ structure: true,
1026
+ persistence: true,
1027
+ pickable: true,
1028
+ rects: true,
1029
+ boxEdit: true,
1030
+ text: true,
1031
+ colorSample: true,
1032
+ stories: true,
1033
+ creationSite: true,
1034
+ spatialHandles: true,
1035
+ assetDrop: true,
1036
+ subscribe: true,
1037
+ observeTransforms: true,
1038
+ };
1039
+
1040
+ /**
1041
+ * The vocabulary as an ordered list — declaration order of the interface, so
1042
+ * two reports of the same root are diffable line for line.
1043
+ *
1044
+ * Derived from {@link AUTHORING_PROVIDER_PRESENCE} rather than spelled a second
1045
+ * time: one list that the compiler pins to the type, and nothing downstream can
1046
+ * enumerate a different set.
1047
+ */
1048
+ export const AUTHORING_PROVIDER_KEYS: readonly AuthoringProviderKey[] = Object.keys(
1049
+ AUTHORING_PROVIDER_PRESENCE,
1050
+ ) as AuthoringProviderKey[];
1051
+
1052
+ /**
1053
+ * Which providers this adapter actually exposes — PRESENCE, measured off the
1054
+ * object itself. Nothing here consults `capabilities`, the adapter's class, the
1055
+ * route that built it, or the game's id: an adapter this function has never
1056
+ * heard of gets the same answer as one it has.
1057
+ */
1058
+ export function measureAuthoringProviders(
1059
+ adapter: AuthoringAdapter,
1060
+ ): Readonly<Record<AuthoringProviderKey, boolean>> {
1061
+ const measured = {} as Record<AuthoringProviderKey, boolean>;
1062
+ for (const key of AUTHORING_PROVIDER_KEYS) {
1063
+ measured[key] = (adapter as unknown as Record<string, unknown>)[key] !== undefined;
1064
+ }
1065
+ return measured;
1066
+ }
@@ -15,7 +15,7 @@ export type ConstraintStatus = 'ready' | 'disabled' | 'unresolved' | 'error';
15
15
  export interface ConstraintConfig {
16
16
  /** Stable only within this mounted owner. */
17
17
  readonly id: string;
18
- readonly type: 'two-bone-ik' | 'ccd-ik' | (string & {});
18
+ readonly type: 'two-bone-ik' | 'ccd-ik' | 'aim' | 'rotation' | (string & {});
19
19
  readonly label: string;
20
20
  readonly enabled: boolean;
21
21
  readonly weight: number;
@@ -23,15 +23,22 @@ export interface ConstraintConfig {
23
23
  readonly order: number;
24
24
  }
25
25
 
26
+ export interface ConstraintError {
27
+ readonly value: number;
28
+ readonly unit: 'metres' | 'degrees';
29
+ }
30
+
26
31
  export interface ConstraintSnapshot {
27
32
  readonly status: ConstraintStatus;
28
33
  readonly message?: string;
29
34
  /** Root → effector, using the real bones from the live skeleton. */
30
35
  readonly chain: readonly THREE.Object3D[];
36
+ /** The ordinary object whose transform the constraint affects. */
37
+ readonly constrained: THREE.Object3D | null;
31
38
  readonly target: THREE.Object3D | null;
32
39
  readonly pole: THREE.Object3D | null;
33
- /** Effector-to-target distance after the latest solve, in world units. */
34
- readonly error: number | null;
40
+ /** Residual after the latest solve, in the constraint's native unit. */
41
+ readonly error: ConstraintError | null;
35
42
  }
36
43
 
37
44
  export interface ConstraintMark {
@@ -0,0 +1,29 @@
1
+ /**
2
+ * What every finder answers with.
3
+ *
4
+ * A finder is a PURE function over project source/files with explicit inputs
5
+ * and outputs — it never reads a filesystem, never fetches, and never consults
6
+ * ambient state. What it cannot answer it says so in {@link FinderResult.notes}
7
+ * rather than degrading silently: "has none" and "nobody could look" are
8
+ * different facts, and only the second is a defect.
9
+ */
10
+
11
+ import type { SceneEntry } from '../adapter-module';
12
+
13
+ export interface FinderResult {
14
+ /** Scene-table entries this finder produced, in discovery order. */
15
+ readonly entries: readonly SceneEntry[];
16
+ /**
17
+ * The entry this finder's own answer makes the default, when its answer
18
+ * settles it UNAMBIGUOUSLY (one candidate). A finder never picks between
19
+ * candidates — that is the adapter author's declaration to make, and an
20
+ * absent default stays the honest fact that none was declared.
21
+ */
22
+ readonly default?: string;
23
+ /**
24
+ * Loud, non-fatal diagnostics: a selection identifier that is not in the
25
+ * source, an entrypoint whose bytes could not be read. Every note names the
26
+ * file it is about. The host surfaces these; it never swallows them.
27
+ */
28
+ readonly notes: readonly string[];
29
+ }