@vgai/engine 0.5.2 → 0.5.4

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 (185) hide show
  1. package/README.md +18 -11
  2. package/package.json +10 -6
  3. package/schemas/engine-api.json +1 -68
  4. package/schemas/engine-api.md +1 -32
  5. package/schemas/engine-capabilities.json +30 -42
  6. package/schemas/{vgai-game.schema.json → vgai-project.schema.json} +52 -34
  7. package/src/adapter/adapter-surface.ts +5 -5
  8. package/src/adapter/authoring.ts +168 -226
  9. package/src/adapter/colyseus-networking-adapter.ts +44 -5
  10. package/src/adapter/first-party-systems.ts +156 -42
  11. package/src/adapter/host-context.ts +177 -46
  12. package/src/adapter/index.ts +47 -51
  13. package/src/adapter/ingest/game-contract.ts +2 -2
  14. package/src/adapter/ingest/scene-capture.ts +18 -19
  15. package/src/adapter/ingest/structural-ids.ts +127 -0
  16. package/src/adapter/ingest/upstream-pin.ts +9 -12
  17. package/src/adapter/loop-gate-report.ts +11 -11
  18. package/src/adapter/rapier-physics-adapter.ts +27 -9
  19. package/src/adapter/root-adapter.ts +217 -0
  20. package/src/adapter/{vgai-scene-game-adapter.ts → setup-three-root-adapter.ts} +173 -351
  21. package/src/adapter/system-adapter.ts +80 -63
  22. package/src/ai/navigation.ts +1 -1
  23. package/src/animation/animation-clock.ts +1 -1
  24. package/src/animation/camera-ownership.ts +1 -2
  25. package/src/animation/cubic-spline-interpolant.ts +132 -0
  26. package/src/animation/theatre-clock-binding.ts +2 -2
  27. package/src/animation/theatre-object-binding.ts +4 -4
  28. package/src/animation/xstate-animation-binding.ts +75 -5
  29. package/src/{scene/schema → asset-formats}/camera.ts +2 -5
  30. package/src/{scene/schema → asset-formats}/collider.ts +2 -5
  31. package/src/asset-formats/index.ts +54 -0
  32. package/src/{scene/schema → asset-formats}/instances.ts +10 -6
  33. package/src/{scene/schema → asset-formats}/light.ts +3 -6
  34. package/src/{scene/schema → asset-formats}/material.ts +4 -7
  35. package/src/{scene/schema → asset-formats}/mesh.ts +4 -7
  36. package/src/asset-formats/parse.ts +39 -0
  37. package/src/{scene/schema → asset-formats}/particles.ts +3 -6
  38. package/src/{scene/schema/environment.ts → asset-formats/render-env.ts} +23 -86
  39. package/src/{scene/schema → asset-formats}/tuples.ts +1 -1
  40. package/src/{scene/asset-loaders.ts → asset-loaders.ts} +8 -9
  41. package/src/asset-parse-error.ts +33 -0
  42. package/src/{scene/asset-registry.ts → asset-registry.ts} +1 -1
  43. package/src/assets.ts +1 -1
  44. package/src/audio/wav-encode.ts +9 -9
  45. package/src/canvas-react/engine-bridge.ts +59 -0
  46. package/src/canvas-react/index.ts +50 -0
  47. package/src/canvas-react/pixi-primitive.tsx +202 -0
  48. package/src/canvas-react/pixi-react-adapter.tsx +290 -0
  49. package/src/canvas-react/pixi-react-root-factory.tsx +88 -0
  50. package/src/canvas-react/world-context.ts +328 -0
  51. package/src/core/frame-pacing.ts +100 -0
  52. package/src/core/game-loop.ts +50 -28
  53. package/src/core/seeded-random.ts +7 -7
  54. package/src/core/sim-clock.ts +388 -0
  55. package/src/core/system-runner.ts +17 -63
  56. package/src/core/types.ts +34 -15
  57. package/src/data/data-asset.ts +3 -3
  58. package/src/data/data-check-core.ts +6 -7
  59. package/src/data/data-ref.ts +11 -11
  60. package/src/data/vite-plugin-data.ts +10 -10
  61. package/src/{scene/defaults.ts → defaults.ts} +18 -40
  62. package/src/dev/render-debug-adapter.ts +1 -1
  63. package/src/dev/webgl-frame-capture.ts +1 -1
  64. package/src/ecs/scene-index.ts +439 -0
  65. package/src/ecs/scene-query.ts +43 -0
  66. package/src/{scene → ecs}/user-data.ts +17 -36
  67. package/src/index.ts +7 -9
  68. package/src/input/input-manager.ts +29 -32
  69. package/src/input/input-types.ts +2 -2
  70. package/src/input/schema.ts +5 -5
  71. package/src/loader.ts +57 -0
  72. package/src/manifest/editor-port.ts +69 -0
  73. package/src/manifest/filename.ts +49 -0
  74. package/src/manifest/index.ts +8 -2
  75. package/src/manifest/load-file.ts +11 -0
  76. package/src/manifest/load.ts +65 -77
  77. package/src/manifest/locate.ts +55 -0
  78. package/src/manifest/schema.ts +400 -233
  79. package/src/{scene → physics}/collider-dimensions.ts +3 -3
  80. package/src/physics/physics-registry.ts +1 -1
  81. package/src/{world2d/authoring-2d.ts → pixi/authoring.ts} +24 -11
  82. package/src/pixi/index.ts +43 -0
  83. package/src/{world2d/ingest-iframe-2d.ts → pixi/ingest-iframe.ts} +9 -9
  84. package/src/{world2d/ingest2d.ts → pixi/ingest.ts} +28 -28
  85. package/src/{world2d/physics2d-registry.ts → pixi/physics-registry.ts} +1 -1
  86. package/src/{world2d/scene-capture-2d.ts → pixi/scene-capture.ts} +3 -3
  87. package/src/{world2d/system-adapters-2d.ts → pixi/system-adapters.ts} +2 -2
  88. package/src/react/unmanaged-root-detector.ts +26 -1
  89. package/src/react/use-data.ts +5 -5
  90. package/src/react/use-selection.tsx +15 -42
  91. package/src/react/{game-state.tsx → world-state.tsx} +44 -46
  92. package/src/render/auto-batcher.ts +1 -2
  93. package/src/{scene → render}/instance-mesh.ts +1 -1
  94. package/src/{scene → render}/light-camera-factory.ts +14 -13
  95. package/src/render/lod.ts +17 -0
  96. package/src/{scene → render}/material-factory.ts +8 -7
  97. package/src/{scene → render}/particles-factory.ts +62 -12
  98. package/src/render/render-batch-system.ts +14 -41
  99. package/src/render/render-features.ts +1 -1
  100. package/src/render/render-settings.ts +1 -2
  101. package/src/render/spark-renderer-lifecycle.ts +1 -1
  102. package/src/runtime/create-runtime.ts +352 -463
  103. package/src/runtime/debug-bridge.ts +148 -93
  104. package/src/runtime/debug-registry.ts +79 -54
  105. package/src/runtime/dev-layers.ts +40 -0
  106. package/src/runtime/frame-selector-cache.ts +4 -4
  107. package/src/runtime/game.ts +618 -369
  108. package/src/runtime/gameplay-rng-trap.ts +6 -7
  109. package/src/runtime/input-router.ts +11 -11
  110. package/src/runtime/mount-game.ts +54 -55
  111. package/src/runtime/mount-manifest.ts +154 -150
  112. package/src/runtime/presentation.ts +141 -0
  113. package/src/runtime/render-audio-control.ts +64 -53
  114. package/src/runtime/render-control.ts +45 -51
  115. package/src/runtime/render-seed.ts +3 -4
  116. package/src/runtime/state-bridge.ts +17 -18
  117. package/src/runtime/types.ts +94 -61
  118. package/src/setup/setup-renderer.ts +11 -6
  119. package/src/world3d-react/engine-bridge.ts +46 -33
  120. package/src/world3d-react/index.ts +31 -26
  121. package/src/world3d-react/r3f-adapter.tsx +211 -96
  122. package/src/world3d-react/r3f-root-factory.tsx +91 -0
  123. package/src/world3d-react/renderer-config.ts +137 -0
  124. package/src/world3d-react/world-context.ts +141 -111
  125. package/schemas/entity2d.schema.json +0 -468
  126. package/schemas/prefab.schema.json +0 -9992
  127. package/schemas/scn2d.schema.json +0 -494
  128. package/schemas/vscn.schema.json +0 -10851
  129. package/src/adapter/game-adapter.ts +0 -164
  130. package/src/adapter/ingest/overlay-applier.ts +0 -207
  131. package/src/adapter/ingest/overlay-apply.ts +0 -168
  132. package/src/adapter/ingest/overlay-file.ts +0 -126
  133. package/src/adapter/ingest/overlay-report.ts +0 -176
  134. package/src/animation/gsap-registration.ts +0 -184
  135. package/src/audio/audio-introspection.ts +0 -290
  136. package/src/audio/index.ts +0 -39
  137. package/src/audio/tone-clock-binding.ts +0 -98
  138. package/src/audio/tone-context.ts +0 -175
  139. package/src/audio/tone-offline-render.ts +0 -167
  140. package/src/ecs/component-manager.ts +0 -814
  141. package/src/ecs/game-component.ts +0 -260
  142. package/src/ecs/hmr-swap-report.ts +0 -65
  143. package/src/physics/trigger-dispatch.ts +0 -97
  144. package/src/react/root-adapter.tsx +0 -49
  145. package/src/scene/asset-paths.ts +0 -121
  146. package/src/scene/asset-ref-check.ts +0 -248
  147. package/src/scene/component-registry.ts +0 -51
  148. package/src/scene/parse.ts +0 -204
  149. package/src/scene/scene-apply.ts +0 -407
  150. package/src/scene/scene-diff-schema.ts +0 -115
  151. package/src/scene/scene-diff-types.ts +0 -29
  152. package/src/scene/scene-loader.ts +0 -1526
  153. package/src/scene/scene-query.ts +0 -63
  154. package/src/scene/scene-types.ts +0 -33
  155. package/src/scene/scene-version.ts +0 -40
  156. package/src/scene/schema/animation.ts +0 -46
  157. package/src/scene/schema/audio.ts +0 -25
  158. package/src/scene/schema/entity-ref.ts +0 -78
  159. package/src/scene/schema/entity.ts +0 -189
  160. package/src/scene/schema/index.ts +0 -51
  161. package/src/scene/schema/joint.ts +0 -26
  162. package/src/scene/schema/physics.ts +0 -49
  163. package/src/scene/schema/scene-file.ts +0 -292
  164. package/src/scene/schema/shadow.ts +0 -24
  165. package/src/scene/schema/spline.ts +0 -21
  166. package/src/world2d/asset-paths2d.ts +0 -44
  167. package/src/world2d/capture-to-scene2d.ts +0 -52
  168. package/src/world2d/collision-2d.ts +0 -99
  169. package/src/world2d/entity2d-asset.ts +0 -22
  170. package/src/world2d/index.ts +0 -91
  171. package/src/world2d/physics2d-transform.ts +0 -173
  172. package/src/world2d/physics2d-units.ts +0 -10
  173. package/src/world2d/pixi-game-adapter.ts +0 -439
  174. package/src/world2d/pixi-surface.ts +0 -78
  175. package/src/world2d/scene2d-identity.ts +0 -49
  176. package/src/world2d/scene2d-loader.ts +0 -433
  177. package/src/world2d/schema/entity2d.ts +0 -163
  178. package/src/world2d/schema/physics2d.ts +0 -64
  179. package/src/world2d/schema/sprite.ts +0 -99
  180. package/src/world2d/schema/tilemap.ts +0 -39
  181. package/src/world2d/schema/tuples2d.ts +0 -25
  182. package/src/world2d/transform-writer-2d.ts +0 -42
  183. package/src/world2d/types.ts +0 -74
  184. package/src/world3d-react/behavior.tsx +0 -146
  185. /package/src/{scene → render}/mesh-shadow.ts +0 -0
