@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
@@ -5,21 +5,20 @@ import type { z } from 'zod';
5
5
  import type { SystemAdapters } from '../adapter/system-adapter';
6
6
  import type { AssetCache } from '../assets';
7
7
  import type { SeededRandom } from '../core/seeded-random';
8
+ import type { SimClock } from '../core/sim-clock';
8
9
  import type { createSystemRunner } from '../core/system-runner';
9
10
  import type { createDebugDraw } from '../dev/debug-draw';
10
- import type { ComponentManager } from '../ecs/component-manager';
11
+ import type { SceneIndex } from '../ecs/scene-index';
11
12
  import type { InputManager } from '../input/input-manager';
12
13
  import type { CollisionSystem } from '../physics/collision-system';
13
14
  import type { PhysicsRegistry } from '../physics/physics-registry';
14
- import type { SceneFile } from '../scene/scene-types';
15
15
  import type { AudioContext as GameAudio } from '../setup/setup-audio';
16
16
  import type { ParticlesContext } from '../setup/setup-particles';
17
- import type { Game, WorldInstance } from './game';
17
+ import type { Game, RootInstance } from './game';
18
18
 
19
19
  /**
20
20
  * The minimal structural shape `ctx.debug.attachRoom` needs from a joined
21
- * Colyseus room (Task 2.2, `docs/SYNTHETIC-PLAYER-SPEC.md` §3.1's "Multiplayer
22
- * locus" note): just enough to send the reserved `__vgai:debugCommand`
21
+ * Colyseus room: just enough to send the reserved `__vgai:debugCommand`
23
22
  * message and listen for its `__vgai:debugCommandResult` reply. Structural,
24
23
  * not a Colyseus type import — the same seam-boundary style every other
25
24
  * `DebugCtxSurface` member uses (no wrapper around Colyseus itself, D2).
@@ -36,11 +35,10 @@ export interface DebugRoomHandle {
36
35
  /**
37
36
  * Infers a `registerCommand`/`registerReactCommand`/`useDebugCommand`
38
37
  * handler's parameter tuple from its declared Zod `args` tuple (dry-run
39
- * finding, `docs/E2E-TESTING-BUILD-PLAN.md` Wave 6 ledger
40
- * "`registerCommand` fn typing forces `unknown[]` casts"): a real
41
- * `z.ZodTuple` infers its element types (`z.tuple([z.number(), z.string()])`
42
- * → `(n: number, s: string) => ...`), while omitting `args` (the generic's
43
- * `undefined` default) resolves to the original permissive
38
+ * finding, ledger — "`registerCommand` fn typing forces `unknown[]` casts"):
39
+ * a real `z.ZodTuple` infers its element types (`z.tuple([z.number(),
40
+ * z.string()])` `(n: number, s: string) => ...`), while omitting `args`
41
+ * (the generic's `undefined` default) resolves to the original permissive
44
42
  * `(...args: unknown[]) => ...` shape every command had before this generic
45
43
  * existed. That permissive fallback is deliberate, not just a placeholder —
46
44
  * it keeps every existing no-schema registration (including ones whose
@@ -53,13 +51,11 @@ export type DebugCommandArgs<T extends z.ZodTuple | undefined = undefined> = T e
53
51
  : unknown[];
54
52
 
55
53
  /**
56
- * The authoring half of the debug/synthetic-player seam
57
- * (`docs/SYNTHETIC-PLAYER-SPEC.md` §3.1) the read/actuate half is
58
- * `SystemAdapters.DebugAdapter` (`adapter/system-adapter.ts`). Callable from
59
- * any GameComponent's `init` (one file per new provider/command), or from a
60
- * react world's `useDebugProvider`/`useDebugCommand`/`useDebugEmit` hooks. Every registration
61
- * feeds the ONE game-scoped registry (`runtime/debug-registry.ts`) — never a
62
- * per-world accumulator.
54
+ * The authoring half of the debug/synthetic-player seam — the read/actuate half is
55
+ * `SystemAdapters.DebugAdapter` (`adapter/system-adapter.ts`). Callable from any
56
+ * `init` (one file per new provider/command), or from a react world's
57
+ * `useDebugProvider`/`useDebugCommand`/`useDebugEmit` hooks. Every registration feeds the ONE
58
+ * game-scoped registry (`runtime/debug-registry.ts`) never a per-world accumulator.
63
59
  */
