@vgai/engine 0.5.2 → 0.5.3

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
@@ -1,60 +1,28 @@
1
1
  /**
2
- * First-party implementations of the remaining `SystemAdapters` — input, assets,
2
+ * First-party implementations of the remaining `SystemAdapters` — audio and
3
3
  * navigation — each a thin coordination/introspection boundary over the real
4
4
  * engine subsystem (the original vision named these as first-class System
5
5
  * adapters; physics + networking shipped earlier). The editor speaks only the
6
- * interfaces; these wrap `InputManager`, the asset loader, and `NavMeshManager`
7
- * so an external game could supply its own equivalents. (The former
6
+ * interfaces; these wrap the engine audio bus and `NavMeshManager` so an
7
+ * external game could supply its own equivalents. (The former
8
8
  * `AnimationAdapter`/`createAnimationAdapter` over the `AnimGraph` map was
9
- * removed by E5 — AnimGraph no longer exists; see
10
- * docs/AI-NATIVE-AUTHORING-IMPLEMENTATION-SPEC.md §3.3/§11 E5.)
9
+ * removed by E5 — AnimGraph no longer exists. The former
10
+ * `createInputManagerAdapter`/`createVgaiAssetAdapter` went with
11
+ * `SystemAdapters.input`/`.assets` — registered, never read by anything.)
11
12
  */
12
13
 
13
14
  import type * as THREE from 'three';
14
15
  import type { NavMeshManager } from '../ai/navigation';
15
- import type { InputManager } from '../input/input-manager';
16
- import { resolveUrl } from '../loader';
17
16
  import type { AudioContext as GameAudio } from '../setup/setup-audio';
18
17
  import type {
19
- ActionValueSnapshot,
20
- AssetAdapter,
21
18
  AudioAdapter,
22
- InputAdapter,
19
+ AudioGraphNode,
20
+ AudioMeterFrame,
21
+ AudioMeterHandle,
23
22
  NavigationAdapter,
24
23
  NavPoint,
25
24
  } from './system-adapter';
26
25
 
27
- /** First-party `InputAdapter` over the engine `InputManager`. Reads each action through
28
- * `InputManager.readAction`, dispatched on that action's OWN declared `valueType`
29
- * (`getActionValueType`) — never a hardcoded getter — so a scalar/Vector2/pointer action
30
- * surfaces its real typed value instead of being coerced through `isPressed` (F1-followup;
31
- * `readAction` on the wrong type would throw, so dispatch must follow the declared type). */
32
- export function createInputManagerAdapter(input: InputManager): InputAdapter {
33
- return {
34
- poll: () => input.poll(),
35
- actions: () => {
36
- const out: Record<string, ActionValueSnapshot> = {};
37
- for (const name of input.actionNames()) {
38
- const type = input.getActionValueType(name);
39
- // `readAction`'s return type is inferred from a literal `expectedType`; with `type`
40
- // widened to `ActionValueType` here it collapses to `boolean | number | Vector2`, so
41
- // digital's `boolean` is normalized to the adapter's 1/0 the same way the old digital-
42
- // only adapter did — every other value type already reads as `number | Vector2`.
43
- const raw = input.readAction(name, type);
44
- out[name] = { type, value: typeof raw === 'boolean' ? (raw ? 1 : 0) : raw };
45
- }
46
- return out;
47
- },
48
- };
49
- }
50
-
51
- /** First-party `AssetAdapter` — resolves URLs through the engine loader (which
52
- * applies the project base + downloaded-asset remap). External/ingest games can
53
- * supply their own to redirect relative paths. */
54
- export function createVgaiAssetAdapter(): AssetAdapter {
55
- return { resolve: (url) => resolveUrl(url) };
56
- }
57
-
58
26
  /**
59
27
  * First-party `AudioAdapter` over the engine's master-gain bus
60
28
  * (`setup-audio.ts`'s `AudioContext.masterGain`) — the seam `Game.play.pause()`
@@ -64,10 +32,42 @@ export function createVgaiAssetAdapter(): AssetAdapter {
64
32
  * (the editor's `MuteButton`, `PlayBar.tsx`): pausing while already
65
33
  * user-muted resumes muted, exactly as a user would expect.
66
34
  */