@@ -23,8 +23,8 @@
23
23
  * than silently simulating (or rendering) the wrong shape.
24
24
  */
25
25
 
26
- import { DEFAULTS } from './defaults';
27
- import type { SceneCollider } from './scene-types';
26
+ import type { ColliderDescriptor } from '../asset-formats/collider';
27
+ import { DEFAULTS } from '../defaults';
28
28
 
29
29
  /** Axes are considered "uniform enough" within this tolerance (float error only). */
30
30
  const UNIFORM_SCALE_EPSILON = 1e-4;
@@ -51,7 +51,7 @@ export interface ColliderWorldDimensions {
51
51
  * non-uniform world scale (beyond {@link UNIFORM_SCALE_EPSILON}).
52
52
  */
53
53
  export function computeColliderWorldDimensions(
54
- collider: SceneCollider,
54
+ collider: ColliderDescriptor,
55
55
  worldScale: readonly [number, number, number],
56
56
  entityLabel: string,
57
57
  ): ColliderWorldDimensions {
@@ -13,7 +13,7 @@ export interface PhysicsRefs {
13
13
  *
14
14
  * Replaces the old bitecs RigidBodyRef/ColliderRef SoA components. Two indexes:
15
15
  * - `Object3D → {body, collider}` for the postPhysics transform writer and for
16
- * GameComponents/editor to reach an entity's physics.
16
+ * game code/editor to reach an entity's physics.
17
17
  * - `colliderHandle → Object3D` reverse index for collision/trigger dispatch
18
18
  * (Rapier collision events report collider handles).
19
19
  */
@@ -1,10 +1,10 @@
1
1
  import type { Container } from 'pixi.js';
2
2
 
3
3
  /**
4
- * AuthoringAdapter-2D — the world2d analog of the editor AuthoringAdapter /
5
- * IngestAuthoringAdapter, implemented DIRECTLY over a live PixiJS display tree
6
- * (no fabricated Scene2D / SceneEntity — the anti-shim rule). Used by the editor to
7
- * inspect + edit world2d entities (first-party OR an ingested unmodified game),
4
+ * AuthoringAdapter-2D — the Pixi analog of the editor AuthoringAdapter /
5
+ * LiveThreeAuthoringAdapter, implemented DIRECTLY over a live PixiJS display tree
6
+ * (no fabricated document — the anti-shim rule). Used by the editor to
7
+ * inspect + edit Pixi display-tree entities (first-party OR an ingested unmodified game),
8
8
  * with stable structural-path ids so edits re-bind after the tree rebuilds.
9
9
  */
10
10
 
@@ -29,7 +29,7 @@ export interface Property2D {
29
29
  value: unknown;
30
30
  }
31
31
 
32
- /** A persisted edit overlay keyed by structural-path id (the 2D analog of IngestOverlay). */
32
+ /** An in-memory edit overlay keyed by structural-path id (2D). */
33
33
  export interface Override2D {
34
34
  label?: string;
35
35
  alpha?: number;
@@ -62,8 +62,16 @@ export class AuthoringAdapter2D {
62
62
  this.refresh();
63
63
  }
64
64
 
65
- /** Walk the live tree assigning deterministic structural-path ids, reapply overlay. */
66
- refresh(): { count: number; sprites: number } {
65
+ /**
66
+ * Walk the live tree assigning deterministic structural-path ids.
67
+ *
68
+ * Separate from {@link refresh} because the two are wanted at different rates: the ids have to be
69
+ * current whenever the tree is READ (a world that mounts its content asynchronously — a
70
+ * translated port waiting on its resources, anything behind Suspense — commits real nodes after
71
+ * the adapter was handed its root, and an unindexed node is one the hierarchy cannot name), while
72
+ * re-applying the edit overlay is a WRITE and must happen only when something asked for it.
73
+ */
74
+ reindex(): { count: number; sprites: number } {
67
75
  this.byId.clear();
68
76
  let count = 0;
69
77
  let sprites = 0;
@@ -80,11 +88,16 @@ export class AuthoringAdapter2D {
80
88
  this.root.children.forEach((c, i) => {
81
89
  visit(c as Container, `${i}`);
82
90
  });
83
- // Reapply any saved overlay edits to the live tree.
84
- for (const [id, ov] of Object.entries(this.overlay)) this.applyOverride(id, ov);
85
91
  return { count, sprites };
86
92
  }
87
93
 
94
+ /** {@link reindex}, then reapply every saved overlay edit to the live tree. */
95
+ refresh(): { count: number; sprites: number } {
96
+ const counts = this.reindex();
97
+ for (const [id, ov] of Object.entries(this.overlay)) this.applyOverride(id, ov);
98
+ return counts;
99
+ }
100
+
88
101
  roots(): EditorNode2D[] {
89
102
  return this.root.children.map((c) => this.toNode(c as Container));
90
103
  }
@@ -186,7 +199,7 @@ export class AuthoringAdapter2D {
186
199
  * it into the in-memory overlay so a later `serializeOverlay()`/save preserves it
187
200
  * and a later `refresh()` re-applies it again. Ids absent from the current tree
188
201
  * are skipped (orphaned, not an error) — the 2D analog of
189
- * `IngestAuthoringAdapter.applyOverlay` (T3.2 slice 3). Used both for the initial
202
+ * `LiveThreeAuthoringAdapter.applyOverlay` (T3.2 slice 3). Used both for the initial
190
203
  * reload (constructor `opts.overlay`) and for `PersistenceProvider.applyExternal`
191
204
  * (re-apply a changed overlay file without a full remount).
192
205
  */
@@ -217,7 +230,7 @@ export class AuthoringAdapter2D {
217
230
  for (const [id, ov] of Object.entries(this.overlay)) this.applyOverride(id, ov);
218
231
  this.dirty = true;
219
232
  }
220
- /** Clear the dirty flag after a successful save (mirrors IngestAuthoringAdapter). */
233
+ /** Clear the dirty flag after a successful save (mirrors LiveThreeAuthoringAdapter). */
221
234
  markClean(): void {
222
235
  this.dirty = false;
223
236
  }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * pixi — the PixiJS surface substrate.
3
+ *
4
+ * PixiJS is opt-in: the engine core never imports it, so a three-only bundle
5
+ * never pays for it. What lives here is what the host needs to mount and
6
+ * inspect a PixiJS game it did not write — the render-call capture trap, the
7
+ * in-realm and iframe mounts, the live display-tree authoring adapter, and the
8
+ * Rapier-2D handle registry the editor's transform coordination reads.
9
+ */
10
+
11
+ export {
12
+ AuthoringAdapter2D,
13
+ type EditorNode2D,
14
+ type Overlay2D,
15
+ type Override2D,
16
+ type Property2D,
17
+ type Transform2DValue,
18
+ } from './authoring';
19
+ export {
20
+ type ExtraDepsResolution2D,
21
+ type IngestGame2D,
22
+ type IngestMount2D,
23
+ mountIngestGame2D,
24
+ } from './ingest';
25
+ export {
26
+ type EmbedMount2D,
27
+ type IframeReachableMount2D,
28
+ type IframeReachableMultiOpts,
29
+ mountIngestGame2DEmbed,
30
+ mountIngestGame2DIframeReachable,
31
+ mountIngestGame2DIframeReachableMulti,
32
+ } from './ingest-iframe';
33
+ export {
34
+ createPhysics2DRegistry,
35
+ type Physics2DRefs,
36
+ type Physics2DRegistry,
37
+ } from './physics-registry';
38
+ export {
39
+ type CapturedRuntime2D,
40
+ installSceneCapture2D,
41
+ type SceneCapture2DHandle,
42
+ } from './scene-capture';
43
+ export { createPhysicsAdapter2D, type PhysicsAdapter2D } from './system-adapters';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * world2d iframe ingest tiers — the PixiJS analogs of the 3D ingest-iframe adapters.
2
+ * Pixi iframe ingest tiers — the PixiJS analogs of the 3D ingest-iframe adapters.
3
3
  *
4
4
  * - OPAQUE EMBED (tier 0-1): a prebuilt/self-contained bundle runs in a sandboxed
5
5
  * iframe; hosted + sized + disposed, NOT introspectable.
@@ -12,7 +12,7 @@ import {
12
12
  type CapturedRuntime2D,
13
13
  installSceneCapture2D,
14
14
  type SceneCapture2DHandle,
15
- } from './scene-capture-2d';
15
+ } from './scene-capture';
16
16
 
17
17
  export interface EmbedMount2D {
18
18
  iframe: HTMLIFrameElement;
@@ -77,13 +77,13 @@ export interface IframeReachableMultiOpts {
77
77
  */
78
78
  extraDeps?: Record<string, Record<string, unknown>>;
79
79
  /**
80
- * D-Z3 (docs/WAVE3-ADAPTER-PLUMBING-DESIGN.md) bare-specifier -> VERBATIM
81
- * URL importmap entries (the project-node_modules fallback for a specifier
82
- * that isn't in the host-namespace registry but IS installed in the open
83
- * project's own `node_modules`). Written into the importmap DIRECTLY
84
- * alongside the `extraDeps` blob-namespace entries — no host-side import,
85
- * no blob wrapping, no decision logic here: this function just merges
86
- * whatever `Record<specifier, url>` the caller (editor-side
80
+ * Bare-specifier -> VERBATIM URL importmap entries (the
81
+ * project-node_modules fallback for a specifier that isn't in the
82
+ * host-namespace registry but IS installed in the open project's own
83
+ * `node_modules`). Written into the importmap DIRECTLY alongside the
84
+ * `extraDeps` blob-namespace entries — no host-side import, no blob
85
+ * wrapping, no decision logic here: this function just merges whatever
86
+ * `Record<specifier, url>` the caller (editor-side
87
87
  * `host-namespace-registry.ts`) already decided. The URL's own transitive
88
88
  * `import 'pixi.js'` resolves through this SAME importmap back to the host
89
89
  * pixi trap (§0.3), keeping module identity converged without a second pixi
@@ -1,12 +1,12 @@
1
- import { installSceneCapture2D, type SceneCapture2DHandle } from './scene-capture-2d';
1
+ import { installSceneCapture2D, type SceneCapture2DHandle } from './scene-capture';
2
2
 
3
3
  /**
4
- * D-Z3 (docs/WAVE3-ADAPTER-PLUMBING-DESIGN.md) — a manifest's `extraDeps`
5
- * resolve to TWO kinds of importmap entry: `namespaces` are today's
6
- * registry-known bare specifiers (host-namespace-registry.ts), re-exported
7
- * via a blob module (`ingest-iframe-2d.ts`'s `makeHostNamespaceReexportUrl`);
8
- * `urls` are the D-Z3 fallback — a specifier that isn't registered but IS
9
- * present in the open project's own `node_modules`, mapped DIRECTLY to its
4
+ * A manifest's `extraDeps` resolve to TWO kinds of importmap entry:
5
+ * `namespaces` are today's registry-known bare specifiers
6
+ * (host-namespace-registry.ts), re-exported via a blob module
7
+ * (`ingest-iframe-2d.ts`'s `makeHostNamespaceReexportUrl`); `urls` are the
8
+ * D-Z3 fallback — a specifier that isn't registered but IS present in the
9
+ * open project's own `node_modules`, mapped DIRECTLY to its
10
10
  * `/project-game-static/node_modules/<spec>/<esm entry>` URL (no host-side
11
11
  * import, no blob — the dep executes in the iframe realm and its own
12
12
  * transitive `import 'pixi.js'` resolves through the same importmap back to
@@ -21,7 +21,7 @@ export interface ExtraDepsResolution2D {
21
21
  urls: Record<string, string>;
22
22
  }
23
23
 
24
- /** A registered unmodified PixiJS game (the world2d analog of `IngestGame`). */
24
+ /** A registered unmodified PixiJS game (the Pixi analog of `IngestGame`). */
25
25
  export interface IngestGame2D {
26
26
  id: string;
27
27
  name: string;
@@ -32,18 +32,19 @@ export interface IngestGame2D {
32
32
  tier?: 'shared' | 'iframe-reachable' | 'opaque-embed';
33
33
  captureTimeoutMs?: number;
34
34
  /**
35
- * IFRAME-REACHABLE-MULTI fields (Track P, docs/PIXI-INGEST-LANDING-DESIGN.md
36
- * §2): an externalized built bundle mounted via
37
- * `mountIngestGame2DIframeReachableMulti` (`./ingest-iframe-2d.ts`) instead
38
- * of `load()` populated only when `tier` is `'iframe-reachable'` AND
39
- * `load` is absent. Mirror `IframeReachableMultiOpts`'s fields, EXCEPT
40
- * `extraDeps`/`pixiNamespace` are THUNKS here, not resolved values — the
41
- * same "lazy, only invoked at mount time" shape `load` already uses, so
42
- * building this descriptor (`resolveIngest2DDescriptor` / `discovery2d.ts`,
43
- * editor-side) never has to be async just to wire up which specifiers/
44
- * version-skew URL a game needs; only actually MOUNTING it (the ingest-mode
45
- * mount bridge) pays for the dynamic import of `@pixi/sound`/`gsap`/spine/
46
- * a standalone pixi build.
35
+ * IFRAME-REACHABLE-MULTI fields (Track P): an externalized built bundle
36
+ * mounted via `mountIngestGame2DIframeReachableMulti`
37
+ * (`./ingest-iframe-2d.ts`) instead of `load()` — populated only when `tier`
38
+ * is `'iframe-reachable'` AND `load` is absent. Mirror
39
+ * `IframeReachableMultiOpts`'s fields, EXCEPT `extraDeps`/`pixiNamespace` are
40
+ * THUNKS here, not resolved values — the same "lazy, only invoked at mount
41
+ * time" shape `load` already uses, so building this descriptor
42
+ * (`resolveIngest2DDescriptor` / the canvas surface's
43
+ * `ingest/surface-canvas.ts`, editor-side Wave 0.6 replaced the three
44
+ * per-surface `discovery*.ts` modules with one manifest-driven registry) never
45
+ * has to be async just to wire up which specifiers/ version-skew URL a game
46
+ * needs; only actually MOUNTING it (the ingest-mode mount bridge) pays for the
47
+ * dynamic import of `@pixi/sound`/`gsap`/spine/ a standalone pixi build.
47
48
  */
48
49
  bundleUrl?: string;
49
50
  /** <base href> for the iframe (see `IframeReachableMultiOpts.baseHref`). */
@@ -65,14 +66,13 @@ export interface IngestGame2D {
65
66
  /** DOM the game expects before boot (see `IframeReachableMultiOpts.bodyHtml`). */
66
67
  bodyHtml?: string;
67
68
  /**
68
- * Lazily read an OPAQUE prebuilt bundle's self-contained HTML (D-W2,
69
- * docs/WAVE4-FTUE-HARDENING-DESIGN.md) — the pixi twin of `IngestGame.embedHtml`.
70
- * Always a lazy thunk here (no in-tree world2d opaque-embed fixture exists
71
- * yet to need a plain-string form, unlike the three side):
72
- * `resolveIngest2DDescriptor` (adapter-resolver.ts) builds one that reads
69
+ * Lazily read an OPAQUE prebuilt bundle's self-contained HTML (D-W2) — the pixi
70
+ * twin of `IngestGame.embedHtml`. Always a lazy thunk here (no in-tree Pixi
71
+ * opaque-embed fixture exists yet to need a plain-string form, unlike the three
72
+ * side): `resolveIngest2DDescriptor` (adapter-resolver.ts) builds one that reads
73
73
  * the declared `entryHtml` file's bytes through the `/project-game-static/`
74
- * route, only when the game is actually mounted. The host runs the
75
- * resolved HTML in a sandboxed iframe at the embed-only floor (Tier 0-1),
74
+ * route, only when the game is actually mounted. The host runs the resolved HTML
75
+ * in a sandboxed iframe at the embed-only floor (Tier 0-1),
76
76
  * `mountIngestGame2DEmbed` (`./ingest-iframe-2d.ts`).
77
77
  */
78
78
  embedHtml?: () => Promise<string>;
@@ -105,7 +105,7 @@ export async function mountIngestGame2D(
105
105
  await game.load?.();
106
106
  } catch (err) {
107
107
  capture.uninstall();
108
- throw new Error(`world2d ingest: game "${game.id}" failed to load: ${err}`);
108
+ throw new Error(`pixi ingest: game "${game.id}" failed to load: ${err}`);
109
109
  }
110
110
 
111
111
  const timeout = opts.captureTimeoutMs ?? game.captureTimeoutMs ?? 8000;
@@ -9,7 +9,7 @@ export interface Physics2DRefs {
9
9
 
10
10
  /**
11
11
  * Maps PixiJS display objects to their Rapier 2D bodies/colliders and back —
12
- * the world2d analog of `PhysicsRegistry`. Two indexes:
12
+ * the Pixi analog of `PhysicsRegistry`. Two indexes:
13
13
  * - `Container → {body, collider}` for the postPhysics transform writer.
14
14
  * - `colliderHandle → Container` reverse index for collision/trigger dispatch.
15
15
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Scene capture for world2d — the PixiJS analog of the three.js render
2
+ * Scene capture for the Pixi surface — the PixiJS analog of the three.js render
3
3
  * accessor-trap (`adapter/ingest/scene-capture.ts`).
4
4
  *
5
5
  * An unmodified PixiJS game owns its own `Application`, `stage`, renderer, and
@@ -18,7 +18,7 @@
18
18
  * way (degrade to iframe-reachable or opaque-embed tiers — see the spec).
19
19
  */
20
20
 
21
- /** A live world2d runtime captured from an external PixiJS game. */
21
+ /** A live Pixi runtime captured from an external PixiJS game. */
22
22
  export interface CapturedRuntime2D {
23
23
  /** The game's root stage Container. */
24
24
  stage: unknown;
@@ -92,7 +92,7 @@ export function installSceneCapture2D(pixiNamespace: unknown): SceneCapture2DHan
92
92
  if (i >= 0) waiters.splice(i, 1);
93
93
  reject(
94
94
  new Error(
95
- `world2d scene capture timed out after ${timeoutMs}ms — the game never ` +
95
+ `pixi scene capture timed out after ${timeoutMs}ms — the game never ` +
96
96
  `rendered, or it bundles its own (un-shared) copy of pixi.js.`,
97
97
  ),
98
98
  );
@@ -1,8 +1,8 @@
1
1
  import type { Container } from 'pixi.js';
2
- import type { Physics2DRegistry } from './physics2d-registry';
2
+ import type { Physics2DRegistry } from './physics-registry';
3
3
 
4
4
  /**
5
- * PhysicsAdapter2D — the world2d analog of the 3D PhysicsAdapter (rapier-physics-adapter).
5
+ * PhysicsAdapter2D — the Pixi analog of the 3D PhysicsAdapter (rapier-physics-adapter).
6
6
  * Lets the editor coordinate editing of a Rapier-2D-driven entity WITHOUT owning the
7
7
  * simulation: report transform ownership, then freeze → commit → unfreeze so a drag
8
8
  * sticks instead of being stomped by the next physics step.
@@ -8,15 +8,40 @@ function lineAt(source: string, offset: number): number {
8
8
  return source.slice(0, offset).split('\n').length;
9
9
  }
10
10
 
11
+ /**
12
+ * The project's own `dom`-root adapter implementation, which is ALLOWED to
13
+ * create a React root because creating it is the whole job.
14
+ *
15
+ * The rule here is "a React root must come from a declared manifest root, not
16
+ * from game code mounting a second lifecycle beside it". That held trivially
17
+ * while the only `createRoot` in existence lived in the engine. It stopped
18
+ * holding when the `react-root` capability moved INTO the project (`vgai add
19
+ * react-root`): its `createRoot` is precisely what serves a declared `dom`
20
+ * root — the opposite of bypassing the manifest.
21
+ *
22
+ * Matched on the capability's DIRECTORY rather than a filename, so that
23
+ * editing the copied source — the entire point of the copied-source model —
24
+ * cannot re-arm the rule against itself. A Vue or Svelte replacement never
25
+ * trips it at all: only React DOM's own API is detected.
26
+ */
27
+ const DOM_ROOT_ADAPTER_DIR = /(^|[/\\])src[/\\]lib[/\\]react-root[/\\]/;
28
+
11
29
  /**
12
30
  * Find project-owned React DOM root creation. VGAI hosts React through
13
31
  * manifest adapter roots, so any of these calls in game source creates an
14
- * unmanaged second lifecycle and is a hard validation error.
32
+ * unmanaged second lifecycle and is a hard validation error — except in the
33
+ * project's own dom-root adapter (see {@link DOM_ROOT_ADAPTER_DIR}).
34
+ *
35
+ * The exemption lives HERE, in the detector, rather than in either caller:
36
+ * the editor's validate-on-change watcher and `vgai validate` both scan the
37
+ * same tree, and a rule that means two different things depending on who asks
38
+ * is worse than no rule.
15
39
  */
16
40
  export function detectUnmanagedReactRoots(
17
41
  source: string,
18
42
  file: string,
19
43
  ): UnmanagedReactRootFinding[] {
44
+ if (DOM_ROOT_ADAPTER_DIR.test(file)) return [];
20
45
  const findings: UnmanagedReactRootFinding[] = [];
21
46
  const aliases = new Map<string, 'createRoot' | 'hydrateRoot'>();
22
47
 
@@ -1,6 +1,6 @@
1
1
  /**
2
- * React hooks for data assets — the tool-hooks half of
3
- * `docs/DATA-TOOLS-DESIGN.md` §3.3, shipped with W4 (dock tools).
2
+ * React hooks for data assets — the tool-hooks half, shipped with W4 (dock
3
+ * tools).
4
4
  *
5
5
  * Lives under `packages/engine/src/react/` deliberately: this is the ONE
6
6
  * directory of the engine allowed to value-import react (colocated with
@@ -72,7 +72,7 @@ export async function writeData(
72
72
  if (!projectRelativePath.startsWith('src/data/') || !projectRelativePath.endsWith('.data.json')) {
73
73
  throw new Error(
74
74
  `writeData: invalid path ${JSON.stringify(projectRelativePath)} — data assets live at ` +
75
- 'src/data/**/*.data.json, addressed project-relative (docs/DATA-TOOLS-DESIGN.md §2.1), ' +
75
+ 'src/data/**/*.data.json, addressed project-relative, ' +
76
76
  "e.g. writeData('src/data/tuning.data.json', next).",
77
77
  );
78
78
  }
@@ -109,8 +109,8 @@ export async function writeData(
109
109
  } catch (err) {
110
110
  throw new Error(
111
111
  'writeData: could not reach the editor dev server (/__editor/data-file) — this helper is ' +
112
- 'editor-tool plumbing (docs/DATA-TOOLS-DESIGN.md §3.3) and only works inside the running ' +
113
- 'editor. Game code must not write data assets: they are immutable at runtime (§2.1) — ' +
112
+ 'editor-tool plumbing and only works inside the running ' +
113
+ 'editor. Game code must not write data assets: they are immutable at runtime — ' +
114
114
  `keep runtime state in your game's own store. (${String(err)})`,
115
115
  );
116
116
  }
@@ -1,11 +1,7 @@
1
1
  /**
2
- * W3.1 of docs/DATA-TOOLS-DESIGN.md §3.3 — `useSelection()`, the third
3
- * tool-hooks contract member (alongside `useData`/`writeData` from
4
- * `use-data.ts`). Where those two hooks are about DATA ASSETS, this one is
5
- * about the currently-selected ENTITY and the components authored on it —
6
- * the gap a real consumer (`critter-types-w3b`'s `critter-type.tool.tsx`,
7
- * §9.4) hit hard: with no way to read a component's own authored config, it
8
- * had to recover species from `node.label` (a display string, not data).
2
+ * W3.1 — `useSelection()`, the third tool-hooks contract member (alongside
3
+ * `useData`/`writeData` from `use-data.ts`). Where those two hooks are about
4
+ * DATA ASSETS, this one is about the currently-selected ENTITY.
9
5
  *
10
6
  * Scope, v1:
11
7
  * - INSPECTOR-placement tools only (`placement: 'inspector'`,
@@ -14,17 +10,9 @@
14
10
  * (`ToolHost`/`BottomPanel`) have no adapter/selection in scope and never
15
11
  * get a provider. Calling `useSelection()` there throws a teaching error,
16
12
  * contained by the tool's `ToolErrorBoundary` (the editor survives).
17
- * - READ-only. `config` is a shallow copy of the authored record; write
18
- * through `InspectorProvider.set('components.<Type>.<field>', v)` /
19
- * `writeData` (the existing paths), never by mutating what this hook
20
- * returns. Per-entity WRITE-from-tool is deferred with `useTunable` (§10).
21
- * - Honest degrades, not errors, when the adapter is thinner than the vgai
22
- * first-party one: no `components` provider at all ⇒ `components: []`;
23
- * a `components` provider present but with no optional `config()`
24
- * accessor ⇒ each entry still appears (from `list()`) but with
25
- * `config: null`. Both mirror the `AuthoringAdapter` optional-provider
26
- * doctrine (§4) — an ingest/UI adapter that never implements `components`
27
- * was already legal, and stays legal.
13
+ * - READ-only. Write through `InspectorProvider.set` / `writeData` (the
14
+ * existing paths), never by mutating what this hook returns. Per-entity
15
+ * WRITE-from-tool is deferred with `useTunable` (§10).
28
16
  *
29
17
  * Liveness — two existing paths reused, no new mechanism (§4's "reuse that
30
18
  * path" rule):
@@ -32,12 +20,11 @@
32
20
  * `useSyncExternalStore(store.subscribe, store.getSnapshot)` re-renders
33
21
  * the panel, each matched `Section` gets the new `nodeId` prop, and
34
22
  * `InspectorToolSection` re-creates this provider's context value.
35
- * 2. Config edits while the SAME node stays selected: `adapter.subscribe`
36
- * fires (the vgai adapter's store notifies on every `updateEntity`,
37
- * which is what `InspectorProvider.set` goes through) this hook keeps
38
- * a local version counter bumped per notification (exactly
39
- * `game-state.tsx`'s `useWorldObservation` pattern: the adapter has no
40
- * snapshot/version of its own, `subscribe` is fire-only) and recomputes.
23
+ * 2. Edits while the SAME node stays selected: `adapter.subscribe` fires —
24
+ * this hook keeps a local version counter bumped per notification
25
+ * (exactly `game-state.tsx`'s `useRootObservation` pattern: the adapter
26
+ * has no snapshot/version of its own, `subscribe` is fire-only) and
27
+ * recomputes.
41
28
  */
42
29
 
43
30
  import {
@@ -76,22 +63,13 @@ export function EditorSelectionProvider({
76
63
  return <SelectionContext.Provider value={value}>{children}</SelectionContext.Provider>;
77
64
  }
78
65
 
79
- /** One component attached to the selected entity, with its authored config
80
- * if the adapter can produce one (see the module doc's degrade rules). */
81
- export interface SelectedComponent {
82
- type: string;
83
- config: Record<string, unknown> | null;
84
- }
85
-
86
66
  export interface EditorSelection {
87
67
  nodeId: string | null;
88
68
  node: EditorNode | null;
89
- components: SelectedComponent[];
90
69
  }
91
70
 
92
71
  /**
93
- * The selected entity, live: its node, and every component attached to it
94
- * with that component's own authored config (read-only — see module doc).
72
+ * The selected entity, live (read-only see module doc).
95
73
  *
96
74
  * Throws outside an `EditorSelectionProvider` — inspector tools always render
97
75
  * inside one; a dock tool or game-code caller does not, and the error names
@@ -102,7 +80,7 @@ export function useSelection(): EditorSelection {
102
80
  if (!ctx) {
103
81
  throw new Error(
104
82
  'useSelection: no editor selection in context — this hook only works inside an editor-hosted ' +
105
- "INSPECTOR tool (placement: 'inspector', docs/DATA-TOOLS-DESIGN.md §3.3/§3.2). Dock tools and " +
83
+ "INSPECTOR tool (placement: 'inspector'). Dock tools and " +
106
84
  'game code have no selection to read.',
107
85
  );
108
86
  }
@@ -110,7 +88,7 @@ export function useSelection(): EditorSelection {
110
88
 
111
89
  // Local version counter bumped per adapter notification — the adapter has
112
90
  // no snapshot/version of its own, `subscribe` is fire-only (same pattern
113
- // as `useWorldObservation`'s `versionRef`, game-state.tsx).
91
+ // as `useRootObservation`'s `versionRef`, game-state.tsx).
114
92
  const versionRef = useRef(0);
115
93
  const version = useSyncExternalStore(
116
94
  (onStoreChange) =>
@@ -123,12 +101,7 @@ export function useSelection(): EditorSelection {
123
101
 
124
102
  return useMemo(() => {
125
103
  const node = nodeId ? adapter.hierarchy.node(nodeId) : null;
126
- const types = nodeId ? (adapter.components?.list(nodeId) ?? []) : [];
127
- const components: SelectedComponent[] = types.map(({ type }) => ({
128
- type,
129
- config: nodeId ? (adapter.components?.config?.(nodeId, type) ?? null) : null,
130
- }));
131
- return { nodeId, node, components };
104
+ return { nodeId, node };
132
105
  // `version` has no value of its own — only its CHANGE matters, as the
133
106
  // liveness signal described above.
134
107
  }, [adapter, nodeId, version]);