@vgai/engine 0.5.15 → 0.5.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/adapter-module.d.ts +229 -33
- package/dist/adapter/adapter-module.d.ts.map +1 -1
- package/dist/adapter/adapter-module.js +222 -50
- package/dist/adapter/authoring-seam-contract.d.ts +530 -0
- package/dist/adapter/authoring-seam-contract.d.ts.map +1 -0
- package/dist/adapter/authoring-seam-contract.js +162 -0
- package/dist/adapter/authoring.d.ts +237 -50
- package/dist/adapter/authoring.d.ts.map +1 -1
- package/dist/adapter/authoring.js +2 -1
- package/dist/adapter/entrypoint-selection-source.d.ts +35 -0
- package/dist/adapter/entrypoint-selection-source.d.ts.map +1 -0
- package/dist/adapter/entrypoint-selection-source.js +151 -0
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts +7 -3
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
- package/dist/adapter/finders/scenes-from-entrypoint-selection.js +145 -27
- package/dist/adapter/first-party-systems.d.ts +13 -3
- package/dist/adapter/first-party-systems.d.ts.map +1 -1
- package/dist/adapter/first-party-systems.js +10 -3
- package/dist/adapter/index.d.ts +7 -2
- package/dist/adapter/index.d.ts.map +1 -1
- package/dist/adapter/index.js +4 -0
- package/dist/adapter/ingest/contract-system-adapters.d.ts +1 -1
- package/dist/adapter/ingest/contract-system-adapters.d.ts.map +1 -1
- package/dist/adapter/ingest/contract-system-adapters.js +13 -25
- package/dist/adapter/ingest/game-contract-seams.d.ts +214 -0
- package/dist/adapter/ingest/game-contract-seams.d.ts.map +1 -0
- package/dist/adapter/ingest/game-contract-seams.js +62 -0
- package/dist/adapter/ingest/game-contract.d.ts +5 -3
- package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
- package/dist/adapter/ingest/game-contract.js +4 -1
- package/dist/adapter/ingest/merge-debug-adapters.d.ts +52 -0
- package/dist/adapter/ingest/merge-debug-adapters.d.ts.map +1 -0
- package/dist/adapter/ingest/merge-debug-adapters.js +141 -0
- package/dist/adapter/ingest/observation-debug-adapter.d.ts +56 -0
- package/dist/adapter/ingest/observation-debug-adapter.d.ts.map +1 -0
- package/dist/adapter/ingest/observation-debug-adapter.js +131 -0
- package/dist/adapter/ingest/scene-capture.d.ts +38 -13
- package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
- package/dist/adapter/ingest/scene-capture.js +101 -24
- package/dist/adapter/ingest/visible-capture-window.d.ts +22 -5
- package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -1
- package/dist/adapter/ingest/visible-capture-window.js +46 -7
- package/dist/adapter/native-debug-module.d.ts +43 -0
- package/dist/adapter/native-debug-module.d.ts.map +1 -0
- package/dist/adapter/native-debug-module.js +230 -0
- package/dist/adapter/object3d-authoring-subject.d.ts +32 -0
- package/dist/adapter/object3d-authoring-subject.d.ts.map +1 -0
- package/dist/adapter/object3d-authoring-subject.js +25 -0
- package/dist/adapter/root-adapter.d.ts +25 -56
- package/dist/adapter/root-adapter.d.ts.map +1 -1
- package/dist/adapter/root-adapter.js +4 -20
- package/dist/adapter/root-seam-contract.d.ts +129 -0
- package/dist/adapter/root-seam-contract.d.ts.map +1 -0
- package/dist/adapter/root-seam-contract.js +37 -0
- package/dist/adapter/seam-evidence.d.ts +118 -0
- package/dist/adapter/seam-evidence.d.ts.map +1 -0
- package/dist/adapter/seam-evidence.js +231 -0
- package/dist/adapter/setup-three-root-adapter.d.ts +2 -3
- package/dist/adapter/setup-three-root-adapter.d.ts.map +1 -1
- package/dist/adapter/setup-three-root-adapter.js +11 -12
- package/dist/adapter/system-adapter.d.ts +6 -1
- package/dist/adapter/system-adapter.d.ts.map +1 -1
- package/dist/adapter/system-seam-contract.d.ts +354 -0
- package/dist/adapter/system-seam-contract.d.ts.map +1 -0
- package/dist/adapter/system-seam-contract.js +95 -0
- package/dist/animation/xstate-animation-binding.d.ts +6 -0
- package/dist/animation/xstate-animation-binding.d.ts.map +1 -1
- package/dist/animation/xstate-animation-binding.js +142 -4
- package/dist/asset-formats/collider.d.ts +1 -1
- package/dist/asset-formats/mesh.d.ts +2 -2
- package/dist/asset-formats/particles.d.ts +1 -1
- package/dist/canvas-react/engine-bridge.d.ts +6 -6
- package/dist/canvas-react/engine-bridge.d.ts.map +1 -1
- package/dist/canvas-react/engine-bridge.js +2 -2
- package/dist/canvas-react/index.d.ts +2 -2
- package/dist/canvas-react/index.d.ts.map +1 -1
- package/dist/canvas-react/index.js +1 -1
- package/dist/canvas-react/pixi-react-adapter.d.ts +15 -9
- package/dist/canvas-react/pixi-react-adapter.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-adapter.js +129 -64
- package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-root-factory.js +4 -0
- package/dist/canvas-react/world-context.d.ts +22 -12
- package/dist/canvas-react/world-context.d.ts.map +1 -1
- package/dist/canvas-react/world-context.js +12 -51
- package/dist/data/curve.d.ts +1 -1
- package/dist/data/curve.js +2 -2
- package/dist/dev/instruments.d.ts +4 -4
- package/dist/dev/instruments.js +4 -4
- package/dist/dev/register-render-vitals.d.ts +2 -2
- package/dist/dev/register-render-vitals.js +2 -2
- package/dist/dev/static-batch-advisor.d.ts +1 -1
- package/dist/dev/static-batch-advisor.js +2 -2
- package/dist/ecs/user-data.d.ts +5 -0
- package/dist/ecs/user-data.d.ts.map +1 -1
- package/dist/ecs/user-data.js +3 -0
- package/dist/input/input-manager.d.ts +64 -0
- package/dist/input/input-manager.d.ts.map +1 -1
- package/dist/input/input-manager.js +149 -0
- package/dist/manifest/load.d.ts +0 -3
- package/dist/manifest/load.d.ts.map +1 -1
- package/dist/manifest/load.js +0 -4
- package/dist/manifest/schema.d.ts +9 -11
- package/dist/manifest/schema.d.ts.map +1 -1
- package/dist/manifest/schema.js +2 -23
- package/dist/pixi/authoring.d.ts +33 -0
- package/dist/pixi/authoring.d.ts.map +1 -1
- package/dist/pixi/authoring.js +301 -2
- package/dist/pixi/ingest.d.ts +2 -1
- package/dist/pixi/ingest.d.ts.map +1 -1
- package/dist/pixi/ingest.js +4 -2
- package/dist/pixi/scene-capture.d.ts +9 -1
- package/dist/pixi/scene-capture.d.ts.map +1 -1
- package/dist/pixi/scene-capture.js +9 -6
- package/dist/react/world-state.d.ts +12 -0
- package/dist/react/world-state.d.ts.map +1 -1
- package/dist/react/world-state.js +14 -0
- package/dist/render/particles-factory.d.ts +13 -6
- package/dist/render/particles-factory.d.ts.map +1 -1
- package/dist/render/particles-factory.js +6 -3
- package/dist/render/soft-particle-depth.d.ts +3 -3
- package/dist/render/soft-particle-depth.d.ts.map +1 -1
- package/dist/render/soft-particle-depth.js +8 -13
- package/dist/runtime/create-runtime.d.ts +25 -62
- package/dist/runtime/create-runtime.d.ts.map +1 -1
- package/dist/runtime/create-runtime.js +58 -105
- package/dist/runtime/dev-build.d.ts +37 -0
- package/dist/runtime/dev-build.d.ts.map +1 -0
- package/dist/runtime/dev-build.js +49 -0
- package/dist/runtime/game.d.ts +38 -72
- package/dist/runtime/game.d.ts.map +1 -1
- package/dist/runtime/game.js +40 -55
- package/dist/runtime/mount-game.d.ts.map +1 -1
- package/dist/runtime/mount-game.js +12 -1
- package/dist/runtime/mount-manifest.d.ts +0 -13
- package/dist/runtime/mount-manifest.d.ts.map +1 -1
- package/dist/runtime/mount-manifest.js +5 -25
- package/dist/runtime/types.d.ts +18 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/setup/setup-renderer.d.ts +3 -4
- package/dist/setup/setup-renderer.d.ts.map +1 -1
- package/dist/setup/setup-renderer.js +3 -4
- package/dist/world3d-react/engine-bridge.d.ts +1 -1
- package/dist/world3d-react/engine-bridge.d.ts.map +1 -1
- package/dist/world3d-react/engine-bridge.js +29 -4
- package/dist/world3d-react/index.d.ts +1 -1
- package/dist/world3d-react/index.js +1 -1
- package/dist/world3d-react/r3f-adapter.d.ts +7 -0
- package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
- package/dist/world3d-react/r3f-adapter.js +132 -28
- package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
- package/dist/world3d-react/r3f-root-factory.js +14 -0
- package/dist/world3d-react/rapier-physics-bridge.d.ts +4 -4
- package/dist/world3d-react/rapier-physics-bridge.js +3 -3
- package/dist/world3d-react/world-context.d.ts +52 -3
- package/dist/world3d-react/world-context.d.ts.map +1 -1
- package/dist/world3d-react/world-context.js +94 -64
- package/dist-config/config.js.map +1 -1
- package/package.json +1 -1
- package/schemas/engine-capabilities.json +8 -8
- package/schemas/vgai-project.schema.json +1 -6
- package/src/adapter/adapter-module.ts +405 -72
- package/src/adapter/authoring-seam-contract.ts +211 -0
- package/src/adapter/authoring.ts +242 -50
- package/src/adapter/entrypoint-selection-source.ts +159 -0
- package/src/adapter/finders/scenes-from-entrypoint-selection.ts +162 -36
- package/src/adapter/first-party-systems.ts +17 -3
- package/src/adapter/index.ts +51 -1
- package/src/adapter/ingest/contract-system-adapters.ts +26 -30
- package/src/adapter/ingest/game-contract-seams.ts +85 -0
- package/src/adapter/ingest/game-contract.ts +9 -4
- package/src/adapter/ingest/merge-debug-adapters.ts +193 -0
- package/src/adapter/ingest/observation-debug-adapter.ts +158 -0
- package/src/adapter/ingest/scene-capture.ts +126 -23
- package/src/adapter/ingest/visible-capture-window.ts +63 -9
- package/src/adapter/native-debug-module.ts +307 -0
- package/src/adapter/object3d-authoring-subject.ts +50 -0
- package/src/adapter/root-adapter.ts +25 -56
- package/src/adapter/root-seam-contract.ts +57 -0
- package/src/adapter/seam-evidence.ts +336 -0
- package/src/adapter/setup-three-root-adapter.ts +13 -15
- package/src/adapter/system-adapter.ts +6 -1
- package/src/adapter/system-seam-contract.ts +121 -0
- package/src/animation/xstate-animation-binding.ts +151 -4
- package/src/canvas-react/engine-bridge.ts +6 -6
- package/src/canvas-react/index.ts +2 -1
- package/src/canvas-react/pixi-react-adapter.tsx +143 -77
- package/src/canvas-react/pixi-react-root-factory.tsx +4 -0
- package/src/canvas-react/world-context.ts +26 -101
- package/src/data/curve.ts +2 -2
- package/src/dev/instruments.ts +4 -4
- package/src/dev/register-render-vitals.ts +2 -2
- package/src/dev/static-batch-advisor.ts +2 -2
- package/src/ecs/user-data.ts +5 -0
- package/src/input/input-manager.ts +145 -0
- package/src/manifest/load.ts +0 -7
- package/src/manifest/schema.ts +2 -25
- package/src/pixi/authoring.ts +329 -2
- package/src/pixi/ingest.ts +9 -2
- package/src/pixi/scene-capture.ts +20 -5
- package/src/react/world-state.tsx +15 -0
- package/src/render/particles-factory.ts +16 -6
- package/src/render/soft-particle-depth.ts +8 -13
- package/src/runtime/create-runtime.ts +79 -158
- package/src/runtime/dev-build.ts +49 -0
- package/src/runtime/game.ts +72 -113
- package/src/runtime/mount-game.ts +19 -1
- package/src/runtime/mount-manifest.ts +5 -37
- package/src/runtime/types.ts +22 -0
- package/src/setup/setup-renderer.ts +3 -4
- package/src/world3d-react/engine-bridge.ts +29 -4
- package/src/world3d-react/index.ts +1 -1
- package/src/world3d-react/r3f-adapter.tsx +140 -31
- package/src/world3d-react/r3f-root-factory.tsx +14 -0
- package/src/world3d-react/rapier-physics-bridge.tsx +4 -4
- package/src/world3d-react/world-context.ts +111 -87
- package/dist/runtime/dev-layers.d.ts +0 -34
- package/dist/runtime/dev-layers.d.ts.map +0 -1
- package/dist/runtime/dev-layers.js +0 -46
- package/src/runtime/dev-layers.ts +0 -46
|
@@ -31,12 +31,11 @@ import type { PlaytestContext } from './types';
|
|
|
31
31
|
* by `createGameRuntime` and every headless test harness
|
|
32
32
|
* (`test/game-root.test.ts`, `test/frame-order.test.ts`,
|
|
33
33
|
* `test/game-two-roots.test.ts`) so there is exactly one code path for this
|
|
34
|
-
* wiring
|
|
35
|
-
* 3) — `physics`/`collisions`/`camera`/ `frame` are populated from the mount's
|
|
34
|
+
* wiring. `physics`/`collisions`/`camera`/`frame` are populated from the mount's
|
|
36
35
|
* first-party `GameContext`/ `MountedSetupThreeRoot` when available, left
|
|
37
36
|
* `undefined` otherwise (an external adapter's mount has none of these
|
|
38
37
|
* first-party handles — `frame` in particular is what makes
|
|
39
|
-
* `GameInternal.runFrame`
|
|
38
|
+
* `GameInternal.runFrame` fall back to a single opaque
|
|
40
39
|
* `mounted.update` call per substep for such a world).
|
|
41
40
|
*/
|
|
42
41
|
function firstPartyExtras(
|
|
@@ -53,10 +52,8 @@ function firstPartyExtras(
|
|
|
53
52
|
|
|
54
53
|
/**
|
|
55
54
|
* Register a three world onto the Game shell. `opts.id` defaults to
|
|
56
|
-
* `'main'
|
|
57
|
-
*
|
|
58
|
-
* real multi-world manifests later) pass an explicit id. This is the ONE
|
|
59
|
-
* registration code path for a three world, whatever its id.
|
|
55
|
+
* `'main'`; multi-root callers pass an explicit id. This is the one
|
|
56
|
+
* registration path for a three world, whatever its id.
|
|
60
57
|
*/
|
|
61
58
|
export function registerThreeRoot(
|
|
62
59
|
game: GameInternal,
|
|
@@ -104,13 +101,13 @@ export function registerPixiRoot(
|
|
|
104
101
|
}
|
|
105
102
|
|
|
106
103
|
/**
|
|
107
|
-
* Register a
|
|
104
|
+
* Register a React world onto the Game shell — the DOM
|
|
108
105
|
* analog of {@link registerThreeRoot}/{@link registerPixiRoot}. A react
|
|
109
106
|
* world has no `frame` hooks (react's own
|
|
110
107
|
* `createRoot` schedules its commits; `GameInternal.runFrame` correctly
|
|
111
108
|
* leaves a world with no `frame` untouched by its opaque-`update` fallback
|
|
112
109
|
* too, since `MountedReactGame` declares no `update`). `adapter` is typed
|
|
113
|
-
* as the real {@link ReactRootAdapter} shape (
|
|
110
|
+
* as the real {@link ReactRootAdapter} shape (same reasoning as
|
|
114
111
|
* `registerPixiRoot`'s doc comment above) — `RootInstance.adapter` only
|
|
115
112
|
* needs `.id`, so this passes through with zero cast.
|
|
116
113
|
*/
|
|
@@ -134,12 +131,12 @@ export function registerReactRoot(
|
|
|
134
131
|
}
|
|
135
132
|
|
|
136
133
|
/**
|
|
137
|
-
* One world to mount in the
|
|
138
|
-
*
|
|
139
|
-
*
|
|
134
|
+
* One world to mount in the universal host — the host-facing mirror of
|
|
135
|
+
* `manifest/load.ts`'s `ResolvedAdapterRoot` (same identity and presentation
|
|
136
|
+
* fields; the
|
|
140
137
|
* manifest-to-host translation itself is the editor's job, not this file's).
|
|
141
138
|
*
|
|
142
|
-
* `
|
|
139
|
+
* A `dom` root mounts as a DOM layer `<div>` in the same
|
|
143
140
|
* stack instead of a canvas — see {@link ReactRootMountSpec}/{@link
|
|
144
141
|
* ReactRootAdapter} below.
|
|
145
142
|
*/
|
|
@@ -152,28 +149,6 @@ export interface RootMountSpecBase {
|
|
|
152
149
|
readonly zOrder?: number | undefined;
|
|
153
150
|
/** Whether play-mode pause/step applies to this world (D10). Defaults to `true`. */
|
|
154
151
|
readonly pausable?: boolean | undefined;
|
|
155
|
-
/**
|
|
156
|
-
* `vgai.project.json`'s `dev: true` — this root is a DEV LAYER (the game's
|
|
157
|
-
* own dev GUI), not shipped game content. Two things happen here: the
|
|
158
|
-
* surface is stacked ABOVE every non-dev root (a dev layer sits topmost),
|
|
159
|
-
* and it is marked `data-vgai-dev-layer="true"` so the play compositor can
|
|
160
|
-
* leave it out of a capture (`composite-screenshot.ts`). Whether a dev root
|
|
161
|
-
* reaches this function at all is the CALLER's gate —
|
|
162
|
-
* `mountManifestRoots` asks `devLayersEnabled()` before building the spec.
|
|
163
|
-
*
|
|
164
|
-
* It steals no input from the game while closed for a reason that predates
|
|
165
|
-
* this flag and needs no new mechanism: a dev layer is a `dom` root, DOM
|
|
166
|
-
* layers are `pointer-events:none` (see `mountOneReactRoot`) with the
|
|
167
|
-
* mounted tree opting individual elements back in, and the input router
|
|
168
|
-
* never sees DOM roots at all.
|
|
169
|
-
*/
|
|
170
|
-
readonly dev?: boolean | undefined;
|
|
171
|
-
/** `'gated'` (host-driven, default) or `'self-driven'` (this world drives
|
|
172
|
-
* its own loop — D5's "composited, unsynchronized" tier). Carried through
|
|
173
|
-
* for parity with `ResolvedAdapterRoot`; T6.1 slice 1 does not yet
|
|
174
|
-
* validate it against the mounted adapter's actual `drivesOwnLoop` (that
|
|
175
|
-
* cross-check, if ever needed, is T7.6's loop-gate surface). */
|
|
176
|
-
readonly loop?: 'gated' | 'self-driven' | undefined;
|
|
177
152
|
/**
|
|
178
153
|
* Optional claim predicate for the delegating input router (D5 §2a), over
|
|
179
154
|
* a point RELATIVE TO THE CONTAINER. Absent means: this world claims only
|
|
@@ -199,8 +174,8 @@ export interface PixiRootMountSpec extends RootMountSpecBase {
|
|
|
199
174
|
}
|
|
200
175
|
|
|
201
176
|
/**
|
|
202
|
-
* A live, mounted
|
|
203
|
-
*
|
|
177
|
+
* A live, mounted React world — the DOM analog of a mounted canvas world.
|
|
178
|
+
* React roots render from game state
|
|
204
179
|
* instead of a per-frame `update`, so this shape carries no `update`/`fixedUpdate`/
|
|
205
180
|
* `ctx`/`frame` — `drivesOwnLoop` is always `false` (react's `createRoot`
|
|
206
181
|
* schedules its OWN commits; the host's fixed-step loop never drives it,
|
|
@@ -217,10 +192,10 @@ export interface PixiRootMountSpec extends RootMountSpecBase {
|
|
|
217
192
|
* `kind: 'dom'` (§7.1-15): a react world has no `observe` BY DESIGN — that
|
|
218
193
|
* hook is scoped to `useRootObservation` (the ingested/foreign-world case),
|
|
219
194
|
* not `useWorldState` (`ui/game-state.tsx`), which a react world's own
|
|
220
|
-
* mounted tree uses instead — it reads `Game.state` directly
|
|
221
|
-
*
|
|
195
|
+
* mounted tree uses instead — it reads `Game.state` directly, never a
|
|
196
|
+
* per-world `observe`.
|
|
222
197
|
*
|
|
223
|
-
* `kind`/`container`
|
|
198
|
+
* `kind`/`container` satisfy `MountedReactRoot` (`adapter/
|
|
224
199
|
* root-adapter.ts`) — `container` is the SAME `DomHostContext.container` the
|
|
225
200
|
* adapter's `mount` was handed (identity matters, mirroring `threeScene()`/
|
|
226
201
|
* `pixiStage()`'s "same instance the adapter mounted" contract); every
|
|
@@ -236,7 +211,7 @@ export interface MountedReactGame extends MountedReactRoot {
|
|
|
236
211
|
* A react-shaped adapter — the structural contract `RootMountSpec`'s
|
|
237
212
|
* `react` variant requires. Deliberately
|
|
238
213
|
* NOT tied to a concrete implementer here so an editor-resolved adapter (the
|
|
239
|
-
* `default-react` resolver branch
|
|
214
|
+
* `default-react` resolver branch) can satisfy
|
|
240
215
|
* this shape without this file importing react-dom or any editor code.
|
|
241
216
|
*
|
|
242
217
|
* A genuine `RootAdapter<'dom'>` refinement (`HostContextFor<'dom'>` =
|
|
@@ -328,37 +303,25 @@ export interface RootsRuntimeConfig {
|
|
|
328
303
|
*
|
|
329
304
|
* This is the GENERIC host handle — it has no first-party concepts (no
|
|
330
305
|
* `GameContext`, no `GameSetupFn`). First-party features (the live `GameContext`,
|
|
331
|
-
* warm-restart hot reload) are reached
|
|
306
|
+
* warm-restart hot reload) are reached through `game.defaultRoot.mounted`
|
|
307
|
+
* and narrowed to `MountedSetupThreeRoot`
|
|
332
308
|
* (the editor does this for HMR/physics-sync — those are inherently first-party).
|
|
333
309
|
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
* world) by DECLARATION order, per `Game.defaultRoot`'s existing rule —
|
|
337
|
-
* independent of `zOrder`/canvas stacking, which is a rendering-only concern.
|
|
310
|
+
* Root access is explicit through `game.roots`, `game.world(id)`, and
|
|
311
|
+
* `game.defaultRoot`; the session does not project a surface-specific alias.
|
|
338
312
|
*/
|
|
339
313
|
export interface GameSession {
|
|
340
314
|
stop(): void;
|
|
315
|
+
/** Resolves after every root has finished the cleanup initiated by stop(). */
|
|
316
|
+
readonly stopComplete: Promise<void>;
|
|
341
317
|
pause(): void;
|
|
342
318
|
resume(): void;
|
|
343
319
|
step(): void;
|
|
344
|
-
/** Resize every world's render buffer. `pixelRatio`
|
|
345
|
-
*
|
|
346
|
-
* the mount-time ratio is left untouched (legacy behavior). */
|
|
320
|
+
/** Resize every world's render buffer. `pixelRatio` optionally re-pins the
|
|
321
|
+
* three renderers' DPR in the same pass. */
|
|
347
322
|
resize(width: number, height: number, pixelRatio?: number): void;
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
/** The default world's mounted root, as the honest `MountedRoot` union —
|
|
351
|
-
* narrow on `.kind` before reaching for a surface's own members (the same
|
|
352
|
-
* discriminant `scene`/`camera` above use). This deliberately does NOT
|
|
353
|
-
* claim `MountedThreeRoot`: a roots-path session whose default world is
|
|
354
|
-
* canvas or dom returns that world's mount, and the old
|
|
355
|
-
* `as MountedThreeRoot` cast made every such value a type-level lie. For a
|
|
356
|
-
* first-party three mount, narrow to `'three'` and cast to
|
|
357
|
-
* `MountedSetupThreeRoot` for the first-party extras. */
|
|
358
|
-
readonly mounted: MountedRoot;
|
|
359
|
-
/** The Game root (T6.1 slice 1) — the multi-world entry point
|
|
360
|
-
* (`game.roots`/`game.world(id)`/`game.queryByComponent`) for callers
|
|
361
|
-
* that need more than the default-world aliases above. */
|
|
323
|
+
/** The multi-root entry point. Root access stays explicit through
|
|
324
|
+
* `game.roots`, `game.world(id)`, and `game.defaultRoot`. */
|
|
362
325
|
readonly game: Game;
|
|
363
326
|
}
|
|
364
327
|
|
|
@@ -374,7 +337,7 @@ export async function createGameRuntime(config: RootsRuntimeConfig): Promise<Gam
|
|
|
374
337
|
}
|
|
375
338
|
|
|
376
339
|
// ---------------------------------------------------------------------------
|
|
377
|
-
//
|
|
340
|
+
// Universal root host
|
|
378
341
|
// ---------------------------------------------------------------------------
|
|
379
342
|
|
|
380
343
|
/** A stand-in `THREE.WebGLRenderer` for headless (`headless:true`) three
|
|
@@ -397,7 +360,7 @@ function createHeadlessRendererStub(): THREE.WebGLRenderer {
|
|
|
397
360
|
|
|
398
361
|
/** One already-mounted world, tracked for disposal + the router. `element`
|
|
399
362
|
* is the world's stacked surface — an `HTMLCanvasElement` for three/
|
|
400
|
-
* canvas, or the DOM-root layer `<div>` for
|
|
363
|
+
* canvas, or the DOM-root layer `<div>` for React — kept
|
|
401
364
|
* under one field name so `fullCleanup`'s disposal loop stays kind-generic
|
|
402
365
|
* (`container.removeChild(entry.element)` needs no branch). */
|
|
403
366
|
interface MountedAdapterRoot {
|
|
@@ -477,11 +440,8 @@ async function mountOneThreeRoot(
|
|
|
477
440
|
// resize-staleness (the roots-path `resize()` below always calls
|
|
478
441
|
// `setSize(rw, rh, false)`, so nothing else was ever going to update this
|
|
479
442
|
// canvas' CSS after mount). `createHostRenderer`'s own default
|
|
480
|
-
// (`updateStyle:true`) is intentionally left alone
|
|
481
|
-
//
|
|
482
|
-
// relies on that construction-time stamp + its own later `updateStyle:true`
|
|
483
|
-
// resizes for byte-identical behavior; this fix touches only the
|
|
484
|
-
// roots-path canvas, after the fact.
|
|
443
|
+
// (`updateStyle:true`) is intentionally left alone; the host owns CSS layout
|
|
444
|
+
// and reasserts the container-relative size after renderer construction.
|
|
485
445
|
canvas.style.width = '100%';
|
|
486
446
|
canvas.style.height = '100%';
|
|
487
447
|
renderer.setPixelRatio(dpr);
|
|
@@ -559,7 +519,7 @@ async function mountOnePixiRoot(
|
|
|
559
519
|
}
|
|
560
520
|
|
|
561
521
|
/**
|
|
562
|
-
* Mount one
|
|
522
|
+
* Mount one React `RootMountSpec` — the DOM sibling of
|
|
563
523
|
* `mountOneThreeRoot`/`mountOnePixiRoot`. Unlike its canvas-backed siblings
|
|
564
524
|
* this returns NO `routerEntry`: a react world's DOM-root layer participates in
|
|
565
525
|
* D5's z-order/box stacking (the caller still creates and positions its `<div>`
|
|
@@ -592,7 +552,7 @@ async function mountOneReactRoot(
|
|
|
592
552
|
// An absolutely-positioned layer with no width/height collapses to zero
|
|
593
553
|
// content size, so a child's
|
|
594
554
|
// own position:absolute offsets resolve against a degenerate containing
|
|
595
|
-
// block
|
|
555
|
+
// block; otherwise clicks land outside the game.
|
|
596
556
|
layer.style.width = '100%';
|
|
597
557
|
layer.style.height = '100%';
|
|
598
558
|
const reactHost: DomHostContext = { container: layer, game };
|
|
@@ -613,8 +573,8 @@ async function mountOneReactRoot(
|
|
|
613
573
|
}
|
|
614
574
|
|
|
615
575
|
/**
|
|
616
|
-
* The
|
|
617
|
-
*
|
|
576
|
+
* The universal host implementer behind {@link createGameRuntime}. It builds
|
|
577
|
+
* one surface per world — a
|
|
618
578
|
* canvas for three/canvas, a DOM-root `<div>` layer for react — stacked
|
|
619
579
|
* per D5 §1, z-order/ties exactly matching
|
|
620
580
|
* `manifest/load.ts`'s sort, ONE `Game`, and registers every world onto it
|
|
@@ -628,16 +588,11 @@ async function mountOneReactRoot(
|
|
|
628
588
|
|
|
629
589
|
/**
|
|
630
590
|
* Dev/e2e-only `window.__vgaiScene`/`__vgaiCamera` exposure for the roots
|
|
631
|
-
* path's
|
|
632
|
-
* to keep that function's own cyclomatic complexity down.
|
|
633
|
-
*
|
|
634
|
-
*
|
|
635
|
-
*
|
|
636
|
-
* non-threejs default world (or none at all) publishes neither global,
|
|
637
|
-
* exactly like those getters return `undefined` in that case. Returns a
|
|
638
|
-
* retraction callback — identity-guarded (a newer session's globals must
|
|
639
|
-
* never be clobbered by an older session's stop, same reason the legacy
|
|
640
|
-
* path's own cleanup guards it) — that is a no-op when nothing was
|
|
591
|
+
* path's default world — split out of `createRootsGameRuntime` purely
|
|
592
|
+
* to keep that function's own cyclomatic complexity down. It uses the
|
|
593
|
+
* "first three world, else none" rule: a non-Three default world publishes
|
|
594
|
+
* neither global. Returns an identity-guarded retraction callback so an
|
|
595
|
+
* older session's stop cannot clobber a newer session's globals. It is a no-op when nothing was
|
|
641
596
|
* published (non-DEV build, or non-threejs default world).
|
|
642
597
|
*/
|
|
643
598
|
function installDefaultRootDevGlobals(game: GameInternal): () => void {
|
|
@@ -804,26 +759,15 @@ async function createRootsGameRuntime(config: RootsRuntimeConfig): Promise<GameS
|
|
|
804
759
|
// --- Surface stack (D5 §1): DOM/z-order follows zOrder, ties -> array order
|
|
805
760
|
// — computed FIRST (bottom -> top) so both the z-index assignment below and
|
|
806
761
|
// the router's default-claim rule share one definition. A react world's
|
|
807
|
-
// DOM-root layer shares this SAME stacking pass
|
|
808
|
-
// stacking model, no special case") even though it is a `<div>`, not a
|
|
762
|
+
// DOM-root layer shares this SAME stacking pass even though it is a `<div>`, not a
|
|
809
763
|
// canvas, and carries no `hitTest` (the router never sees react entries at
|
|
810
764
|
// all — see the dispatch loop below). ---
|
|
811
|
-
// Dev layers sit TOPMOST, above every non-dev root, whatever zOrder either
|
|
812
|
-
// declares: a dev GUI the game can paint over is not a dev GUI. Expressed as
|
|
813
|
-
// two independent `stackOrder` passes concatenated rather than a magic
|
|
814
|
-
// zOrder value, so `stackOrder`'s one rule (ascending zOrder, ties -> array
|
|
815
|
-
// order) still governs WITHIN each band and nothing has to reason about what
|
|
816
|
-
// number is "high enough".
|
|
817
765
|
const claimEntries = mountSpecs.map((spec) => ({
|
|
818
766
|
id: spec.id,
|
|
819
767
|
zOrder: spec.zOrder ?? 0,
|
|
820
768
|
hitTest: spec.hitTest,
|
|
821
|
-
dev: spec.dev === true,
|
|
822
769
|
}));
|
|
823
|
-
const stacked =
|
|
824
|
-
...stackOrder(claimEntries.filter((e) => !e.dev)),
|
|
825
|
-
...stackOrder(claimEntries.filter((e) => e.dev)),
|
|
826
|
-
];
|
|
770
|
+
const stacked = stackOrder(claimEntries);
|
|
827
771
|
const bottomId = stacked[0]?.id;
|
|
828
772
|
const kindById = new Map(mountSpecs.map((spec) => [spec.id, spec.kind] as const));
|
|
829
773
|
|
|
@@ -839,11 +783,6 @@ async function createRootsGameRuntime(config: RootsRuntimeConfig): Promise<GameS
|
|
|
839
783
|
// surface.ts`, which is the one door the capture/staleness/screenshot
|
|
840
784
|
// sites now ask (ARCHITECTURE-CORE §The editor protocol, zero inference).
|
|
841
785
|
surface.dataset['vgaiRootId'] = entry.id;
|
|
842
|
-
// The capture's marker (`composite-screenshot.ts` reads exactly this
|
|
843
|
-
// attribute): a dev layer is excluded from a play capture unless the
|
|
844
|
-
// capture asks for it, because a contaminated "does the game look right"
|
|
845
|
-
// frame is worthless.
|
|
846
|
-
if (entry.dev) surface.dataset['vgaiDevLayer'] = 'true';
|
|
847
786
|
if (!isReact) {
|
|
848
787
|
const canvas = surface as unknown as HTMLCanvasElement;
|
|
849
788
|
canvas.width = w;
|
|
@@ -862,7 +801,7 @@ async function createRootsGameRuntime(config: RootsRuntimeConfig): Promise<GameS
|
|
|
862
801
|
// too (not just in `mountOneThreeRoot`) means every surface, whatever
|
|
863
802
|
// kind, starts container-relative from its very first paint, before any
|
|
864
803
|
// per-kind mount work has even run.
|
|
865
|
-
// `contain:layout paint` makes each surface the
|
|
804
|
+
// `contain:layout paint` makes each surface the containing block for
|
|
866
805
|
// `position:fixed` descendants (and clips overflow to the world's
|
|
867
806
|
// rectangle): full-screen game UI written the natural way (`fixed;
|
|
868
807
|
// inset:0`) then fills the WORLD, not the page. Without it, `fixed` UI
|
|
@@ -936,17 +875,7 @@ async function createRootsGameRuntime(config: RootsRuntimeConfig): Promise<GameS
|
|
|
936
875
|
started = true;
|
|
937
876
|
loop.start();
|
|
938
877
|
|
|
939
|
-
// Expose scene
|
|
940
|
-
// single-world path's identical dev-only exposure above, generalized to
|
|
941
|
-
// the roots path's DEFAULT world (E4): `mountGameFromManifest`/
|
|
942
|
-
// `mountManifestRoots` route every caller (including a
|
|
943
|
-
// single-threejs-world scaffold project) through THIS path, so a caller
|
|
944
|
-
// migrating off the legacy `{canvas, adapter}` call must not silently
|
|
945
|
-
// lose `window.__vgaiScene`/`__vgaiCamera` — real e2e/dev tooling
|
|
946
|
-
// depends on them
|
|
947
|
-
// (`packages/editor/e2e/tests/04-standalone-game.spec.ts`). Split into
|
|
948
|
-
// its own top-level helper (with its retraction counterpart below)
|
|
949
|
-
// purely to keep this function's own cyclomatic complexity down.
|
|
878
|
+
// Expose the default Three root's scene and camera for dev/e2e tooling.
|
|
950
879
|
const retractDevGlobals = installDefaultRootDevGlobals(game);
|
|
951
880
|
|
|
952
881
|
// G3/FT-11 — publish `window.__vgaiRender` for the deterministic capture
|
|
@@ -959,39 +888,49 @@ async function createRootsGameRuntime(config: RootsRuntimeConfig): Promise<GameS
|
|
|
959
888
|
? installSessionRenderHarness(game, renderModeLocation, config.renderControl?.target)
|
|
960
889
|
: undefined;
|
|
961
890
|
|
|
891
|
+
let resolveStopComplete!: () => void;
|
|
892
|
+
const stopComplete = new Promise<void>((resolve) => {
|
|
893
|
+
resolveStopComplete = resolve;
|
|
894
|
+
});
|
|
895
|
+
let stopping = false;
|
|
896
|
+
|
|
962
897
|
function fullCleanup(): void {
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
entry
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
898
|
+
if (stopping) return;
|
|
899
|
+
stopping = true;
|
|
900
|
+
try {
|
|
901
|
+
loop.stop();
|
|
902
|
+
router.dispose();
|
|
903
|
+
for (const entry of mountedEntries) {
|
|
904
|
+
entry.mounted.dispose();
|
|
905
|
+
entry.renderer?.dispose();
|
|
906
|
+
entry.renderer?.forceContextLoss();
|
|
907
|
+
// Remove from the element's CURRENT parent, not the mount-time
|
|
908
|
+
// `container`: the editor's Game panel re-parents the live surfaces
|
|
909
|
+
// when its mount element swaps (fill <-> device preset, W2c), and
|
|
910
|
+
// `container.removeChild` would throw NotFoundError after such a move.
|
|
911
|
+
// `container` remains the fallback for hosts whose element stand-ins
|
|
912
|
+
// never wire `parentNode` (headless unit fixtures); an element already
|
|
913
|
+
// detached by such a host is a no-op via the catch.
|
|
914
|
+
try {
|
|
915
|
+
(entry.element.parentNode ?? container).removeChild(entry.element);
|
|
916
|
+
} catch {
|
|
917
|
+
/* already detached — nothing to remove */
|
|
918
|
+
}
|
|
980
919
|
}
|
|
920
|
+
game.dispose();
|
|
921
|
+
retractDevGlobals();
|
|
922
|
+
retractRenderHarness?.();
|
|
923
|
+
} finally {
|
|
924
|
+
void Promise.allSettled(
|
|
925
|
+
mountedEntries.map((entry) => entry.mounted.disposeComplete ?? Promise.resolve()),
|
|
926
|
+
).then(() => resolveStopComplete());
|
|
981
927
|
}
|
|
982
|
-
game.dispose();
|
|
983
|
-
retractDevGlobals();
|
|
984
|
-
retractRenderHarness?.();
|
|
985
928
|
}
|
|
986
929
|
|
|
987
930
|
return {
|
|
988
931
|
stop: fullCleanup,
|
|
989
|
-
|
|
990
|
-
//
|
|
991
|
-
// a `pausable: false` world (a menu/HUD world) keeps ticking while every
|
|
992
|
-
// other world freezes, which the old blind `for (const entry of
|
|
993
|
-
// mountedEntries) entry.mounted.setPaused?.(true)` fan-out (with no
|
|
994
|
-
// `pausable` check at all) could never express.
|
|
932
|
+
stopComplete,
|
|
933
|
+
// `Game.play` owns per-world pause, loop-gate, and audio-gate semantics.
|
|
995
934
|
pause() {
|
|
996
935
|
game.play.pause();
|
|
997
936
|
},
|
|
@@ -1014,24 +953,6 @@ async function createRootsGameRuntime(config: RootsRuntimeConfig): Promise<GameS
|
|
|
1014
953
|
entry.mounted.resize?.(safeWidth, safeHeight);
|
|
1015
954
|
}
|
|
1016
955
|
},
|
|
1017
|
-
// `GameSession.scene`/`.camera`/`.mounted` alias `Game.defaultRoot` (T6.1
|
|
1018
|
-
// slice 1's "first three world, else first world" rule) — a legacy
|
|
1019
|
-
// convenience shaped for the threejs-only past. T7.5 narrows the read via
|
|
1020
|
-
// the `kind` discriminant instead of a blind `.scene`/`.camera` cast
|
|
1021
|
-
// through a nonexistent property (identical behavior to before: still
|
|
1022
|
-
// `undefined` for a roots-path session whose default world isn't
|
|
1023
|
-
// three — real per-world surface routing for that case is T7.6's).
|
|
1024
|
-
get scene() {
|
|
1025
|
-
const m = game.defaultRoot.mounted;
|
|
1026
|
-
return (m.kind === 'three' ? m.scene : undefined) as THREE.Scene;
|
|
1027
|
-
},
|
|
1028
|
-
get camera() {
|
|
1029
|
-
const m = game.defaultRoot.mounted;
|
|
1030
|
-
return (m.kind === 'three' ? m.camera : undefined) as THREE.PerspectiveCamera;
|
|
1031
|
-
},
|
|
1032
|
-
get mounted() {
|
|
1033
|
-
return game.defaultRoot.mounted;
|
|
1034
|
-
},
|
|
1035
956
|
game,
|
|
1036
957
|
};
|
|
1037
958
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE one owner of "is this a development context". Every reader that has to
|
|
3
|
+
* answer that question calls {@link devBuildEnabled}; there is deliberately NO
|
|
4
|
+
* second source of truth — not a module-level cached boolean, not a
|
|
5
|
+
* `globalThis` flag, not a per-host copy of the `import.meta.env` read below.
|
|
6
|
+
* Instrumentation that ships to players because two places disagreed about
|
|
7
|
+
* what "dev" means is exactly the failure this single owner exists to make
|
|
8
|
+
* impossible.
|
|
9
|
+
*
|
|
10
|
+
* Ownership, stated in one place (the build rule):
|
|
11
|
+
* - OWNER: this function. It resolves the answer; nothing else derives it.
|
|
12
|
+
* - SHARERS: the two three-root adapters (`adapter/setup-three-root-adapter.ts`
|
|
13
|
+
* and `world3d-react/r3f-adapter.tsx`), which seed live render vitals only
|
|
14
|
+
* under it, and the `static-batch` capability's mutation watch and `Frozen`
|
|
15
|
+
* warning. Any future dev-only instrument calls this too, with its own
|
|
16
|
+
* `override`.
|
|
17
|
+
* - TEARDOWN: none. This is a pure predicate over build config and one
|
|
18
|
+
* caller-supplied argument — it owns no resource, allocates nothing, and
|
|
19
|
+
* has no lifecycle to end.
|
|
20
|
+
*
|
|
21
|
+
* The three inputs, highest precedence first:
|
|
22
|
+
* 1. `override` — the explicit per-call answer. A headless test, a capture
|
|
23
|
+
* harness, or a host that knows better passes `true`/`false` and gets
|
|
24
|
+
* exactly that. Passing `undefined` (or omitting it) means "decide for
|
|
25
|
+
* me" and falls through. The editor's own preview mount is the worked
|
|
26
|
+
* case: it is a dev session by definition even when the editor SPA it
|
|
27
|
+
* runs inside is a production build.
|
|
28
|
+
* 2. A dev build — `import.meta.env.DEV`. The ordinary local/editor case.
|
|
29
|
+
* 3. A production build's EXPLICIT opt-in — `VITE_VGAI_DEV_BUILD=true`.
|
|
30
|
+
* Instrumenting a production bundle is a real, legitimate choice (an
|
|
31
|
+
* internal playtest build, a QA build), and it must be an opt-in someone
|
|
32
|
+
* had to type, never something a default drifts into.
|
|
33
|
+
*
|
|
34
|
+
* Anything else — a production build with no opt-in — is `false`.
|
|
35
|
+
*/
|
|
36
|
+
export function devBuildEnabled(override?: boolean | undefined): boolean {
|
|
37
|
+
if (override !== undefined) return override;
|
|
38
|
+
// `import.meta` is cast whole, not just its `.env`: this module is reachable
|
|
39
|
+
// from programs whose tsconfig does not pull in `vite/client` (the session
|
|
40
|
+
// client's, for one, which reaches the three adapter transitively), and there
|
|
41
|
+
// `ImportMeta` has no declared `env` at all. The cast keeps the single owner
|
|
42
|
+
// of the dev answer importable from ANY program rather than forcing every
|
|
43
|
+
// downstream tsconfig to adopt Vite's ambient types.
|
|
44
|
+
const env = (import.meta as unknown as { env?: unknown }).env as
|
|
45
|
+
| { DEV?: boolean | undefined; VITE_VGAI_DEV_BUILD?: string | undefined }
|
|
46
|
+
| undefined;
|
|
47
|
+
if (env?.DEV === true) return true;
|
|
48
|
+
return env?.VITE_VGAI_DEV_BUILD === 'true';
|
|
49
|
+
}
|