@vgai/engine 0.5.14 → 0.5.16
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 +228 -17
- package/dist/adapter/adapter-module.d.ts.map +1 -1
- package/dist/adapter/adapter-module.js +224 -42
- 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 -1
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
- package/dist/adapter/finders/scenes-from-entrypoint-selection.js +142 -21
- 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 +21 -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 +124 -0
- package/dist/adapter/root-seam-contract.d.ts.map +1 -0
- package/dist/adapter/root-seam-contract.js +36 -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 +2 -3
- 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 +43 -29
- 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/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 +49 -0
- package/dist/input/input-manager.d.ts.map +1 -1
- package/dist/input/input-manager.js +127 -0
- package/dist/manifest/schema.d.ts +9 -9
- package/dist/manifest/schema.js +1 -1
- package/dist/pixi/authoring.d.ts.map +1 -1
- package/dist/pixi/authoring.js +220 -1
- 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/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/runtime/create-runtime.d.ts +23 -46
- package/dist/runtime/create-runtime.d.ts.map +1 -1
- package/dist/runtime/create-runtime.js +22 -67
- package/dist/runtime/game.d.ts +29 -71
- package/dist/runtime/game.d.ts.map +1 -1
- package/dist/runtime/game.js +15 -54
- 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.map +1 -1
- package/dist/runtime/mount-manifest.js +4 -5
- 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.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 +50 -21
- package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
- package/dist/world3d-react/r3f-root-factory.js +4 -0
- package/dist/world3d-react/world-context.d.ts +9 -3
- package/dist/world3d-react/world-context.d.ts.map +1 -1
- package/dist/world3d-react/world-context.js +5 -18
- package/package.json +1 -1
- package/schemas/engine-capabilities.json +8 -8
- package/schemas/vgai-project.schema.json +1 -1
- package/src/adapter/adapter-module.ts +407 -50
- 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 +159 -28
- 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 +21 -56
- package/src/adapter/root-seam-contract.ts +56 -0
- package/src/adapter/seam-evidence.ts +336 -0
- package/src/adapter/setup-three-root-adapter.ts +4 -6
- 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 +55 -34
- package/src/canvas-react/pixi-react-root-factory.tsx +4 -0
- package/src/canvas-react/world-context.ts +26 -101
- package/src/ecs/user-data.ts +5 -0
- package/src/input/input-manager.ts +122 -0
- package/src/manifest/schema.ts +1 -1
- package/src/pixi/authoring.ts +236 -1
- package/src/pixi/ingest.ts +9 -2
- package/src/pixi/scene-capture.ts +20 -5
- package/src/render/particles-factory.ts +16 -6
- package/src/runtime/create-runtime.ts +41 -106
- package/src/runtime/game.ts +41 -111
- package/src/runtime/mount-game.ts +19 -1
- package/src/runtime/mount-manifest.ts +4 -5
- package/src/runtime/types.ts +22 -0
- package/src/setup/setup-renderer.ts +3 -4
- package/src/world3d-react/engine-bridge.ts +1 -1
- package/src/world3d-react/r3f-adapter.tsx +64 -24
- package/src/world3d-react/r3f-root-factory.tsx +4 -0
- package/src/world3d-react/world-context.ts +11 -36
package/src/runtime/game.ts
CHANGED
|
@@ -1,25 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Game root
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* instead of driving its own loop, and in T7.3 slice 2 with a
|
|
7
|
-
* construction-time stage check (symmetric with the three/scene check).
|
|
8
|
-
* T6.2 slice 1 adds real `'react'`-kind
|
|
9
|
-
* `RootInstance` support (`container`, a real `reactRoot()`) — the DOM-root
|
|
10
|
-
* world surface `runtime/create-runtime.ts`'s roots path now mounts, with
|
|
11
|
-
* `physics`/`collisions`/`camera`/`frame` `undefined` for them exactly
|
|
12
|
-
* like an opaque/foreign mount.
|
|
13
|
-
*
|
|
14
|
-
* (D6), particularly §4 (the frame algorithm) and §8 stage 2, for the full
|
|
15
|
-
* target shape. This file holds the Game/ RootInstance shell, the NEW
|
|
16
|
-
* game-scoped `SystemRunner`, and the host-facing `runFrame` frame executor.
|
|
17
|
-
* The public surface is NOT frozen yet — do not add these exports to a
|
|
18
|
-
* barrel/index; only the in-scope runtime/adapter files import this module
|
|
19
|
-
* directly. The `gpu` per-world cache is the one remaining slice-1 omission.
|
|
2
|
+
* The surface-neutral Game root and its declaration-ordered root registry.
|
|
3
|
+
* Three, Canvas, and DOM roots share one game-scoped loop, input manager,
|
|
4
|
+
* system runner, state bridge, debug registry, clock, and seeded RNG. Each
|
|
5
|
+
* RootInstance retains its own mounted surface and optional capabilities.
|
|
20
6
|
*/
|
|
21
7
|
|
|
22
|
-
// TYPE-ONLY pixi import
|
|
8
|
+
// TYPE-ONLY pixi import: `game.ts` must never value-import
|
|
23
9
|
// `pixi.js` — a value import would pull the Pixi runtime into 3D-only
|
|
24
10
|
// bundles that never touch a canvas root. Every pixi-typed field below is erased
|
|
25
11
|
// at compile time; nothing here constructs or calls into Pixi.
|
|
@@ -48,7 +34,6 @@ import type { PhysicsRegistry } from '../physics/physics-registry';
|
|
|
48
34
|
// TYPE-ONLY (same rule as the pixi import above): this lives under `pixi/`,
|
|
49
35
|
// but `game.ts` only ever names its TYPE.
|
|
50
36
|
import type { Physics2DRegistry } from '../pixi/physics-registry';
|
|
51
|
-
import type { AudioContext as GameAudio } from '../setup/setup-audio';
|
|
52
37
|
import {
|
|
53
38
|
createDebugRegistry,
|
|
54
39
|
DebugError,
|
|
@@ -89,10 +74,7 @@ const DISPLAY_RATE_PHASES: readonly SystemPhaseName[] = PHASE_ORDER.filter(
|
|
|
89
74
|
* `runFrame`. `GameLoop.timeScale` (unaffected by this) remains the orthogonal
|
|
90
75
|
* "speed up/slow down" axis — pausing never touches it, so the host's
|
|
91
76
|
* accumulator/rAF loop keeps ticking at its normal cadence, which is what makes
|
|
92
|
-
* "paused still renders" possible
|
|
93
|
-
* implementation set `loop.timeScale = 0`, which starves the accumulator — and,
|
|
94
|
-
* before WO-P13, stopped rendering with it; that was the bug this control
|
|
95
|
-
* surface replaces, not a compatible behavior to keep).
|
|
77
|
+
* "paused still renders" possible.
|
|
96
78
|
*/
|
|
97
79
|
export interface PlayState {
|
|
98
80
|
/** Whether the game is currently paused (game-level — see the per-world
|
|
@@ -131,11 +113,9 @@ export interface PlayState {
|
|
|
131
113
|
|
|
132
114
|
/**
|
|
133
115
|
* The kinds of render surface a world can be. `'three'` and `'canvas'`
|
|
134
|
-
* roots are
|
|
135
|
-
* here so the type is already shaped for T7.4 and no caller has to widen a
|
|
136
|
-
* union later.
|
|
116
|
+
* roots are canvas-backed; `'dom'` roots own a DOM layer.
|
|
137
117
|
*
|
|
138
|
-
* Re-exported from `adapter/adapter-surface.ts`
|
|
118
|
+
* Re-exported from `adapter/adapter-surface.ts` so
|
|
139
119
|
* `adapter/root-adapter.ts`'s kind-tagged `MountedRoot` types can name it
|
|
140
120
|
* without an import cycle back to this file. This re-export keeps every
|
|
141
121
|
* existing `import type { AdapterSurface } from '../runtime/game'` call site
|
|
@@ -144,7 +124,7 @@ export interface PlayState {
|
|
|
144
124
|
export type AdapterSurface = AdapterSurfaceLeaf;
|
|
145
125
|
|
|
146
126
|
/**
|
|
147
|
-
* A world's per-phase frame hooks
|
|
127
|
+
* A world's per-phase frame hooks.
|
|
148
128
|
* Populated on a `RootInstance` only for first-party mounts — an opaque/
|
|
149
129
|
* foreign mount has no phase-partitioned entry point, so it stays
|
|
150
130
|
* `undefined` and the Game's frame executor (`GameInternal.runFrame`) falls
|
|
@@ -153,7 +133,7 @@ export type AdapterSurface = AdapterSurfaceLeaf;
|
|
|
153
133
|
export interface RootFrameHooks {
|
|
154
134
|
/** Run this world's engine systems + component ticks + world-bound game
|
|
155
135
|
* systems for one phase. For a first-party world this delegates to the
|
|
156
|
-
*
|
|
136
|
+
* same `SystemRunner.runPhase` as its direct `mounted.update` entry. */
|
|
157
137
|
runPhase(phase: SystemPhaseName, dt: number): void;
|
|
158
138
|
/** Run once per substep, after ALL phases have run for ALL roots this
|
|
159
139
|
* substep (mirrors where `mounted.update`'s post-`systems.run` work sat
|
|
@@ -164,14 +144,12 @@ export interface RootFrameHooks {
|
|
|
164
144
|
|
|
165
145
|
/**
|
|
166
146
|
* A single world instance: the unit of adaptation.
|
|
167
|
-
* Slice-1 subset — `gpu` (per-world GPU resource cache, §6.2) is omitted
|
|
168
|
-
* until the slice that builds it.
|
|
169
147
|
*/
|
|
170
148
|
export interface RootInstance {
|
|
171
|
-
/** Manifest id
|
|
149
|
+
/** Manifest root id. */
|
|
172
150
|
readonly id: string;
|
|
173
151
|
readonly kind: AdapterSurface;
|
|
174
|
-
/** Per-world play/pause semantics
|
|
152
|
+
/** Per-world play/pause semantics. */
|
|
175
153
|
readonly pausable: boolean;
|
|
176
154
|
/** The RootAdapter that produced `mounted` — first-party or external.
|
|
177
155
|
* Deliberately narrower than `RootAdapter<K>` (T7.5): this field is only
|
|
@@ -195,7 +173,7 @@ export interface RootInstance {
|
|
|
195
173
|
* descriptively for a non-pixijs world, or a canvas world built without
|
|
196
174
|
* a `stage` (see `RootInstanceInit.stage`). */
|
|
197
175
|
pixiStage(): PIXI.Container;
|
|
198
|
-
/** Kind-narrowed accessor for
|
|
176
|
+
/** Kind-narrowed accessor for DOM roots: returns the
|
|
199
177
|
* DOM-root layer `<div>` the host mounted this world's react tree into
|
|
200
178
|
* (the SAME element passed as `container` to `createRootInstance` —
|
|
201
179
|
* identity matters, mirroring `threeScene()`/`pixiStage()`'s "same
|
|
@@ -214,7 +192,7 @@ export interface RootInstance {
|
|
|
214
192
|
readonly physics2d?: Physics2DRegistry | undefined;
|
|
215
193
|
/** Kind-typed via `mounted` in T7.5; `unknown` here deliberately. */
|
|
216
194
|
readonly camera?: unknown;
|
|
217
|
-
/** Phase-partitioned frame entry point
|
|
195
|
+
/** Phase-partitioned frame entry point — present only for
|
|
218
196
|
* first-party mounts. `undefined` for an opaque/foreign mount, which
|
|
219
197
|
* `GameInternal.runFrame` drives via its single `mounted.update` call
|
|
220
198
|
* instead (unless it `drivesOwnLoop`, in which case it isn't ticked at
|
|
@@ -399,8 +377,8 @@ export function createRootInstance(init: RootInstanceInit): RootInstance {
|
|
|
399
377
|
/**
|
|
400
378
|
* Type guard for whether a `MountedThreeRoot` is a first-party
|
|
401
379
|
* `SetupThreeRootAdapter` mount (has a live `GameContext` at `.ctx`). Used to
|
|
402
|
-
* decide whether a world's `physics`/`collisions`/`camera`
|
|
403
|
-
*
|
|
380
|
+
* decide whether a world's `physics`/`collisions`/`camera` can be populated
|
|
381
|
+
* from it — an external
|
|
404
382
|
* adapter's mount has none of these first-party handles.
|
|
405
383
|
*
|
|
406
384
|
* Checks the `firstParty: true` brand (checklist item 1), NOT `'ctx' in
|
|
@@ -505,13 +483,9 @@ function debrisOwnersOf(world: RootInstance): DebrisOwner[] {
|
|
|
505
483
|
}
|
|
506
484
|
|
|
507
485
|
/**
|
|
508
|
-
* The Game root. Owns the
|
|
509
|
-
*
|
|
510
|
-
*
|
|
511
|
-
* additions: they delegate to the default world's first-party mount so
|
|
512
|
-
* existing single-world call sites keep working; hoisting them to true
|
|
513
|
-
* Game ownership (the target shape) is a later slice's
|
|
514
|
-
* work, not this one's.
|
|
486
|
+
* The Game root. Owns the loop, raw-asset cache, world registry, input,
|
|
487
|
+
* and game-scoped `SystemRunner`. Surface-specific capabilities remain on
|
|
488
|
+
* their mounted roots or in the aggregated `SystemAdapters` contract.
|
|
515
489
|
*/
|
|
516
490
|
export interface Game {
|
|
517
491
|
readonly loop: GameLoop;
|
|
@@ -521,18 +495,16 @@ export interface Game {
|
|
|
521
495
|
/** Per-game diagnostic store. Disabled by default; the editor enables it on demand. */
|
|
522
496
|
readonly profiler: PerformanceProfiler;
|
|
523
497
|
/**
|
|
524
|
-
* The game-scoped `SystemRunner
|
|
525
|
-
*
|
|
498
|
+
* The game-scoped `SystemRunner`, separate from any world's own runner.
|
|
499
|
+
* Within each phase,
|
|
526
500
|
* `GameInternal.runFrame` runs THIS runner's `runPhase` first, before any
|
|
527
501
|
* world's engine systems/component ticks/world-bound game systems (e.g.
|
|
528
502
|
* `ctx.systems.add`, which stays world-bound to the default world — see
|
|
529
503
|
* `runtime/types.ts`). Empty for every existing game (nothing registers
|
|
530
|
-
* against it
|
|
531
|
-
* unchanged by its presence.
|
|
504
|
+
* against it), so an empty runner has no frame cost beyond dispatch.
|
|
532
505
|
*/
|
|
533
506
|
readonly systems: SystemRunner;
|
|
534
|
-
/** Declaration-ordered.
|
|
535
|
-
* three world) — this is the SAME array reference `registerRoot`
|
|
507
|
+
/** Declaration-ordered. This is the same array reference `registerRoot`
|
|
536
508
|
* mutates, not a snapshot, so holders (e.g. `GameContext.roots`) observe
|
|
537
509
|
* later registrations. */
|
|
538
510
|
readonly roots: ReadonlyArray<RootInstance>;
|
|
@@ -568,9 +540,6 @@ export interface Game {
|
|
|
568
540
|
subscribeSystemAdapters?(listener: () => void): () => void;
|
|
569
541
|
/** The one game-owned `InputManager`, shared by every first-party root. */
|
|
570
542
|
readonly input: InputManager;
|
|
571
|
-
/** Delegates to the default world's first-party audio context. Throws
|
|
572
|
-
* when the default world is not a first-party mount. */
|
|
573
|
-
readonly audio: GameAudio;
|
|
574
543
|
/**
|
|
575
544
|
* Frame-versioned state bridge (T7.4 slice 1). Bumped once per completed
|
|
576
545
|
* `runFrame`, after all phases of all roots and all `endFrame` hooks (see
|
|
@@ -653,28 +622,18 @@ export interface GameInternal extends Game {
|
|
|
653
622
|
* else: world.mounted.update?.(dt) // opaque world fallback
|
|
654
623
|
* ```
|
|
655
624
|
*
|
|
656
|
-
*
|
|
657
|
-
*
|
|
658
|
-
*
|
|
659
|
-
* `update`
|
|
660
|
-
* `drivesOwnLoop` world is never ticked here at all — matching its
|
|
661
|
-
* exclusion from the legacy `!mountedRef.drivesOwnLoop` guard. An opaque
|
|
662
|
-
* host-driven world (no `frame`) gets exactly one `update(dt)` call per
|
|
663
|
-
* substep, after the phase loop — unchanged cadence from today.
|
|
625
|
+
* A single first-party world and its direct `mounted.update(dt)` entry run
|
|
626
|
+
* the same `SystemRunner` and `postFrame` work. A `drivesOwnLoop` world is
|
|
627
|
+
* never ticked here. An opaque host-driven world (no `frame`) gets exactly
|
|
628
|
+
* one `update(dt)` call per substep, after the phase loop.
|
|
664
629
|
*
|
|
665
630
|
* D10/T7.6 play-state addendum: when `Game.play.paused` is true, every
|
|
666
631
|
* `pausable` (and non-`drivesOwnLoop`) world skips every phase EXCEPT
|
|
667
632
|
* `render` (still called, every substep, with `dt` forced to `0`) and skips
|
|
668
633
|
* its `endFrame`/opaque-`update` call entirely — a `pausable: false` world
|
|
669
|
-
* is completely unaffected. `
|
|
670
|
-
*
|
|
671
|
-
*
|
|
672
|
-
* none) but is no longer how `Game.play.step()` works (§7.1-2 fix,
|
|
673
|
-
* probe4: the old `ignorePause` full-frame re-run double-ticked every
|
|
674
|
-
* already-running `pausable: false` world, since D10's ordinary loop never
|
|
675
|
-
* stops ticking them). `Game.play.step()` now drives this function via the
|
|
676
|
-
* internal-only `onlyFrozen` mode instead (see `runFrameImpl` — not part of
|
|
677
|
-
* this public, host-facing signature): it ticks EXACTLY the currently
|
|
634
|
+
* is completely unaffected. `Game.play.step()` drives this function via the
|
|
635
|
+
* internal-only `onlyFrozen` mode (see `runFrameImpl` — not part of this
|
|
636
|
+
* public, host-facing signature): it ticks exactly the currently
|
|
678
637
|
* frozen set (host-driven, `pausable`, and `paused`) through every phase +
|
|
679
638
|
* `endFrame` with the real `dt` (not the render-phase's forced `0`), and
|
|
680
639
|
* touches no other world at all — a natural no-op while not paused, since
|
|
@@ -690,7 +649,7 @@ export interface GameInternal extends Game {
|
|
|
690
649
|
* calls `runFrame(fixedDt)` with no opts) is frame-exact and unchanged.
|
|
691
650
|
* `runTicks` sets it per tick for its `render: 'none' | 'last'` modes.
|
|
692
651
|
*/
|
|
693
|
-
runFrame(dt: number, opts?: {
|
|
652
|
+
runFrame(dt: number, opts?: { skipRenderPhases?: boolean }): void;
|
|
694
653
|
/**
|
|
695
654
|
* WO-P13 — run ONE display frame's presentation pass: the registered
|
|
696
655
|
* `onRenderStep` callbacks, then the `preRender` and `render` phases across
|
|
@@ -781,14 +740,6 @@ export interface GameInternal extends Game {
|
|
|
781
740
|
dispose(): void;
|
|
782
741
|
}
|
|
783
742
|
|
|
784
|
-
function describeMismatch(handle: 'input' | 'audio', world: RootInstance): string {
|
|
785
|
-
return (
|
|
786
|
-
`Game.${handle}: default world "${world.id}" (kind: ${world.kind}) is not a first-party ` +
|
|
787
|
-
"mount — hoisting these to true Game ownership is a later slice's work; available only " +
|
|
788
|
-
'via a first-party default world today'
|
|
789
|
-
);
|
|
790
|
-
}
|
|
791
|
-
|
|
792
743
|
/**
|
|
793
744
|
* Construct the (host-internal) Game shell. Callers: `createGameRuntime`
|
|
794
745
|
* builds this BEFORE mounting its one adapter, then registers the default
|
|
@@ -879,7 +830,7 @@ export function createGame(opts: {
|
|
|
879
830
|
getTick: () => tick,
|
|
880
831
|
getSimT: () => simT,
|
|
881
832
|
// D15/T-D15.5 — "the manifest's first/default world" for the debug
|
|
882
|
-
// registry's world-addressed input-target
|
|
833
|
+
// registry's world-addressed input-target surface. Every
|
|
883
834
|
// first-party root now registers the SAME game-owned InputManager, but the
|
|
884
835
|
// stable default id still keeps explicit/implicit debug routing coherent.
|
|
885
836
|
// Use the SAME "first three world, else first world" rule
|
|
@@ -913,14 +864,6 @@ export function createGame(opts: {
|
|
|
913
864
|
return roots.find((w) => w.kind === 'three') ?? roots[0]!;
|
|
914
865
|
}
|
|
915
866
|
|
|
916
|
-
function requireFirstPartyCtx(handle: 'input' | 'audio') {
|
|
917
|
-
const world = requireDefaultRoot();
|
|
918
|
-
if (!isFirstPartyMounted(world.mounted)) {
|
|
919
|
-
throw new Error(describeMismatch(handle, world));
|
|
920
|
-
}
|
|
921
|
-
return world.mounted.ctx;
|
|
922
|
-
}
|
|
923
|
-
|
|
924
867
|
// --- Game.systemAdapters aggregation (§7.1-3, probe1) --------------------
|
|
925
868
|
// Warn-once-per-colliding-key state, scoped to this Game instance (a fresh
|
|
926
869
|
// Game gets a fresh warn history) — deliberately NOT reset by anything
|
|
@@ -1023,7 +966,7 @@ export function createGame(opts: {
|
|
|
1023
966
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: the frame algorithm (now with D10's per-world pause gate + the onlyFrozen step()-only mode) is one cohesive nested loop over phases/roots — splitting it would obscure the ordering contract documented on GameInternal.runFrame
|
|
1024
967
|
function runFrameImpl(
|
|
1025
968
|
dt: number,
|
|
1026
|
-
frameOpts?: {
|
|
969
|
+
frameOpts?: { onlyFrozen?: boolean; skipRenderPhases?: boolean },
|
|
1027
970
|
): void {
|
|
1028
971
|
// D15 (T-D15.3) — brackets the ENTIRE frame body (every phase, every
|
|
1029
972
|
// world, both the `onlyFrozen` and normal branches below converge on the
|
|
@@ -1033,7 +976,6 @@ export function createGame(opts: {
|
|
|
1033
976
|
// declare `determinism.seededRandom`).
|
|
1034
977
|
if (rngTrapEnabled) rngTrap.enable();
|
|
1035
978
|
profiler.beginFrame();
|
|
1036
|
-
const ignorePause = frameOpts?.ignorePause ?? false;
|
|
1037
979
|
// D15/T-D15.4 + WO-P13: the render-phase skip. Two callers set it, for the
|
|
1038
980
|
// same reason — this substep is not the thing that paints. `runTicks`'s
|
|
1039
981
|
// `render: 'none'|'last'` fast-forward sets it on every tick it doesn't
|
|
@@ -1048,9 +990,6 @@ export function createGame(opts: {
|
|
|
1048
990
|
// one and only caller. It ticks EXACTLY the currently-frozen set (host-driven,
|
|
1049
991
|
// `pausable`, and `paused`) through every phase + `endFrame`, with the REAL
|
|
1050
992
|
// `dt` (not the render-phase's forced `0`), and touches no other world at all
|
|
1051
|
-
// — §7.1-2 fix, probe4: the previous `ignorePause` full-frame re-run ticked
|
|
1052
|
-
// EVERY host-driven world, double-ticking every already-running
|
|
1053
|
-
// `pausable: false` world (D10's ordinary loop never stops ticking them).
|
|
1054
993
|
const onlyFrozen = frameOpts?.onlyFrozen ?? false;
|
|
1055
994
|
// Checklist item 7: snapshot the world count ONCE at entry and iterate
|
|
1056
995
|
// by index in both loops below. A world registered mid-frame (e.g. from
|
|
@@ -1068,10 +1007,9 @@ export function createGame(opts: {
|
|
|
1068
1007
|
break;
|
|
1069
1008
|
}
|
|
1070
1009
|
} else if (
|
|
1071
|
-
|
|
1072
|
-
(world.mounted.drivesOwnLoop
|
|
1010
|
+
world.mounted.drivesOwnLoop
|
|
1073
1011
|
? !paused || !world.pausable || !world.mounted.setPaused
|
|
1074
|
-
: !paused || !world.pausable
|
|
1012
|
+
: !paused || !world.pausable
|
|
1075
1013
|
) {
|
|
1076
1014
|
inputFrameActive = true;
|
|
1077
1015
|
break;
|
|
@@ -1169,13 +1107,12 @@ export function createGame(opts: {
|
|
|
1169
1107
|
for (let i = 0; i < n; i++) {
|
|
1170
1108
|
const world = roots[i]!;
|
|
1171
1109
|
if (world.mounted.drivesOwnLoop) continue;
|
|
1172
|
-
// D10/T7.6: a `pausable` world under an active
|
|
1110
|
+
// D10/T7.6: a `pausable` world under an active pause
|
|
1173
1111
|
// skips every phase except `render` — its render still runs, every
|
|
1174
1112
|
// substep, but with `dt` forced to `0` (deterministic: no
|
|
1175
1113
|
// time-based render effect silently keeps animating a "frozen"
|
|
1176
|
-
// scene). A `pausable: false` world
|
|
1177
|
-
|
|
1178
|
-
const frozen = !ignorePause && paused && world.pausable;
|
|
1114
|
+
// scene). A `pausable: false` world is unaffected.
|
|
1115
|
+
const frozen = paused && world.pausable;
|
|
1179
1116
|
if (frozen && phase !== SystemPhase.RENDER) continue;
|
|
1180
1117
|
const phaseDt = frozen ? 0 : dt;
|
|
1181
1118
|
// Checklist item 2: isolate each world's per-phase work — one
|
|
@@ -1199,7 +1136,7 @@ export function createGame(opts: {
|
|
|
1199
1136
|
if (world.mounted.drivesOwnLoop) continue;
|
|
1200
1137
|
// A fully-frozen world gets no `endFrame`/opaque-`update` call either —
|
|
1201
1138
|
// there is nothing to "end the frame" of when nothing ran this substep.
|
|
1202
|
-
const frozen =
|
|
1139
|
+
const frozen = paused && world.pausable;
|
|
1203
1140
|
if (frozen) continue;
|
|
1204
1141
|
advanced = true;
|
|
1205
1142
|
if (world.frame) {
|
|
@@ -1346,9 +1283,6 @@ export function createGame(opts: {
|
|
|
1346
1283
|
for (const listener of systemAdapterListeners) listener();
|
|
1347
1284
|
},
|
|
1348
1285
|
input,
|
|
1349
|
-
get audio() {
|
|
1350
|
-
return requireFirstPartyCtx('audio').audio;
|
|
1351
|
-
},
|
|
1352
1286
|
state: stateBridge,
|
|
1353
1287
|
play: {
|
|
1354
1288
|
get paused() {
|
|
@@ -1387,11 +1321,7 @@ export function createGame(opts: {
|
|
|
1387
1321
|
}
|
|
1388
1322
|
}
|
|
1389
1323
|
}
|
|
1390
|
-
//
|
|
1391
|
-
// paused) set — not `{ ignorePause: true }`, which re-ran a FULL
|
|
1392
|
-
// extra frame for every host-driven world (including already-running
|
|
1393
|
-
// `pausable: false` ones — a double-tick, since D10's ordinary loop
|
|
1394
|
-
// never stops ticking them). See `runFrameImpl`'s `onlyFrozen` mode.
|
|
1324
|
+
// Tick exactly the frozen (host-driven, pausable, paused) set.
|
|
1395
1325
|
runFrameImpl(dt, { onlyFrozen: true });
|
|
1396
1326
|
},
|
|
1397
1327
|
},
|
|
@@ -1486,7 +1416,7 @@ export function createGame(opts: {
|
|
|
1486
1416
|
);
|
|
1487
1417
|
}
|
|
1488
1418
|
},
|
|
1489
|
-
runFrame(dt: number, frameOpts?: {
|
|
1419
|
+
runFrame(dt: number, frameOpts?: { skipRenderPhases?: boolean }): void {
|
|
1490
1420
|
runFrameImpl(dt, frameOpts);
|
|
1491
1421
|
},
|
|
1492
1422
|
runRenderFrame(alpha: number, displayDt: number): void {
|
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
// even the 'three' convenience export is a plain function the CALLER
|
|
18
18
|
// registers, not something this module wires up automatically.
|
|
19
19
|
|
|
20
|
+
import {
|
|
21
|
+
installNativeDebugBindings,
|
|
22
|
+
type NativeDebugBinding,
|
|
23
|
+
nativeDebugBindingFromEntryModule,
|
|
24
|
+
} from '../adapter/native-debug-module';
|
|
20
25
|
import type { RootAdapter } from '../adapter/root-adapter';
|
|
21
26
|
import type { ResolvedAdapterRoot } from '../manifest/load';
|
|
22
27
|
import type { GameSession } from './create-runtime';
|
|
@@ -209,6 +214,15 @@ function worldIsSelfSufficient(_world: ResolvedAdapterRoot): boolean {
|
|
|
209
214
|
return false;
|
|
210
215
|
}
|
|
211
216
|
|
|
217
|
+
function nativeDebugForEntry(
|
|
218
|
+
world: ResolvedAdapterRoot,
|
|
219
|
+
entryModule: unknown,
|
|
220
|
+
): NativeDebugBinding[] {
|
|
221
|
+
if (entryModule === undefined || world.adapter.type !== 'builtin') return [];
|
|
222
|
+
const binding = nativeDebugBindingFromEntryModule(world.id, entryModule);
|
|
223
|
+
return binding ? [binding] : [];
|
|
224
|
+
}
|
|
225
|
+
|
|
212
226
|
/**
|
|
213
227
|
* Mount every world declared by a `vgai.project.json` manifest onto `host
|
|
214
228
|
* .container`, resolving each world's kind through the `registerAdapter`
|
|
@@ -240,6 +254,7 @@ export async function mountGameFromManifest(
|
|
|
240
254
|
assertExportedOrInEditor();
|
|
241
255
|
const manifest = resolveManifest(manifestInput);
|
|
242
256
|
const entries: Record<string, MountEntry> = { ...opts.entries };
|
|
257
|
+
const nativeDebug: NativeDebugBinding[] = [];
|
|
243
258
|
|
|
244
259
|
for (const world of manifest.roots) {
|
|
245
260
|
if (entries[world.id] !== undefined) continue; // explicit entry always wins
|
|
@@ -250,6 +265,7 @@ export async function mountGameFromManifest(
|
|
|
250
265
|
world.entry !== undefined && host.loadEntryModule
|
|
251
266
|
? await host.loadEntryModule(world.entry)
|
|
252
267
|
: undefined;
|
|
268
|
+
nativeDebug.push(...nativeDebugForEntry(world, entryModule));
|
|
253
269
|
entries[world.id] = await factory(world, { host, entryModule });
|
|
254
270
|
continue;
|
|
255
271
|
}
|
|
@@ -265,7 +281,7 @@ export async function mountGameFromManifest(
|
|
|
265
281
|
);
|
|
266
282
|
}
|
|
267
283
|
|
|
268
|
-
|
|
284
|
+
const session = await mountManifestRoots({
|
|
269
285
|
manifest,
|
|
270
286
|
container: host.container,
|
|
271
287
|
entries,
|
|
@@ -273,4 +289,6 @@ export async function mountGameFromManifest(
|
|
|
273
289
|
height: host.height,
|
|
274
290
|
headless: host.headless,
|
|
275
291
|
});
|
|
292
|
+
installNativeDebugBindings(session.game, nativeDebug);
|
|
293
|
+
return session;
|
|
276
294
|
}
|
|
@@ -20,8 +20,7 @@
|
|
|
20
20
|
// `entries[id].adapter` (the caller's own module graph built it). Threejs is
|
|
21
21
|
// different: `three`/
|
|
22
22
|
// `SetupThreeRootAdapter` are already unconditional dependencies of every
|
|
23
|
-
// caller of `createGameRuntime
|
|
24
|
-
// immediately below in this directory), so wrapping a `setup` with
|
|
23
|
+
// caller of `createGameRuntime`, so wrapping a `setup` with
|
|
25
24
|
// `fromSetup` here adds no NEW bundle weight — only pixi/react are avoided.
|
|
26
25
|
|
|
27
26
|
import { assertNever } from '../adapter/adapter-surface';
|
|
@@ -275,8 +274,9 @@ function resolveThreeAdapter(
|
|
|
275
274
|
if (world.adapter.type === 'ingest') {
|
|
276
275
|
throw new Error(
|
|
277
276
|
`mountManifestRoots: world "${world.id}" (three) declares an { ingest } adapter — ` +
|
|
278
|
-
"ingest roots require the editor's dev-server-backed mount machinery (an EditorStore " +
|
|
279
|
-
|
|
277
|
+
"captured ingest roots require the editor's dev-server-backed mount machinery (an EditorStore " +
|
|
278
|
+
'plus in-realm scene capture, see resolveIngestDescriptor + ' +
|
|
279
|
+
'mountThreeIngestRootFromManifest) and are ' +
|
|
280
280
|
'not supported by mountManifestRoots (no porting aids, no hosted ingest routes).',
|
|
281
281
|
);
|
|
282
282
|
}
|
|
@@ -347,7 +347,6 @@ function buildRootMountSpec(
|
|
|
347
347
|
zOrder: world.zOrder,
|
|
348
348
|
pausable: world.pausable,
|
|
349
349
|
dev: world.dev,
|
|
350
|
-
loop: world.loop,
|
|
351
350
|
};
|
|
352
351
|
|
|
353
352
|
// `entry`'s `kind` is guaranteed to agree with `world.kind` past the guard
|
package/src/runtime/types.ts
CHANGED
|
@@ -259,6 +259,28 @@ export interface GameContext {
|
|
|
259
259
|
playtest?: PlaytestContext | null | undefined;
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Services shared by component-authored roots, independent of their render
|
|
264
|
+
* surface. This is deliberately an explicit pick from {@link GameContext}:
|
|
265
|
+
* adding a subsystem to the imperative Three setup context never silently
|
|
266
|
+
* exposes it to R3F or Canvas components.
|
|
267
|
+
*
|
|
268
|
+
* `input` is nullable because it belongs to the optional `Game` host. Normal
|
|
269
|
+
* vgai project mounts always provide it; bare harnesses and foreign hosts do
|
|
270
|
+
* not. Component code therefore sees the absence in its type instead of a
|
|
271
|
+
* getter that first throws from inside a frame callback.
|
|
272
|
+
*
|
|
273
|
+
* `clock` is surface-specific and intentionally absent here. The imperative
|
|
274
|
+
* and R3F clocks can dispose `THREE.Object3D` debris; a Canvas context narrows
|
|
275
|
+
* that Three-only operation away.
|
|
276
|
+
*/
|
|
277
|
+
export type ComponentContextBase = Pick<
|
|
278
|
+
GameContext,
|
|
279
|
+
'systems' | 'debug' | 'random' | 'game' | 'roots' | 'playtest' | 'registerSystemAdapter'
|
|
280
|
+
> & {
|
|
281
|
+
readonly input: InputManager | null;
|
|
282
|
+
};
|
|
283
|
+
|
|
262
284
|
/** Return value from a game setup function. */
|
|
263
285
|
export interface GameCleanup {
|
|
264
286
|
dispose: () => void;
|
|
@@ -79,10 +79,9 @@ export function applyRendererSettings(
|
|
|
79
79
|
* reload-only `antialias` option at construction and the live shadow/resolution
|
|
80
80
|
* settings (via {@link applyRendererSettings}). Absent → registry defaults.
|
|
81
81
|
*
|
|
82
|
-
* `opts` is
|
|
83
|
-
*
|
|
84
|
-
* `
|
|
85
|
-
* passes it. The roots path passes `alpha:true` for every stacked canvas
|
|
82
|
+
* When `opts` is omitted, construction leaves `alpha` and
|
|
83
|
+
* `preserveDrawingBuffer` at their renderer defaults. The host passes
|
|
84
|
+
* `alpha:true` for every stacked canvas
|
|
86
85
|
* above the bottom one (so its clear-alpha-0 shows the layer below through
|
|
87
86
|
* it) and `preserveDrawingBuffer:true` for every stacked canvas (the
|
|
88
87
|
* recorded capture-tier cost, paid once here rather than re-derived later).
|
|
@@ -39,7 +39,7 @@ export const EngineBridge = createContext<EngineBridgeValue | null>(null);
|
|
|
39
39
|
* function Player({ speed = 4.5 }) {
|
|
40
40
|
* const { input } = useGameContext();
|
|
41
41
|
* const ref = useRef<THREE.Group>(null);
|
|
42
|
-
* useFrame((_, dt) => { if (input
|
|
42
|
+
* useFrame((_, dt) => { if (input?.isPressed('move_forward')) … });
|
|
43
43
|
* return <group ref={ref}><PlayerCharacter /></group>;
|
|
44
44
|
* }
|
|
45
45
|
* ```
|