35
+ function audioNodeTypeName(value: object): string {
36
+ const name = (value as { constructor?: { name?: string } }).constructor?.name;
37
+ return name && name !== 'Object' ? name : 'AudioNode';
38
+ }
39
+
40
+ /** This world's real native context, when it has one (headless worlds do not). */
41
+ function nativeAudioContext(audio: GameAudio): (BaseAudioContext & Partial<AudioContext>) | null {
42
+ const context = (audio.listener as { context?: BaseAudioContext } | undefined)?.context;
43
+ return context && typeof context.state === 'string'
44
+ ? (context as BaseAudioContext & Partial<AudioContext>)
45
+ : null;
46
+ }
47
+
48
+ /**
49
+ * Live meter handles per adapter, so a world teardown can release analyser
50
+ * taps a consumer forgot to dispose.
51
+ *
52
+ * This registry follows `acquireMeters` out of `audio-introspection.ts`: the
53
+ * safety net has to live wherever the taps are created, and they are created
54
+ * here now. Keyed weakly so an adapter that is simply dropped takes its entry
55
+ * with it.
56
+ */
57
+ const meterHandlesByAdapter = new WeakMap<AudioAdapter, Set<AudioMeterHandle>>();
58
+
59
+ /** Release every meter tap still open on `adapter` (world stop / teardown). */
60
+ export function releaseAudioMeters(adapter: AudioAdapter): void {
61
+ const handles = meterHandlesByAdapter.get(adapter);
62
+ if (!handles) return;
63
+ for (const handle of [...handles]) handle.dispose();
64
+ }
65
+
67
66
  export function createAudioSystemAdapter(audio: GameAudio): AudioAdapter {
68
67
  let muted = false;
69
68
  let priorGain = audio.masterGain.gain.value;
70
- return {
69
+ const liveMeterHandles = new Set<AudioMeterHandle>();
70
+ const adapter: AudioAdapter = {
71
71
  resume: () => audio.resume(),
72
72
  setMuted(next: boolean) {
73
73
  if (next === muted) return;
@@ -80,7 +80,121 @@ export function createAudioSystemAdapter(audio: GameAudio): AudioAdapter {
80
80
  }
81
81
  },
82
82
  isMuted: () => muted,
83
+
84
+ /**
85
+ * The engine's own bus hierarchy.
86
+ *
87
+ * This lived in `audio-introspection.ts` — the Tone-backed module — which
88
+ * meant the editor could only ever show an audio graph for a game that had
89
+ * loaded Tone. Nothing here needs Tone: it is the destination, the master
90
+ * gain, the three engine buses and the THREE listener. The Tone half (nodes
91
+ * a game routed through `connectToneBusToMasterGain`) is APPENDED by that
92
+ * module when it attaches, which is the honest split — Tone routes are
93
+ * Tone's to report.
94
+ */
95
+ graphSnapshot(): AudioGraphNode[] {
96
+ const contextState = nativeAudioContext(audio)?.state;
97
+ const destination: AudioGraphNode = {
98
+ id: 'destination',
99
+ type: 'AudioDestinationNode',
100
+ label: 'Output',
101
+ outputs: [],
102
+ };
103
+ if (contextState !== undefined) destination.state = contextState;
104
+ const nodes: AudioGraphNode[] = [
105
+ destination,
106
+ {
107
+ id: 'master',
108
+ type: audioNodeTypeName(audio.masterGain),
109
+ label: 'Master',
110
+ outputs: ['destination'],
111
+ },
112
+ {
113
+ id: 'bus-music',
114
+ type: audioNodeTypeName(audio.buses.music),
115
+ label: 'Music bus',
116
+ outputs: ['master'],
117
+ },
118
+ {
119
+ id: 'bus-sfx',
120
+ type: audioNodeTypeName(audio.buses.sfx),
121
+ label: 'SFX bus',
122
+ outputs: ['master'],
123
+ },
124
+ {
125
+ id: 'bus-voice',
126
+ type: audioNodeTypeName(audio.buses.voice),
127
+ label: 'Voice bus',
128
+ outputs: ['master'],
129
+ },
130
+ ];
131
+ if (audio.listener) {
132
+ nodes.push({
133
+ id: 'listener',
134
+ type: audioNodeTypeName(audio.listener),
135
+ label: '3D listener (THREE)',
136
+ outputs: ['master'],
137
+ });
138
+ }
139
+ return nodes;
140
+ },
141
+
142
+ /**
143
+ * Per-bus linear-RMS levels, via native `AnalyserNode` taps.
144
+ *
145
+ * Zero Tone — analysers work identically whether or not Tone is bridged,
146
+ * and disposal is a plain disconnect. It was in the Tone module purely by
147
+ * where it was written, so a game that never touches Tone had no meters.
148
+ */
149
+ acquireMeters(): AudioMeterHandle | null {
150
+ const meterContext = nativeAudioContext(audio);
151
+ if (!meterContext || typeof meterContext.createAnalyser !== 'function') {
152
+ return null; // headless world — metering is impossible, and says so
153
+ }
154
+ const taps = (
155
+ [
156
+ ['master', 'Master', audio.masterGain],
157
+ ['bus-music', 'Music', audio.buses.music],
158
+ ['bus-sfx', 'SFX', audio.buses.sfx],
159
+ ['bus-voice', 'Voice', audio.buses.voice],
160
+ ] as const
161
+ ).map(([id, label, gain]) => {
162
+ const analyser = meterContext.createAnalyser();
163
+ analyser.fftSize = 1024;
164
+ gain.connect(analyser); // a TAP (analyser is a sink), not an insert
165
+ return { id, label, gain, analyser, buffer: new Float32Array(analyser.fftSize) };
166
+ });
167
+ let disposed = false;
168
+ const handle: AudioMeterHandle = {
169
+ read: (): AudioMeterFrame[] =>
170
+ taps.map(({ id, label, analyser, buffer }) => {
171
+ analyser.getFloatTimeDomainData(buffer);
172
+ let sum = 0;
173
+ for (let i = 0; i < buffer.length; i++) {
174
+ const sample = buffer[i] as number;
175
+ sum += sample * sample;
176
+ }
177
+ return { id, label, level: Math.sqrt(sum / buffer.length) };
178
+ }),
179
+ dispose: (): void => {
180
+ if (disposed) return;
181
+ disposed = true;
182
+ for (const tap of taps) {
183
+ try {
184
+ tap.gain.disconnect(tap.analyser);
185
+ } catch {
186
+ // already torn down with the context — nothing to release
187
+ }
188
+ }
189
+ liveMeterHandles.delete(handle);
190
+ },
191
+ };
192
+ liveMeterHandles.add(handle);
193
+ return handle;
194
+ },
83
195
  };
196
+ meterHandlesByAdapter.set(adapter, liveMeterHandles);
197
+ return adapter;
84
198
  }
85
199
 
86
200
  /** First-party `NavigationAdapter` over the engine `NavMeshManager` — the
@@ -1,22 +1,87 @@
1
1
  /**
2
- * HostContextthe NEUTRAL primitives the host hands a game when mounting it.
2
+ * Host contexts what the host hands a root when it mounts it, PER SURFACE.
3
3
  *
4
4
  * This generalizes the old `GameContext`, which baked in first-party system
5
5
  * choices (a Rapier world, the postprocessing composer, the InputManager, …).
6
- * `HostContext` provides only what ANY game needs the shared three instance, a
7
- * surface, a renderer, a loop, and assets — and lets a game *ask* for
8
- * a first-party subsystem via `requestSystem`, which the host may or may not
9
- * supply. The host never assumes Rapier/Colyseus; those live behind
10
- * `SystemAdapters` owned by the first-party implementer.
11
- *
12
- * Migration note: `VgaiSceneGameAdapter.mount` builds today's `GameContext`
13
- * (Rapier world, composer, …) internally from a `HostContext`. New code targets
14
- * `HostContext`; `GameContext` is now a first-party implementation detail.
6
+ * The host never assumes Rapier/Colyseus; those live behind `SystemAdapters`
7
+ * owned by the first-party implementer.
8
+ *
9
+ * ## Why this file is a family, not one interface (P-8)
10
+ *
11
+ * There used to be a single "neutral" `HostContext` interface handed to EVERY
12
+ * root, with a NON-OPTIONAL `three: typeof THREE` and `renderer:
13
+ * THREE.WebGLRenderer` on it. That made the neutral layer three-LOCKED rather
14
+ * than merely three-named: a Pixi or React root would have been handed a WebGL
15
+ * renderer it cannot use. In practice nobody did hand it one — the production
16
+ * Pixi and React hosts have always built their own canvas/dom-shaped contexts
17
+ * instead — so the "one context for every surface"
18
+ * claim was already false in the tree; it just wasn't expressed in the types,
19
+ * which is why `RootAdapter<K>` could only parameterize its RETURN type and
20
+ * the Pixi/React adapters lived outside `RootAdapter` entirely.
21
+ *
22
+ * The split (ratified, docs/ARCHITECTURE-CORE.md "Seam"):
23
+ *
24
+ * {@link HostContextBase} what EVERY surface genuinely gets
25
+ * ├─ {@link ThreeHostContext} + `three` · `renderer` · `surface` · `assets`
26
+ * ├─ {@link CanvasHostContext} + `canvas`/`width`/`height` (+ dpr/compositing hints)
27
+ * └─ {@link DomHostContext} + `container`
28
+ *
29
+ * and {@link HostContextFor} maps an `AdapterSurface` to its context, so
30
+ * `RootAdapter<K>.mount`'s PARAMETER is K-typed too — the surface tag now
31
+ * picks the context type, which is the whole point of the tag.
32
+ *
33
+ * ### Where the line was drawn, and why
34
+ *
35
+ * The base holds exactly the members all three surfaces ALREADY carry today —
36
+ * nothing was invented to make the base look richer:
37
+ *
38
+ * - `game` — the root handle. Every surface has one (three: `ThreeHostContext.game`,
39
+ * canvas: `CanvasHostContext.game`, dom: `DomHostContext.game`), and it is the one
40
+ * genuinely surface-independent thing a host can hand any root.
41
+ * - `headless` — a property of the ENVIRONMENT (no GPU/DOM/audio), not of the
42
+ * render surface.
43
+ *
44
+ * Deliberately NOT in the base:
45
+ *
46
+ * - `renderer`/`three` — three's own objects; the defect this split exists to
47
+ * fix.
48
+ * - the render surface — its SHAPE is surface-specific and there is no honest
49
+ * common denominator: three nests `{canvas,width,height}` under `surface`,
50
+ * canvas passes those three flat (plus its own compositing hints), and a DOM
51
+ * root gets an `HTMLElement` container and no canvas at all. A shared
52
+ * `surface` field would have to be a union nobody can consume without
53
+ * re-branching on kind — i.e. the same three-shaped promise, one level down.
54
+ * - `assets` — `AssetCache`'s SIGNATURE reads neutral (`load<T>(url)`), but its
55
+ * one implementation is a GLTF/texture loader (`assets.ts` branches on
56
+ * `.glb`/`.gltf` and resolves THREE objects), and neither the canvas nor the
57
+ * dom host has ever been handed one: a canvas adapter builds its own
58
+ * cache inside `mount`. Promoting it to the base would be the anti-shim rule
59
+ * in miniature — a host promise nothing keeps. It stays on
60
+ * {@link ThreeHostContext} until a second surface genuinely receives one.
61
+ *
62
+ * These three names are the ONLY names for these shapes. The transitional
63
+ * aliases that carried the pre-split spellings were deleted; every implementer
64
+ * and call site names the surface it actually mounts on.
65
+ *
66
+ * It also used to carry `requestSystem<T extends keyof SystemRegistry>()` — an
67
+ * "ask the host for a first-party subsystem" hook — over an EMPTY
68
+ * `SystemRegistry`. That made `keyof SystemRegistry` = `never`, so no argument
69
+ * could legally be passed and the method was uncallable by construction. In
70
+ * practice it existed as 28 copies of `requestSystem: () => null` in tests and
71
+ * hosts, with no caller anywhere, and its only real effect would have been to
72
+ * re-open the door this interface exists to close: the host handing a game a
73
+ * subsystem it did not build. Deleted rather than kept as a promise.
74
+ *
75
+ * Migration note: `SetupThreeRootAdapter.mount` builds today's `GameContext`
76
+ * (Rapier world, composer, …) internally from a `ThreeHostContext`. New code
77
+ * targets these contexts; `GameContext` is now a first-party implementation
78
+ * detail.
15
79
  */
16
80
 
17
81
  import type * as THREE from 'three';
18
82
  import type { AssetCache } from '../assets';
19
- import type { GameInternal } from '../runtime/game';
83
+ import type { Game, GameInternal } from '../runtime/game';
84
+ import type { AdapterSurface } from './adapter-surface';
20
85
 
21
86
  /** The canvas/container the game renders into, plus its size. */
22
87
  export interface HostSurface {
@@ -25,51 +90,117 @@ export interface HostSurface {
25
90
  readonly height: number;
26
91
  }
27
92
 
28
- /** Host-owned scheduling. A game may register extra per-frame callbacks; the
29
- * host owns when they run. (First-party ticks its own system runner inside
30
- * `MountedGame.update`, so it does not need this it exists for games that
31
- * want to hook the host loop directly.) */
32
- export interface LoopHandle {
33
- /** Register a per-frame callback; returns an unregister fn. */
34
- onUpdate(fn: (dt: number) => void): () => void;
93
+ /**
94
+ * The surface-INDEPENDENT half of every host context see this file's header
95
+ * for why only these two members earned a place here. Generic host code (a
96
+ * harness, a tier report, anything that must not care what it is mounting) can
97
+ * type against this; a real adapter always receives one of the per-surface
98
+ * contexts below.
99
+ */
100
+ export interface HostContextBase {
101
+ /**
102
+ * The Game root (T7.1 slice 1 — D6). The host
103
+ * constructs the Game shell BEFORE mounting an adapter and hands it down
104
+ * here so an adapter can expose `ctx.game`/`ctx.roots` to the game it
105
+ * mounts. Absent in headless harnesses and foreign hosts that predate the
106
+ * Game root — everything must keep working when this is undefined (the
107
+ * zero-break guarantee for this slice). Typed as the PUBLIC `Game` shell
108
+ * here because that is the weakest promise any surface makes (a react
109
+ * root's host has always carried a plain `Game`); the three and canvas
110
+ * contexts narrow it to `GameInternal`, which is what their hosts really
111
+ * pass.
112
+ */
113
+ readonly game?: Game | undefined;
114
+ /**
115
+ * No GPU/DOM/audio available (Node conformance tests). A first-party adapter
116
+ * skips postprocessing/render/audio/input-map loading but still builds the
117
+ * scene + Rapier + components, so its scene/authoring/physics can be exercised
118
+ * headlessly. Browser hosts leave this false/undefined → full behavior.
119
+ */
120
+ readonly headless?: boolean;
35
121
  }
36
122
 
37
123
  /**
38
- * First-party subsystems a game MAY request from the host. The host returns
39
- * `null` when it does not provide the kind, so a game must tolerate absence —
40
- * the host never forces a subsystem on a game. Extended as new first-party
41
- * systems become host-provisioned.
124
+ * What a `three`-surface root is handed. This is the ONLY context carrying
125
+ * three's own objects the split's entire point (see the header): a Pixi or
126
+ * React root is never handed a `WebGLRenderer` it cannot use.
42
127
  */
43
- // biome-ignore lint/suspicious/noEmptyInterface: extended by first-party host wiring as systems become provisionable
44
- export interface SystemRegistry {}
45
-
46
- export interface HostContext {
128
+ export interface ThreeHostContext extends HostContextBase {
47
129
  /** The ONE shared three instance — identity matters for capture (see ingest). */
48
130
  readonly three: typeof THREE;
49
131
  /** Canvas/container + size; a self-driven game may take the surface over. */
50
132
  readonly surface: HostSurface;
51
133
  /** Host renderer — host-driven games render through it. */
52
134
  readonly renderer: THREE.WebGLRenderer;
53
- /** Host-owned loop scheduling. */
54
- readonly loop: LoopHandle;
55
- /** Shared GLTF/texture cache. */
135
+ /** Shared GLTF/texture cache — three-flavored today; see the header. */
56
136
  readonly assets: AssetCache;
57
- /**
58
- * No GPU/DOM/audio available (Node conformance tests). A first-party adapter
59
- * skips postprocessing/render/audio/input-map loading but still builds the
60
- * scene + Rapier + components, so its scene/authoring/physics can be exercised
61
- * headlessly. Browser hosts leave this false/undefined → full behavior.
62
- */
63
- readonly headless?: boolean;
64
- /** Ask the host for a first-party subsystem; `null` if not provided. */
65
- requestSystem<T extends keyof SystemRegistry>(kind: T): SystemRegistry[T] | null;
66
- /**
67
- * The Game root (T7.1 slice 1 — GAME-ROOT-DESIGN.md D6). The host
68
- * constructs the Game shell BEFORE mounting a `GameAdapter` and hands it
69
- * down here so an adapter can expose `ctx.game`/`ctx.roots` to the game it
70
- * mounts. Absent in headless harnesses and foreign hosts that predate the
71
- * Game root — everything must keep working when this is undefined (the
72
- * zero-break guarantee for this slice).
73
- */
74
137
  readonly game?: GameInternal | undefined;
75
138
  }
139
+
140
+ /**
141
+ * What a `canvas`-surface root is handed: a raw canvas + its
142
+ * logical size, plus the
143
+ * compositing hints the multi-root host computes once for every stacked
144
+ * surface. No `three`, no `renderer`, no `assets` — a Pixi root builds its own
145
+ * `PIXI.Application` and its own asset cache from this.
146
+ */
147
+ export interface CanvasHostContext extends HostContextBase {
148
+ readonly canvas: HTMLCanvasElement;
149
+ readonly width: number;
150
+ readonly height: number;
151
+ /** Shared Game root when mounted by the universal multi-root host. */
152
+ readonly game?: GameInternal | undefined;
153
+ /** DPR override (T6.1 slice 1, D5 §3 — "one DPR
154
+ * everywhere"): the roots-path host computes ONE dpr for every stacked
155
+ * surface and passes it here; omitted -> the canvas adapter's own
156
+ * default (`window.devicePixelRatio`, uncapped). */
157
+ readonly dpr?: number;
158
+ /** This root is stacked ABOVE another root (D5 §1): clear with alpha 0
159
+ * instead of an opaque background so the layer below shows through. */
160
+ readonly transparent?: boolean;
161
+ /** Capture tier cost (D5 §4) — the renderer's `preserveDrawingBuffer`. */
162
+ readonly preserveDrawingBuffer?: boolean;
163
+ }
164
+
165
+ /**
166
+ * What a `dom`-surface root is handed (the shape `runtime/create-runtime.ts`
167
+ * used to spell `ReactRootHost`).
168
+ *
169
+ * `container` is the absolutely-positioned, z-ordered DOM-root layer `<div>`
170
+ * the host already created and stacked (same box/z-order rules as a canvas per
171
+ * D5 §1) — the adapter's `mount` renders its react tree INTO
172
+ * this exact element via `createRoot`; it must never create its own root
173
+ * element (the DOM analog of three's "the root's runtime renders into the
174
+ * surface it is handed" rule).
175
+ *
176
+ * `game` (inherited, and deliberately still optional): a
177
+ * `default-react` sibling mounted BESIDE an ingest root (`packages/editor/src/
178
+ * ingest-siblings.ts`) has no native `Game` to hand it — there is no
179
+ * first-party `GameContext`/loop for the sibling to join, only the ingested
180
+ * root's own foreign runtime — so its host carries no `game` at all rather than
181
+ * fabricating an empty one (anti-shim rule); the sibling mounts its entry
182
+ * component bare, with no `<WorldProvider>` wrap. `mountOneReactRoot`
183
+ * (`runtime/create-runtime.ts`, the NATIVE multi-root runtime path) still
184
+ * ALWAYS supplies a real `game` — this optionality is reached only by the
185
+ * composite sibling's own hand-built host, never by weakening the native path's
186
+ * guarantee.
187
+ */
188
+ export interface DomHostContext extends HostContextBase {
189
+ readonly container: HTMLElement;
190
+ }
191
+
192
+ /**
193
+ * Map an {@link AdapterSurface} to the host context that surface's roots are
194
+ * handed — the mirror image of `MountedRootFor<K>` (`root-adapter.ts`), and
195
+ * what makes `RootAdapter<K>.mount`'s PARAMETER K-typed. A hypothetical 4th
196
+ * surface resolves to `never` here, so it cannot be mounted until it declares
197
+ * its own context (the same fail-to-compile discipline `assertNever` gives the
198
+ * dispatch sites).
199
+ */
200
+ export type HostContextFor<K extends AdapterSurface> = K extends 'three'
201
+ ? ThreeHostContext
202
+ : K extends 'canvas'
203
+ ? CanvasHostContext
204
+ : K extends 'dom'
205
+ ? DomHostContext
206
+ : never;
@@ -1,38 +1,49 @@
1
1
  /**
2
2
  * Adapter interfaces — the seams the engine host and the editor DEPEND ON.
3
3
  *
4
- * host → GameAdapter ← { VgaiSceneGameAdapter, IngestGameAdapter, … }
5
- * editor → AuthoringAdapter ← { VgaiSceneAuthoringAdapter, IngestAuthoringAdapter, … }
6
- * game → SystemAdapters (physics/networking/input/assets)
4
+ * host → RootAdapter ← { SetupThreeRootAdapter, IngestRootAdapter, … }
5
+ * editor → AuthoringAdapter ← { LiveThreeAuthoringAdapter, ReactRootAuthoringAdapter, … }
6
+ * game → SystemAdapters (physics/networking/navigation/audio/debug)
7
7
  *
8
- * The first-party `.vscn`/`GameComponent`/Rapier/Colyseus stack is ONE
9
- * implementer of these interfaces, not the engine's vocabulary. See
10
- * docs/ADAPTER-ARCHITECTURE.md.
8
+ * The first-party Rapier/Colyseus stack is ONE implementer of these
9
+ * interfaces, not the engine's vocabulary..
10
+ *
11
+ * **This module is TYPE-ONLY, and must stay that way (P-6).** It used to
12
+ * value-export its own implementers — `SetupThreeRootAdapter`/`fromSetup`,
13
+ * `createRapierPhysicsAdapter`, `createColyseusNetworkingAdapter`,
14
+ * `createNavigationAdapter` — so `import type { AuthoringAdapter } from
15
+ * '@engine/adapter'` was the only thing keeping the three/Rapier/Colyseus/
16
+ * `.vscn` stack out of a Pixi or React consumer's module graph. A seam that
17
+ * ships its implementers is not a seam. Import an implementer from its OWN
18
+ * path instead:
19
+ *
20
+ * `@engine/adapter/setup-three-root-adapter` SetupThreeRootAdapter, fromSetup
21
+ * `@engine/adapter/rapier-physics-adapter` createRapierPhysicsAdapter
22
+ * `@engine/adapter/colyseus-networking-adapter` createColyseusNetworkingAdapter
23
+ * `@engine/adapter/first-party-systems` createNavigationAdapter
11
24
  */
12
25
 
13
26
  export type { AdapterSurface } from './adapter-surface';
14
27
  export type {
28
+ AssetDropContext,
15
29
  AssetDropProvider,
30
+ AssetSubjectProvider,
16
31
  AuthoringAdapter,
32
+ AuthoringAssetSubject,
17
33
  AuthoringCapabilities,
18
34
  AuthoringProvenance,
19
35
  BoxEditProvider,
20
36
  BoxEditReferencePoint,
21
37
  ColorSampleProvider,
22
- ComponentsProvider,
38
+ CreationSiteProvider,
23
39
  DOMRectLike,
24
40
  EditorNode,
25
41
  EditorNodeRole,
26
- EntityAssetProvider,
27
- EntityAssetSaveDescriptor,
28
42
  FileMapProvider,
29
43
  HierarchyProvider,
30
44
  InspectorProvider,
31
- LayoutProvider,
45
+ NodeCreationSite,
32
46
  PersistenceProvider,
33
- PhysicsShape2DHandle,
34
- PhysicsShape2DProjection,
35
- PhysicsShape2DProvider,
36
47
  PickProvider,
37
48
  PropertyDescriptor,
38
49
  RectProvider,
@@ -40,46 +51,37 @@ export type {
40
51
  RootGroupsProvider,
41
52
  SelectionProvider,
42
53
  SelectionResolution,
43
- SpriteAtlas2D,
44
- SpriteAtlasFrame2D,
45
- SpriteFrames2DDescriptor,
46
- SpriteFrames2DProvider,
47
54
  StoriesProvider,
48
55
  StoryRef,
49
56
  StructureProvider,
50
57
  TextProvider,
51
- TilemapCell,
52
- TilemapEditGrid,
53
- TilemapEditProvider,
54
58
  TransformChannel,
55
59
  TransformEditability,
60
+ TransformObservation,
56
61
  TransformProvider,
57
62
  } from './authoring';
58
- export {
59
- type ColyseusNetworkingConfig,
60
- createColyseusNetworkingAdapter,
61
- } from './colyseus-networking-adapter';
62
- export {
63
- createInputManagerAdapter,
64
- createNavigationAdapter,
65
- createVgaiAssetAdapter,
66
- } from './first-party-systems';
63
+ export type { ColyseusNetworkingConfig } from './colyseus-networking-adapter';
64
+ export type {
65
+ CanvasHostContext,
66
+ DomHostContext,
67
+ HostContextBase,
68
+ HostContextFor,
69
+ HostSurface,
70
+ ThreeHostContext,
71
+ } from './host-context';
67
72
  export type {
68
- GameAdapter,
69
- MountedGame,
70
- MountedPixiWorld,
71
- MountedReactWorld,
72
- MountedThreeWorld,
73
- MountedWorld,
74
- MountedWorldBase,
75
- MountedWorldFor,
76
- WorldStateObserver,
77
- } from './game-adapter';
78
- export type { HostContext, HostSurface, LoopHandle, SystemRegistry } from './host-context';
79
- export { createRapierPhysicsAdapter } from './rapier-physics-adapter';
73
+ MountedPixiRoot,
74
+ MountedReactRoot,
75
+ MountedRoot,
76
+ MountedRootBase,
77
+ MountedThreeRoot,
78
+ RootAdapter,
79
+ RootStateObserver,
80
+ SurfaceAdapter,
81
+ SurfaceAdapterFor,
82
+ } from './root-adapter';
83
+ export type { MountedSetupThreeRoot, SetupThreeRootConfig } from './setup-three-root-adapter';
80
84
  export type {
81
- ActionValueSnapshot,
82
- AssetAdapter,
83
85
  AudioAdapter,
84
86
  AudioDebugEvent,
85
87
  AudioGraphNode,
@@ -87,15 +89,15 @@ export type {
87
89
  AudioMeterHandle,
88
90
  AudioTransportState,
89
91
  ConnectionState,
90
- InputAdapter,
91
- NavBakeParams,
92
92
  NavCrowdAgentState,
93
93
  NavigationAdapter,
94
94
  NavPoint,
95
95
  NetConditioning,
96
96
  NetMessageEvent,
97
97
  NetPeer,
98
+ NetPlayerIdentity,
98
99
  NetRates,
100
+ NetServerConfig,
99
101
  NetworkingAdapter,
100
102
  PhysicsAdapter,
101
103
  RenderDebugAdapter,
@@ -105,9 +107,3 @@ export type {
105
107
  Unsubscribe,
106
108
  } from './system-adapter';
107
109
  export type { Transform, TransformOwner } from './transform';
108
- export {
109
- fromSetup,
110
- type VgaiMountedGame,
111
- type VgaiSceneConfig,
112
- VgaiSceneGameAdapter,
113
- } from './vgai-scene-game-adapter';
@@ -10,12 +10,12 @@
10
10
  *
11
11
  * Relationship to the native engine: first-party content implements the SAME
12
12
  * conceptual surface (mount root, lifecycle, loop gating) through
13
- * `GameAdapter`/`MountedGame` — the native engine is the premade 100%
13
+ * `RootAdapter`/`MountedThreeRoot` — the native engine is the premade 100%
14
14
  * implementation of this contract. An ingested game climbs the same ladder
15
15
  * endpoint by endpoint: capture infers what it can (the scene), the game
16
16
  * declares what inference can't reach (its DOM root, its session lifecycle).
17
17
  *
18
- * Origin: docs/FTUE-EXTERNAL-R3F-GAME.md F16 (root), F17+F21 (lifecycle).
18
+ * Origin (root), F17+F21 (lifecycle).
19
19
  */
20
20
 
21
21
  /**