64
60
  export interface DebugCtxSurface {
65
61
  /** Register (or replace) a named, JSON-serializable state read. Duplicate
@@ -99,7 +95,7 @@ export interface DebugCtxSurface {
99
95
 
100
96
  /**
101
97
  * All engine subsystems, passed to every game `setup(ctx)` and to each
102
- * GameComponent lifecycle method (`init`/`update`/`dispose`/trigger callbacks).
98
+ * game lifecycle callback.
103
99
  *
104
100
  * The surface is intentionally split in two:
105
101
  *
@@ -109,6 +105,17 @@ export interface DebugCtxSurface {
109
105
  * into the phase-ordered game loop. Reach for these when you need behavior the
110
106
  * raw library doesn't provide (physics↔Object3D mapping, system phases, etc.).
111
107
  */
108
+ /** Host-supplied identity for one private play run or coordinated team test.
109
+ * Games may pass `roomKey` as a Colyseus filter/join option; the engine never
110
+ * joins a room or couples this authoring identity to a networking library. */
111
+ export interface PlaytestContext {
112
+ readonly mode: 'private' | 'team';
113
+ readonly id: string;
114
+ readonly roomKey: string;
115
+ readonly revision: number | null;
116
+ readonly participantId: string | null;
117
+ }
118
+
112
119
  export interface GameContext {
113
120
  // ─── Raw libraries (the real library objects — call them directly) ───
114
121
 
@@ -116,7 +123,7 @@ export interface GameContext {
116
123
  * groups) here. This is THE scene — there is no separate engine scene. */
117
124
  scene: THREE.Scene;
118
125
  /** The single render camera. Move/aim it directly, or (preferred) drive it
119
- * from a camera GameComponent — see the camera-precedence note below. */
126
+ * from game code — see the camera-precedence note below. */
120
127
  camera: THREE.PerspectiveCamera;
121
128
  /** The Rapier physics `World`. Step/query it, create bodies & colliders via
122
129
  * `ctx.rapier`. One shared world for the whole scene. */
@@ -136,7 +143,7 @@ export interface GameContext {
136
143
  input: InputManager;
137
144
  /** Rapier collision-event dispatcher. Register handlers with
138
145
  * `collisions.onCollision(...)`; it is drained each `postPhysics`. Prefer a
139
- * GameComponent's `onTriggerEnter/Exit` for per-entity sensor logic. */
146
+ * own handler for per-entity sensor logic. */
140
147
  collisions: CollisionSystem;
141
148
  /** `Object3D ↔ Rapier body/collider` registry (+ collider→Object3D reverse
142
149
  * index). Use to find an entity's body/collider, or the entity behind a
@@ -161,11 +168,36 @@ export interface GameContext {
161
168
  assets: AssetCache;
162
169
  /** The phase-ordered system runner. `systems.add(phase, fn)` registers a
163
170
  * per-frame callback. Reserve this for non-gameplay infra (audio mixers,
164
- * network send); per-entity GAMEPLAY belongs in a GameComponent. */
171
+ * network send); per-entity GAMEPLAY belongs in a `useFrame` hook. */
165
172
  systems: ReturnType<typeof createSystemRunner>;
166
- /** Manager for all live GameComponent instances (attach/detach/HMR). The
167
- * scene loader drives this; gameplay code rarely touches it directly. */
168
- components: ComponentManager;
173
+ /**
174
+ * P3 the SIM-TIME scheduler (`core/sim-clock.ts`). `await ctx.clock.delay(n)`
175
+ * waits `n` seconds of the fixed loop's own time, so it pauses when the game
176
+ * pauses, steps when `Game.play.step()` steps, and reproduces exactly in an
177
+ * offline export; `ctx.clock.after(n, fn)` is the frame-exact synchronous
178
+ * form, and `ctx.clock.disposeAfter(obj, n)` is debris removal. Cancellation
179
+ * is `AbortSignal`.
180
+ *
181
+ * REQUIRED, unlike `debug`/`random` above: every world has a sim clock. When
182
+ * a Game hosts the mount it is the ONE game-scoped clock the fixed loop
183
+ * flushes; a bare harness mount with no Game shell gets a private,
184
+ * mount-local clock that nothing advances (so `now()` stays 0) rather than an
185
+ * absent field.
186
+ *
187
+ * NOT `AnimationClock` (`animation/animation-clock.ts`) — that one is the
188
+ * seekable cinematic playhead and runs backwards. This one is monotonic and
189
+ * never rewinds, which is exactly why a Promise can be bound to it. See
190
+ * `core/sim-clock.ts`'s header.
191
+ */
192
+ clock: SimClock;
193
+ /** The live scene index for this root (P2 `observe`): tag/entity-id lookup
194
+ * that never traverses, plus the change signals — `entityadded`,
195
+ * `entityremoved`, `tagadded`, `tagremoved`, `attributechanged` — on a plain
196
+ * `EventTarget`, unsubscribed with an `AbortSignal`. Maintained from three's
197
+ * own `childadded`/`childremoved` parent events, so it costs nothing on a
198
+ * frame where nothing changes. See `ecs/scene-index.ts`; the one-shot
199
+ * `ecs/scene-query.ts` walks remain for a single ad-hoc question. */
200
+ sceneIndex: SceneIndex;
169
201
  /** Register a game-owned `SystemAdapters` capability (networking,
170
202
  * navigation, …) on the mounted game's adapter surface, so the editor's
171
203
  * introspection panels (via `getActiveSystems()`) can see it. The engine
@@ -186,33 +218,32 @@ export interface GameContext {
186
218
  * mounted surface. */
187
219
  registerSystemAdapter?<K extends keyof SystemAdapters>(kind: K, adapter: SystemAdapters[K]): void;
188
220
 
189
- /** The debug/synthetic-player authoring surface (`docs/SYNTHETIC-PLAYER-SPEC.md`
190
- * §3.1) — see {@link DebugCtxSurface}. Optional, same absence precedent as
191
- * `registerSystemAdapter`: present on every first-party mount, absent only
192
- * in bare test harnesses that build a hand-rolled `ctx`. */
221
+ /** The debug/synthetic-player authoring surface — see {@link DebugCtxSurface}.
222
+ * Optional, same absence precedent as `registerSystemAdapter`: present on every
223
+ * first-party mount, absent only in bare test harnesses that build a
224
+ * hand-rolled `ctx`. */
193
225
  debug?: DebugCtxSurface;
194
226
 
195
227
  /**
196
- * D15 (T-D15.1, `docs/D15-DETERMINISM-DESIGN.md` §2.a) — the game-scoped,
197
- * named-stream seeded PRNG. `ctx.random()` draws from the `'gameplay'`
198
- * stream; `ctx.random.stream('vfx')` (or any other name) draws from an
199
- * INDEPENDENT stream, so a cosmetic/VFX draw can never shift the gameplay
200
- * draw sequence. Use this instead of raw `Math.random()` for anything a
201
- * project wants to keep reproducible; `ctx.random` itself always exists
202
- * and is always internally consistent (same seed same sequence) whether
203
- * or not the project's manifest declares `determinism.seededRandom` — that
204
- * flag only gates the BOOT-TIME seeding from
205
- * `manifest.determinism.defaultSeed`/`?vgai-seed=`, the burn-down lint
206
- * scan, and the dev-mode `Math.random` phase trap (three separate
207
- * enforcers, `test/gameplay-rng-ban.test.ts` + `runtime/gameplay-rng-trap.ts`),
208
- * never `ctx.random`'s mere presence. Same absence precedent as `debug`
209
- * above: present on every first-party mount, optional only for bare test
210
- * harnesses that build a hand-rolled `ctx`. `reseed(seed)` (future draws
211
- * only) is `play.seed.set`'s eventual target (T-D15.6, not yet wired).
228
+ * D15 (T-D15.1) — the game-scoped, named-stream seeded PRNG. `ctx.random()`
229
+ * draws from the `'gameplay'` stream; `ctx.random.stream('vfx')` (or any other
230
+ * name) draws from an INDEPENDENT stream, so a cosmetic/VFX draw can never
231
+ * shift the gameplay draw sequence. Use this instead of raw `Math.random()` for
232
+ * anything a project wants to keep reproducible; `ctx.random` itself always
233
+ * exists and is always internally consistent (same seed ⇒ same sequence)
234
+ * whether or not the project's manifest declares `determinism.seededRandom`
235
+ * that flag only gates the BOOT-TIME seeding from
236
+ * `manifest.determinism.defaultSeed`/`?vgai-seed=`, the burn-down lint scan,
237
+ * and the dev-mode `Math.random` phase trap (three separate enforcers,
238
+ * `test/gameplay-rng-ban.test.ts` + `runtime/gameplay-rng-trap.ts`), never
239
+ * `ctx.random`'s mere presence. Same absence precedent as `debug` above:
240
+ * present on every first-party mount, optional only for bare test harnesses
241
+ * that build a hand-rolled `ctx`. `reseed(seed)` (future draws only) is
242
+ * `play.seed.set`'s eventual target (T-D15.6, not yet wired).
212
243
  */
213
244
  random?: SeededRandom;
214
245
 
215
- // ─── Game root (T7.1 slice 1 — GAME-ROOT-DESIGN.md D6) ───
246
+ // ─── Game root (T7.1 slice 1 — D6) ───
216
247
 
217
248
  /** The Game root — the loop, the world registry, and game-scoped handles.
218
249
  * Absent only in bare test harnesses that construct a partial context (the
@@ -222,7 +253,10 @@ export interface GameContext {
222
253
  game?: Game | undefined;
223
254
  /** Alias for `game.roots` — the live, declaration-ordered world registry.
224
255
  * Same optionality/absence rule as `game` above. */
225
- roots?: ReadonlyArray<WorldInstance> | undefined;
256
+ roots?: ReadonlyArray<RootInstance> | undefined;
257
+ /** The host's current private play or Team Test identity. Optional for
258
+ * standalone/custom hosts; networking remains game-owned and direct. */
259
+ playtest?: PlaytestContext | null | undefined;
226
260
  }
227
261
 
228
262
  /** Return value from a game setup function. */
@@ -232,10 +266,6 @@ export interface GameCleanup {
232
266
 
233
267
  /** Context passed to setup() when launched from the editor's play mode. */
234
268
  export interface EditorPreview {
235
- /** Path of the .vscn.json file the user has open (null if unsaved). */
236
- scenePath: string | null;
237
- /** The editor's in-memory scene document, including unsaved edits. */
238
- sceneData: SceneFile;
239
269
  /** Editor viewport camera transform — use this as the initial game camera viewpoint. */
240
270
  viewportCamera?: {
241
271
  position: [number, number, number];
@@ -243,12 +273,16 @@ export interface EditorPreview {
243
273
  };
244
274
  /**
245
275
  * Which bundled example the static/browser build's project picker wants to
246
- * run (docs/EXAMPLES-AS-PROJECTS-DESIGN.md §4 — Adjudication H). Only
247
- * meaningful to `packages/editor/src/browser-play-entry.ts`'s dispatch over
248
- * the generated `SETUP_BY_EXAMPLE_ID` table; every other `GameSetupFn`
249
- * consumer ignores it. Undefined the generic scene-loader fallback
250
- * (today's only reachable behavior no caller sets this yet, since the
251
- * picker itself is a later wave).
276
+ * run (Adjudication H).
277
+ *
278
+ * Its one former reader — `browser-play-entry.ts`'s whole-bundle `setup()`,
279
+ * which dispatched over the generated `SETUP_BY_EXAMPLE_ID` table is
280
+ * DELETED. Hosted dispatch is per-root (`ADAPTER_BY_EXAMPLE_ROOT` /
281
+ * `REACT_BY_EXAMPLE_ROOT`, resolved in `adapter-resolver.ts`) and never
282
+ * consults this field; `exampleDispatchFor` still sets it as the marker for
283
+ * "play the project's own shipped game" rather than a `?scene=` override
284
+ * (which sets `assetPrefix` instead — that one IS read, by
285
+ * `SetupThreeRootAdapter`). Every `GameSetupFn` consumer ignores it.
252
286
  */
253
287
  exampleId?: string | undefined;
254
288
  /**
@@ -265,9 +299,8 @@ export interface EditorPreview {
265
299
  /** A game setup function: receives engine context, returns cleanup.
266
300
  *
267
301
  * This is the MOUNT body of the FIRST-PARTY implementer. The host no longer
268
- * depends on it — the host depends on `GameAdapter` (`@engine/adapter`), and
269
- * `GameSetupFn` is wrapped into a `VgaiSceneGameAdapter` via `fromSetup`. See
270
- * docs/ADAPTER-ARCHITECTURE.md. (The former `GameAdapter` `{ setup, components }`
271
- * interface lived here; it is superseded by the real adapter interfaces in
272
- * `@engine/adapter`.) */
302
+ * depends on it — the host depends on `RootAdapter` (`@engine/adapter`), and
303
+ * `GameSetupFn` is wrapped into a `SetupThreeRootAdapter` via `fromSetup`.. (The
304
+ * former `RootAdapter` `{ setup, components }` interface lived here; it is
305
+ * superseded by the real adapter interfaces in `@engine/adapter`.) */
273
306
  export type GameSetupFn = (ctx: GameContext, editor?: EditorPreview) => Promise<GameCleanup>;
@@ -35,9 +35,10 @@ import {
35
35
  SSREffect,
36
36
  VelocityDepthNormalPass,
37
37
  } from '../../vendor/realism-effects/dist/index.js';
38
+ import type { RenderEnvironment } from '../asset-formats/render-env';
39
+ import { DEFAULTS } from '../defaults';
40
+ import { detectKtx2Support } from '../loader';
38
41
  import { type RenderScope, resolveRenderSettings } from '../render/render-settings';
39
- import { DEFAULTS } from '../scene/defaults';
40
- import type { SceneEnvironment } from '../scene/scene-types';
41
42
 
42
43
  export interface RendererContext {
43
44
  renderer: THREE.WebGLRenderer;
@@ -69,14 +70,14 @@ export function applyRendererSettings(
69
70
 
70
71
  /**
71
72
  * Create ONLY the WebGL renderer. The HOST owns the renderer (see
72
- * `HostContext.renderer`); the game/adapter owns the scene/camera/composer it
73
+ * `ThreeHostContext.renderer`); the game/adapter owns the scene/camera/composer it
73
74
  * renders into. Split out of `setupRenderer` for the adapter inversion.
74
75
  *
75
76
  * `rendering` is the scene-level render scope (`env.rendering`); it seeds the
76
77
  * reload-only `antialias` option at construction and the live shadow/resolution
77
78
  * settings (via {@link applyRendererSettings}). Absent → registry defaults.
78
79
  *
79
- * `opts` is additive (T6.1 slice 1, COMPOSITION-DESIGN.md D5 §1/§4): omitted
80
+ * `opts` is additive (T6.1 slice 1, D5 §1/§4): omitted
80
81
  * entirely, construction is byte-identical to before (no `alpha`/
81
82
  * `preserveDrawingBuffer` keys at all) — the legacy single-canvas host never
82
83
  * passes it. The roots path passes `alpha:true` for every stacked canvas
@@ -109,6 +110,10 @@ export function createHostRenderer(
109
110
  renderer.shadowMap.type = THREE.PCFSoftShadowMap;
110
111
  renderer.toneMapping = toneMappingModes[DEFAULTS.toneMapping.mode] ?? THREE.ACESFilmicToneMapping;
111
112
  renderer.toneMappingExposure = DEFAULTS.toneMapping.exposure;
113
+ // The shared KTX2Loader cannot transcode a KHR_texture_basisu texture until
114
+ // some renderer has told it which compressed formats this GPU accepts.
115
+ // Idempotent — see `detectKtx2Support`.
116
+ detectKtx2Support(renderer);
112
117
  return renderer;
113
118
  }
114
119
 
@@ -193,7 +198,7 @@ export function applyScenePostProcessing(
193
198
  renderer: THREE.WebGLRenderer,
194
199
  scene: THREE.Scene,
195
200
  camera: THREE.Camera,
196
- env?: SceneEnvironment,
201
+ env?: RenderEnvironment,
197
202
  objectMap?: Map<string, THREE.Object3D>,
198
203
  entityTags?: Map<string, string[]>,
199
204
  ): void {
@@ -553,7 +558,7 @@ export function applySceneRenderPipeline(
553
558
  renderer: THREE.WebGLRenderer,
554
559
  scene: THREE.Scene,
555
560
  camera: THREE.Camera,
556
- env?: SceneEnvironment,
561
+ env?: RenderEnvironment,
557
562
  options?: {
558
563
  basePixelRatio?: number;
559
564
  objectMap?: Map<string, THREE.Object3D>;
@@ -1,48 +1,61 @@
1
1
  /**
2
- * `@engine/world3d-react` — the React context that carries the engine
3
- * component runtime into an R3F tree, so `<Behavior>` (./behavior.tsx) can
4
- * reach the REAL `ComponentManager.attach` path from inside fiber JSX.
2
+ * `@engine/world3d-react` — the React context that carries this R3F world's
3
+ * engine runtime into its fiber tree: the world's `R3FGameContext`, read
4
+ * with {@link useGameContext}.
5
5
  *
6
- * Upstreamed from `examples/r3f-first-party/src/engine-bridge.ts` (R4,
7
- * docs/R3F-FOLLOW-THROUGH-SPEC.md the opt-in module
8
- * R3F-FIRST-PARTY-DESIGN §1.B Phase 2 names). The bridge value is built by
9
- * `createR3FAdapter`'s `mount()` (./r3f-adapter.tsx): a real
10
- * `createSystemRunner` + `createComponentManager` pair whose phases the
11
- * adapter's `update(dt)` runs under the HOST loop — so attached
12
- * GameComponents tick in engine phase order, pause included (no update while
13
- * the world is frozen), exactly like scene-authored components.
6
+ * Upstreamed from `examples/r3f-first-party/src/engine-bridge.ts` (R4).
7
+ * The bridge value
8
+ * is built by `createR3FAdapter`'s `mount()` (./r3f-adapter.tsx): a real
9
+ * `createSystemRunner` whose phases the adapter's `update(dt)` runs under the
10
+ * HOST loop so registered systems tick in engine phase order, pause included
11
+ * (no update while the world is frozen).
14
12
  *
15
- * `registry` is the project's own behavior registry (`<Behavior type="…">`
16
- * resolves against it) — REGISTRY-DRIVEN by design: the engine module never
17
- * imports project code; the project hands its registry to
18
- * `createR3FAdapter({ components })`, which places it here.
13
+ * Behavior in an R3F world is components and hooks: `useGameContext` is how a
14
+ * component reaches engine services.
19
15
  */
20
16
 
21
- import { createContext } from 'react';
22
- import type { ComponentManager } from '../ecs/component-manager';
23
- import type { GameComponentClass } from '../ecs/game-component';
24
-
25
- /** A project's behavior registry: `<Behavior type>` keys → component classes. */
26
- export type BehaviorRegistry = Readonly<Record<string, GameComponentClass>>;
17
+ import { createContext, useContext } from 'react';
18
+ import type { R3FGameContext } from './world-context';
27
19
 
28
20
  export interface EngineBridgeValue {
29
- /** The engine's real component manager for this R3F world. */
30
- readonly components: ComponentManager;
31
- /** The project's behavior registry (absent `<Behavior>` degrades loudly). */
32
- readonly registry?: BehaviorRegistry | undefined;
21
+ /** This root's context `input`, `debug`, `random`, `game`, `roots`, … .
22
+ * Present so ordinary components can reach engine services with a HOOK
23
+ * (see {@link useGameContext}). */
24
+ readonly ctx?: R3FGameContext | undefined;
33
25
  }
34
26
 
35
27
  export const EngineBridge = createContext<EngineBridgeValue | null>(null);
36
28
 
37
- /** Resolve a `<Behavior type>` key against the bridge's registry, throwing a
38
- * teaching error naming the registered keys (the same loud failure shape the
39
- * example's project-owned `getComponent` had). */
40
- export function resolveBehaviorClass(registry: BehaviorRegistry, type: string): GameComponentClass {
41
- const cls = registry[type];
42
- if (!cls) {
29
+ /**
30
+ * This R3F root's context, for use inside any component in the tree — an
31
+ * {@link R3FGameContext}, which is exactly the subsystems an R3F world builds.
32
+ * The ones it does not build are absent from the type, so reaching for one is
33
+ * a compile error at the call site; that type's doc comment says what to reach
34
+ * for instead.
35
+ *
36
+ * An ordinary component does what a model would write:
37
+ *
38
+ * ```tsx
39
+ * function Player({ speed = 4.5 }) {
40
+ * const { input } = useGameContext();
41
+ * const ref = useRef<THREE.Group>(null);
42
+ * useFrame((_, dt) => { if (input.isPressed('move_forward')) … });
43
+ * return <group ref={ref}><PlayerCharacter /></group>;
44
+ * }
45
+ * ```
46
+ *
47
+ * Throws when called outside an R3F root mounted by `createR3FAdapter` —
48
+ * absent context is a wiring bug, and a silent `null` would surface as a
49
+ * mystery `undefined` deep in a frame callback.
50
+ */
51
+ export function useGameContext(): R3FGameContext {
52
+ const bridge = useContext(EngineBridge);
53
+ if (!bridge?.ctx) {
43
54
  throw new Error(
44
- `Unknown behavior type "${type}" registered: ${Object.keys(registry).join(', ')}`,
55
+ 'useGameContext(): no engine root context. This hook only works inside an R3F ' +
56
+ 'root mounted by createR3FAdapter (@engine/world3d-react) — check that the ' +
57
+ 'component is rendered inside your world entry, not in a standalone <Canvas>.',
45
58
  );
46
59
  }
47
- return cls;
60
+ return bridge.ctx;
48
61
  }
@@ -1,15 +1,15 @@
1
1
  /**
2
- * world3d-react — the react-three-fiber bridge for vgai's threejs surface
2
+ * world3d-react — the react-three-fiber bridge for vgai's three surface
3
3
  * (`@engine/world3d-react`).
4
4
  *
5
- * The OPT-IN module R3F-FIRST-PARTY-DESIGN §1.B Phase 2 names, upstreamed
6
- * from the live-proven `examples/r3f-first-party` project copies (R4,
7
- * docs/R3F-FOLLOW-THROUGH-SPEC.md; decision D24 — R3F TSX is the blessed
8
- * three.js authoring path). Mirrors how `world2d/` is pixi's opt-in home: a
9
- * peer surface module the engine CORE never imports (enforced by
10
- * `packages/engine/test/react-core-import-ban.test.ts` — this directory is
11
- * an allowed react-importing entry alongside `react/`, and core files may
12
- * not import it).
5
+ * An OPT-IN module, upstreamed
6
+ * from the live-proven `examples/r3f-first-party` project copies (R4;
7
+ * decision D24 — R3F TSX is the blessed three.js authoring path). Mirrors how
8
+ * `pixi/` is the PixiJS surface's opt-in home: a peer surface module the engine
9
+ * CORE never imports (enforced by
10
+ * `packages/engine/test/react-core-import-ban.test.ts` — this directory is an
11
+ * allowed react-importing entry alongside `react/`, and core files may not
12
+ * import it).
13
13
  *
14
14
  * Peer contract: an importing PROJECT already depends on `react`,
15
15
  * `@react-three/fiber`, and `three` (fiber's three must be deduped to the
@@ -19,26 +19,31 @@
19
19
  * that already mount through fiber ever import this module.
20
20
  *
21
21
  * Surface:
22
- * - `createR3FAdapter({ id, content, components })` — mount an R3F tree as
23
- * a first-party `kind: "threejs"` world under the host's gated loop.
24
- * - `<Behavior type="…" {...config}>` attach a registry GameComponent to
25
- * the wrapped child's Object3D under engine phases (registry-driven via
26
- * `EngineBridge`; generic over the project's registry for typed keys).
27
- * - `EngineBridge` / `EngineBridgeValue` / `BehaviorRegistry` the context
28
- * contract, exported for advanced adopters building their own bridge.
22
+ * - `createR3FAdapter({ id, content })` — mount an R3F tree as a first-party
23
+ * `kind: "three"` world under the host's gated loop.
24
+ * - `useGameContext()`this root's `R3FGameContext` (`input`, `debug`,
25
+ * `random`, `game`, `roots`, …) from inside any component in the tree. This
26
+ * is how an R3F world reaches engine services: behavior is components +
27
+ * hooks (D26b), not a class under a string key. The subsystems an R3F world
28
+ * does not build are absent from that type — see its doc comment for what
29
+ * to use instead.
30
+ * - `EngineBridge` / `EngineBridgeValue` — the context contract, exported
31
+ * for advanced adopters building their own bridge.
29
32
  */
30
33
 
31
- export { Behavior, type BehaviorProps } from './behavior';
32
- export {
33
- type BehaviorRegistry,
34
- EngineBridge,
35
- type EngineBridgeValue,
36
- resolveBehaviorClass,
37
- } from './engine-bridge';
34
+ export { EngineBridge, type EngineBridgeValue, useGameContext } from './engine-bridge';
38
35
  export { type CreateR3FAdapterOptions, createR3FAdapter } from './r3f-adapter';
36
+ export { r3fRootFactory, resolveR3FEntryAdapter } from './r3f-root-factory';
37
+ export {
38
+ applyWorldRendererConfig,
39
+ type WorldOutputColorSpace,
40
+ type WorldRendererConfig,
41
+ type WorldToneMapping,
42
+ } from './renderer-config';
39
43
  export {
40
- createR3FWorldContext,
44
+ createR3FRootContext,
41
45
  DEFAULT_INPUT_MAP_PATH,
42
- type R3FWorldContextOptions,
43
- type R3FWorldRuntime,
46
+ type R3FGameContext,
47
+ type R3FRootContextOptions,
48
+ type R3FRootRuntime,
44
49
  } from './world-context';