@vgai/engine 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -11
- package/package.json +10 -6
- package/schemas/engine-api.json +1 -68
- package/schemas/engine-api.md +1 -32
- package/schemas/engine-capabilities.json +30 -42
- package/schemas/{vgai-game.schema.json → vgai-project.schema.json} +52 -34
- package/src/adapter/adapter-surface.ts +5 -5
- package/src/adapter/authoring.ts +168 -226
- package/src/adapter/colyseus-networking-adapter.ts +44 -5
- package/src/adapter/first-party-systems.ts +156 -42
- package/src/adapter/host-context.ts +177 -46
- package/src/adapter/index.ts +47 -51
- package/src/adapter/ingest/game-contract.ts +2 -2
- package/src/adapter/ingest/scene-capture.ts +18 -19
- package/src/adapter/ingest/structural-ids.ts +127 -0
- package/src/adapter/ingest/upstream-pin.ts +9 -12
- package/src/adapter/loop-gate-report.ts +11 -11
- package/src/adapter/rapier-physics-adapter.ts +27 -9
- package/src/adapter/root-adapter.ts +217 -0
- package/src/adapter/{vgai-scene-game-adapter.ts → setup-three-root-adapter.ts} +173 -351
- package/src/adapter/system-adapter.ts +80 -63
- package/src/ai/navigation.ts +1 -1
- package/src/animation/animation-clock.ts +1 -1
- package/src/animation/camera-ownership.ts +1 -2
- package/src/animation/cubic-spline-interpolant.ts +132 -0
- package/src/animation/theatre-clock-binding.ts +2 -2
- package/src/animation/theatre-object-binding.ts +4 -4
- package/src/animation/xstate-animation-binding.ts +75 -5
- package/src/{scene/schema → asset-formats}/camera.ts +2 -5
- package/src/{scene/schema → asset-formats}/collider.ts +2 -5
- package/src/asset-formats/index.ts +54 -0
- package/src/{scene/schema → asset-formats}/instances.ts +10 -6
- package/src/{scene/schema → asset-formats}/light.ts +3 -6
- package/src/{scene/schema → asset-formats}/material.ts +4 -7
- package/src/{scene/schema → asset-formats}/mesh.ts +4 -7
- package/src/asset-formats/parse.ts +39 -0
- package/src/{scene/schema → asset-formats}/particles.ts +3 -6
- package/src/{scene/schema/environment.ts → asset-formats/render-env.ts} +23 -86
- package/src/{scene/schema → asset-formats}/tuples.ts +1 -1
- package/src/{scene/asset-loaders.ts → asset-loaders.ts} +8 -9
- package/src/asset-parse-error.ts +33 -0
- package/src/{scene/asset-registry.ts → asset-registry.ts} +1 -1
- package/src/assets.ts +1 -1
- package/src/audio/wav-encode.ts +9 -9
- package/src/canvas-react/engine-bridge.ts +59 -0
- package/src/canvas-react/index.ts +50 -0
- package/src/canvas-react/pixi-primitive.tsx +202 -0
- package/src/canvas-react/pixi-react-adapter.tsx +290 -0
- package/src/canvas-react/pixi-react-root-factory.tsx +88 -0
- package/src/canvas-react/world-context.ts +328 -0
- package/src/core/frame-pacing.ts +100 -0
- package/src/core/game-loop.ts +50 -28
- package/src/core/seeded-random.ts +7 -7
- package/src/core/sim-clock.ts +388 -0
- package/src/core/system-runner.ts +17 -63
- package/src/core/types.ts +34 -15
- package/src/data/data-asset.ts +3 -3
- package/src/data/data-check-core.ts +6 -7
- package/src/data/data-ref.ts +11 -11
- package/src/data/vite-plugin-data.ts +10 -10
- package/src/{scene/defaults.ts → defaults.ts} +18 -40
- package/src/dev/render-debug-adapter.ts +1 -1
- package/src/dev/webgl-frame-capture.ts +1 -1
- package/src/ecs/scene-index.ts +439 -0
- package/src/ecs/scene-query.ts +43 -0
- package/src/{scene → ecs}/user-data.ts +17 -36
- package/src/index.ts +7 -9
- package/src/input/input-manager.ts +29 -32
- package/src/input/input-types.ts +2 -2
- package/src/input/schema.ts +5 -5
- package/src/loader.ts +57 -0
- package/src/manifest/editor-port.ts +69 -0
- package/src/manifest/filename.ts +49 -0
- package/src/manifest/index.ts +8 -2
- package/src/manifest/load-file.ts +11 -0
- package/src/manifest/load.ts +65 -77
- package/src/manifest/locate.ts +55 -0
- package/src/manifest/schema.ts +400 -233
- package/src/{scene → physics}/collider-dimensions.ts +3 -3
- package/src/physics/physics-registry.ts +1 -1
- package/src/{world2d/authoring-2d.ts → pixi/authoring.ts} +24 -11
- package/src/pixi/index.ts +43 -0
- package/src/{world2d/ingest-iframe-2d.ts → pixi/ingest-iframe.ts} +9 -9
- package/src/{world2d/ingest2d.ts → pixi/ingest.ts} +28 -28
- package/src/{world2d/physics2d-registry.ts → pixi/physics-registry.ts} +1 -1
- package/src/{world2d/scene-capture-2d.ts → pixi/scene-capture.ts} +3 -3
- package/src/{world2d/system-adapters-2d.ts → pixi/system-adapters.ts} +2 -2
- package/src/react/unmanaged-root-detector.ts +26 -1
- package/src/react/use-data.ts +5 -5
- package/src/react/use-selection.tsx +15 -42
- package/src/react/{game-state.tsx → world-state.tsx} +44 -46
- package/src/render/auto-batcher.ts +1 -2
- package/src/{scene → render}/instance-mesh.ts +1 -1
- package/src/{scene → render}/light-camera-factory.ts +14 -13
- package/src/render/lod.ts +17 -0
- package/src/{scene → render}/material-factory.ts +8 -7
- package/src/{scene → render}/particles-factory.ts +62 -12
- package/src/render/render-batch-system.ts +14 -41
- package/src/render/render-features.ts +1 -1
- package/src/render/render-settings.ts +1 -2
- package/src/render/spark-renderer-lifecycle.ts +1 -1
- package/src/runtime/create-runtime.ts +352 -463
- package/src/runtime/debug-bridge.ts +148 -93
- package/src/runtime/debug-registry.ts +79 -54
- package/src/runtime/dev-layers.ts +40 -0
- package/src/runtime/frame-selector-cache.ts +4 -4
- package/src/runtime/game.ts +618 -369
- package/src/runtime/gameplay-rng-trap.ts +6 -7
- package/src/runtime/input-router.ts +11 -11
- package/src/runtime/mount-game.ts +54 -55
- package/src/runtime/mount-manifest.ts +154 -150
- package/src/runtime/presentation.ts +141 -0
- package/src/runtime/render-audio-control.ts +64 -53
- package/src/runtime/render-control.ts +45 -51
- package/src/runtime/render-seed.ts +3 -4
- package/src/runtime/state-bridge.ts +17 -18
- package/src/runtime/types.ts +94 -61
- package/src/setup/setup-renderer.ts +11 -6
- package/src/world3d-react/engine-bridge.ts +46 -33
- package/src/world3d-react/index.ts +31 -26
- package/src/world3d-react/r3f-adapter.tsx +211 -96
- package/src/world3d-react/r3f-root-factory.tsx +91 -0
- package/src/world3d-react/renderer-config.ts +137 -0
- package/src/world3d-react/world-context.ts +141 -111
- package/schemas/entity2d.schema.json +0 -468
- package/schemas/prefab.schema.json +0 -9992
- package/schemas/scn2d.schema.json +0 -494
- package/schemas/vscn.schema.json +0 -10851
- package/src/adapter/game-adapter.ts +0 -164
- package/src/adapter/ingest/overlay-applier.ts +0 -207
- package/src/adapter/ingest/overlay-apply.ts +0 -168
- package/src/adapter/ingest/overlay-file.ts +0 -126
- package/src/adapter/ingest/overlay-report.ts +0 -176
- package/src/animation/gsap-registration.ts +0 -184
- package/src/audio/audio-introspection.ts +0 -290
- package/src/audio/index.ts +0 -39
- package/src/audio/tone-clock-binding.ts +0 -98
- package/src/audio/tone-context.ts +0 -175
- package/src/audio/tone-offline-render.ts +0 -167
- package/src/ecs/component-manager.ts +0 -814
- package/src/ecs/game-component.ts +0 -260
- package/src/ecs/hmr-swap-report.ts +0 -65
- package/src/physics/trigger-dispatch.ts +0 -97
- package/src/react/root-adapter.tsx +0 -49
- package/src/scene/asset-paths.ts +0 -121
- package/src/scene/asset-ref-check.ts +0 -248
- package/src/scene/component-registry.ts +0 -51
- package/src/scene/parse.ts +0 -204
- package/src/scene/scene-apply.ts +0 -407
- package/src/scene/scene-diff-schema.ts +0 -115
- package/src/scene/scene-diff-types.ts +0 -29
- package/src/scene/scene-loader.ts +0 -1526
- package/src/scene/scene-query.ts +0 -63
- package/src/scene/scene-types.ts +0 -33
- package/src/scene/scene-version.ts +0 -40
- package/src/scene/schema/animation.ts +0 -46
- package/src/scene/schema/audio.ts +0 -25
- package/src/scene/schema/entity-ref.ts +0 -78
- package/src/scene/schema/entity.ts +0 -189
- package/src/scene/schema/index.ts +0 -51
- package/src/scene/schema/joint.ts +0 -26
- package/src/scene/schema/physics.ts +0 -49
- package/src/scene/schema/scene-file.ts +0 -292
- package/src/scene/schema/shadow.ts +0 -24
- package/src/scene/schema/spline.ts +0 -21
- package/src/world2d/asset-paths2d.ts +0 -44
- package/src/world2d/capture-to-scene2d.ts +0 -52
- package/src/world2d/collision-2d.ts +0 -99
- package/src/world2d/entity2d-asset.ts +0 -22
- package/src/world2d/index.ts +0 -91
- package/src/world2d/physics2d-transform.ts +0 -173
- package/src/world2d/physics2d-units.ts +0 -10
- package/src/world2d/pixi-game-adapter.ts +0 -439
- package/src/world2d/pixi-surface.ts +0 -78
- package/src/world2d/scene2d-identity.ts +0 -49
- package/src/world2d/scene2d-loader.ts +0 -433
- package/src/world2d/schema/entity2d.ts +0 -163
- package/src/world2d/schema/physics2d.ts +0 -64
- package/src/world2d/schema/sprite.ts +0 -99
- package/src/world2d/schema/tilemap.ts +0 -39
- package/src/world2d/schema/tuples2d.ts +0 -25
- package/src/world2d/transform-writer-2d.ts +0 -42
- package/src/world2d/types.ts +0 -74
- package/src/world3d-react/behavior.tsx +0 -146
- /package/src/{scene → render}/mesh-shadow.ts +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// hand-built `WorldMountSpec[]`.
|
|
1
|
+
// The standalone manifest-mount helper: an ENGINE-side generalization of
|
|
2
|
+
// `examples/tri-world/src/main.ts`'s hand-built `RootMountSpec[]`.
|
|
4
3
|
//
|
|
5
|
-
// `
|
|
4
|
+
// `resolveAllRoots`/`resolveRootAdapter` (`packages/editor/src/
|
|
6
5
|
// adapter-resolver.ts`) already do this translation for the EDITOR, but they
|
|
7
6
|
// are Vite-coupled by construction (`/@fs/` dynamic imports of project files
|
|
8
7
|
// through the dev server, §0.1/§0.5 of the design doc) — unusable from a
|
|
@@ -14,106 +13,92 @@
|
|
|
14
13
|
// dev server, no editor import.
|
|
15
14
|
//
|
|
16
15
|
// Engine-core react/pixi-free discipline (§0.6, mirrored from
|
|
17
|
-
// `create-runtime.ts`'s own documented rule for its `
|
|
18
|
-
//
|
|
19
|
-
// `pixi.js`, `
|
|
20
|
-
//
|
|
21
|
-
// `entries[id].adapter` (the caller's own module graph built it
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// `VgaiSceneGameAdapter` are already unconditional dependencies of every
|
|
16
|
+
// `create-runtime.ts`'s own documented rule for its `ReactRootAdapter`
|
|
17
|
+
// type-only import): this file never value-imports
|
|
18
|
+
// `pixi.js`, `react`, or `react-dom` — a
|
|
19
|
+
// canvas or react world's adapter is ALWAYS supplied already-constructed via
|
|
20
|
+
// `entries[id].adapter` (the caller's own module graph built it). Threejs is
|
|
21
|
+
// different: `three`/
|
|
22
|
+
// `SetupThreeRootAdapter` are already unconditional dependencies of every
|
|
25
23
|
// caller of `createGameRuntime`'s legacy path (this same file's sibling,
|
|
26
|
-
// immediately below in this directory), so
|
|
27
|
-
// here
|
|
28
|
-
// weight — only pixi/react are avoided.
|
|
24
|
+
// immediately below in this directory), so wrapping a `setup` with
|
|
25
|
+
// `fromSetup` here adds no NEW bundle weight — only pixi/react are avoided.
|
|
29
26
|
|
|
30
|
-
import { fromSetup, VgaiSceneGameAdapter } from '../adapter';
|
|
31
27
|
import { assertNever } from '../adapter/adapter-surface';
|
|
32
|
-
import type {
|
|
28
|
+
import type { RootAdapter } from '../adapter/root-adapter';
|
|
29
|
+
// Value-imported from the implementer's OWN path, not the type-only
|
|
30
|
+
// `../adapter` barrel (P-6).
|
|
31
|
+
import { fromSetup } from '../adapter/setup-three-root-adapter';
|
|
33
32
|
import {
|
|
34
33
|
loadGameManifest,
|
|
35
34
|
type ResolvedAdapterRoot,
|
|
36
35
|
type ResolvedGameManifest,
|
|
37
36
|
} from '../manifest/load';
|
|
38
|
-
import type
|
|
39
|
-
import {
|
|
40
|
-
createGameRuntime,
|
|
41
|
-
type GameSession,
|
|
42
|
-
type Pixi2DGameAdapter,
|
|
43
|
-
type ReactRootAdapter,
|
|
44
|
-
type WorldMountSpec,
|
|
45
|
-
} from './create-runtime';
|
|
37
|
+
import { createGameRuntime, type GameSession, type RootMountSpec } from './create-runtime';
|
|
46
38
|
import { type DebugBridgeWindowTarget, maybeInstallDebugBridge } from './debug-bridge';
|
|
47
39
|
import { getDebugRegistry } from './debug-registry';
|
|
40
|
+
import { devLayersEnabled } from './dev-layers';
|
|
48
41
|
import { getGameplayRngTrapControl } from './gameplay-rng-trap';
|
|
49
42
|
import { RENDER_SEED_QUERY_PARAM } from './render-seed';
|
|
50
|
-
import type { GameSetupFn } from './types';
|
|
43
|
+
import type { GameSetupFn, PlaytestContext } from './types';
|
|
51
44
|
|
|
52
45
|
// ---------------------------------------------------------------------------
|
|
53
46
|
// The `entries` contract
|
|
54
47
|
// ---------------------------------------------------------------------------
|
|
55
48
|
|
|
56
49
|
/**
|
|
57
|
-
* A caller-supplied entry for a `kind: '
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* other shapes `resolveDefaultThreeAdapter` supports (an `entry`-module
|
|
61
|
-
* `setup`, or full caller control via a pre-built adapter):
|
|
50
|
+
* A caller-supplied entry for a `kind: 'three'` world — two ways to satisfy
|
|
51
|
+
* it, matching the two shapes `resolveDefaultThreeAdapter` supports (an
|
|
52
|
+
* `entry`-module `setup`, or full caller control via a pre-built adapter):
|
|
62
53
|
*
|
|
63
|
-
* - `{ adapter }` — a fully-constructed {@link
|
|
64
|
-
* not). Always wins if present, regardless of the world's `
|
|
65
|
-
*
|
|
66
|
-
* (
|
|
54
|
+
* - `{ adapter }` — a fully-constructed {@link RootAdapter} (first-party or
|
|
55
|
+
* not). Always wins if present, regardless of the world's `entry` field —
|
|
56
|
+
* full caller control, including for a `{ module }`-adapter world
|
|
57
|
+
* (mountManifestRoots cannot import an arbitrary module path itself).
|
|
67
58
|
* - `{ setup }` — for an `entry`-declaring world: the project's own
|
|
68
59
|
* `entry` module's exported `setup` function (the same `GameSetupFn`
|
|
69
60
|
* shape `loadProjectScripts` extracts editor-side). Wrapped in a
|
|
70
|
-
* `
|
|
61
|
+
* `SetupThreeRootAdapter` here, mirroring `resolveDefaultThreeAdapter`'s
|
|
71
62
|
* entry branch exactly.
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* support. `componentRegistry` is optional — omit it if the scene
|
|
76
|
-
* authors no named `components:` entries.
|
|
63
|
+
*
|
|
64
|
+
* The former third shape, `{ componentRegistry }` for a `scene`-declaring
|
|
65
|
+
* world, went with the `.vscn.json` format in WO-8.
|
|
77
66
|
*/
|
|
78
67
|
export interface ThreeMountEntry {
|
|
79
|
-
readonly kind: '
|
|
80
|
-
readonly adapter?:
|
|
68
|
+
readonly kind: 'three';
|
|
69
|
+
readonly adapter?: RootAdapter | undefined;
|
|
81
70
|
readonly setup?: GameSetupFn | undefined;
|
|
82
|
-
readonly componentRegistry?: ComponentRegistry | undefined;
|
|
83
71
|
}
|
|
84
72
|
|
|
85
73
|
/**
|
|
86
|
-
* A caller-supplied entry for a `kind: '
|
|
87
|
-
* fully-constructed
|
|
88
|
-
*
|
|
89
|
-
* original hand-mount built it — fetch+parse the `.scn2d.json` yourself,
|
|
90
|
-
* mirroring `resolveDefaultPixiAdapter`'s `fetchScene2DFile` helper, if the
|
|
91
|
-
* world declares `scene`). There is no "mountManifestWorlds builds it for
|
|
74
|
+
* A caller-supplied entry for a `kind: 'canvas'` world — ALWAYS a
|
|
75
|
+
* fully-constructed `RootAdapter<'canvas'>`, built by the caller's own module
|
|
76
|
+
* graph. There is no "mountManifestRoots builds it for
|
|
92
77
|
* you" branch here — doing so would require this file to value-import
|
|
93
78
|
* `pixi.js`, which it deliberately never does (see this file's header
|
|
94
79
|
* comment).
|
|
95
80
|
*/
|
|
96
81
|
export interface PixiMountEntry {
|
|
97
|
-
readonly kind: '
|
|
98
|
-
readonly adapter:
|
|
82
|
+
readonly kind: 'canvas';
|
|
83
|
+
readonly adapter: RootAdapter<'canvas'>;
|
|
99
84
|
}
|
|
100
85
|
|
|
101
86
|
/**
|
|
102
|
-
* A caller-supplied entry for a `kind: '
|
|
103
|
-
* fully-constructed
|
|
104
|
-
* calls `createRoot(host.container).render(<
|
|
105
|
-
* <Entry/></
|
|
87
|
+
* A caller-supplied entry for a `kind: 'dom'` world — ALWAYS a
|
|
88
|
+
* fully-constructed `RootAdapter<'dom'>` (the caller's own module graph
|
|
89
|
+
* calls `createRoot(host.container).render(<WorldProvider game={host.game}>
|
|
90
|
+
* <Entry/></WorldProvider>)` itself, exactly like `tri-world`'s original
|
|
106
91
|
* hand-mount did). A standalone build has ONE module graph, so there is no
|
|
107
|
-
* context-identity hazard the editor's per-project `
|
|
108
|
-
* indirection exists to solve
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
92
|
+
* context-identity hazard the editor's per-project `WorldProvider`
|
|
93
|
+
* indirection exists to solve — the caller's own `<WorldProvider>` import
|
|
94
|
+
* is already the single canonical instance its `Entry` component's own
|
|
95
|
+
* hooks resolve against. This file never value-imports `react`/`react-dom`
|
|
96
|
+
* itself (see this file's header comment) — building the adapter is
|
|
97
|
+
* entirely the caller's job.
|
|
113
98
|
*/
|
|
114
99
|
export interface ReactMountEntry {
|
|
115
|
-
readonly kind: '
|
|
116
|
-
readonly adapter:
|
|
100
|
+
readonly kind: 'dom';
|
|
101
|
+
readonly adapter: RootAdapter<'dom'>;
|
|
117
102
|
}
|
|
118
103
|
|
|
119
104
|
export type MountEntry = ThreeMountEntry | PixiMountEntry | ReactMountEntry;
|
|
@@ -125,7 +110,7 @@ export type MountEntry = ThreeMountEntry | PixiMountEntry | ReactMountEntry;
|
|
|
125
110
|
export interface MountManifestOptions {
|
|
126
111
|
/**
|
|
127
112
|
* A raw (unparsed) manifest value OR an already-`loadGameManifest`d
|
|
128
|
-
* {@link ResolvedGameManifest}. `
|
|
113
|
+
* {@link ResolvedGameManifest}. `mountManifestRoots` NEVER fetches or
|
|
129
114
|
* fs-reads the manifest itself (stays environment-neutral — Node CLI,
|
|
130
115
|
* browser, or headless test alike): the caller owns getting the JSON off
|
|
131
116
|
* disk/network (`loadGameManifestFile` for Node, a plain `fetch()` for a
|
|
@@ -134,28 +119,26 @@ export interface MountManifestOptions {
|
|
|
134
119
|
*/
|
|
135
120
|
readonly manifest: unknown;
|
|
136
121
|
/** The host creates one absolutely-positioned surface per world inside
|
|
137
|
-
* this element — see `
|
|
122
|
+
* this element — see `RootsRuntimeConfig.container`. */
|
|
138
123
|
readonly container: HTMLElement;
|
|
139
124
|
/** Caller-supplied, already-imported entries keyed by manifest world id.
|
|
140
125
|
* Optional overall — a manifest whose every world is scene-driven
|
|
141
|
-
*
|
|
126
|
+
* three with no custom component registry needs none at all. */
|
|
142
127
|
readonly entries?: Readonly<Record<string, MountEntry>> | undefined;
|
|
143
128
|
/** Defaults to the manifest's own `resolution` field if declared, else
|
|
144
|
-
* the container's own size (`
|
|
129
|
+
* the container's own size (`RootsRuntimeConfig`'s existing default). */
|
|
145
130
|
readonly width?: number | undefined;
|
|
146
131
|
readonly height?: number | undefined;
|
|
147
132
|
/** Forwarded to `createGameRuntime` — Node/headless test harnesses only,
|
|
148
|
-
* never a real host. See `
|
|
133
|
+
* never a real host. See `RootsRuntimeConfig.headless`. */
|
|
149
134
|
readonly headless?: boolean | undefined;
|
|
150
135
|
/**
|
|
151
|
-
* Task 2.1
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
* `
|
|
157
|
-
* global object, mirroring `render-control.ts`'s own `location`/`target`
|
|
158
|
-
* override precedent.
|
|
136
|
+
* Task 2.1: overrides for the `?vgai-debug=1` bridge `mountManifestRoots`
|
|
137
|
+
* installs at the tail of every mount — the engine-owned install point so
|
|
138
|
+
* every standalone project gets it with zero template edits. Omit both in
|
|
139
|
+
* a real host (defaults to `window.location`/ `window`); a headless test
|
|
140
|
+
* supplies fakes here instead of touching the global object, mirroring
|
|
141
|
+
* `render-control.ts`'s own `location`/`target` override precedent.
|
|
159
142
|
*/
|
|
160
143
|
readonly debugBridge?:
|
|
161
144
|
| {
|
|
@@ -172,6 +155,17 @@ export interface MountManifestOptions {
|
|
|
172
155
|
* `determinism.seededRandom` — see `resolveDeterminismSeed` below.
|
|
173
156
|
*/
|
|
174
157
|
readonly seed?: number | undefined;
|
|
158
|
+
/** Host identity for a private play run or coordinated Team Test. */
|
|
159
|
+
readonly playtest?: PlaytestContext | null | undefined;
|
|
160
|
+
/**
|
|
161
|
+
* The explicit per-mount answer to "are dev layers enabled" — forwarded
|
|
162
|
+
* verbatim to `devLayersEnabled` (`runtime/dev-layers.ts`), the ONE owner of
|
|
163
|
+
* that question. Omit it and the build decides (dev build ⇒ on; production
|
|
164
|
+
* build ⇒ off unless `VITE_VGAI_DEV_LAYERS=true`). A root declaring
|
|
165
|
+
* `dev: true` is simply not mounted when the answer is `false`, so a shipped
|
|
166
|
+
* build carries no dev GUI at all.
|
|
167
|
+
*/
|
|
168
|
+
readonly devLayers?: boolean | undefined;
|
|
175
169
|
/** Where to read `?vgai-seed=` from for the boot-time seed reader. Same
|
|
176
170
|
* override precedent as `debugBridge.url` (defaults to `window.location`
|
|
177
171
|
* when a real `window` exists; a headless caller with no override gets no
|
|
@@ -181,7 +175,7 @@ export interface MountManifestOptions {
|
|
|
181
175
|
|
|
182
176
|
/**
|
|
183
177
|
* D15 (T-D15.1) boot-time seed reader — resolves the root seed
|
|
184
|
-
* `
|
|
178
|
+
* `mountManifestRoots` threads into `createGameRuntime` (and therefore
|
|
185
179
|
* `createGame`, BEFORE any world's `mount()`/`setup()` runs). Returns
|
|
186
180
|
* `undefined` when the manifest doesn't declare `determinism.seededRandom`
|
|
187
181
|
* at all — `?vgai-seed=` and `defaultSeed` are both ignored in that case
|
|
@@ -214,7 +208,7 @@ function hasRealWindow(): boolean {
|
|
|
214
208
|
return typeof window !== 'undefined';
|
|
215
209
|
}
|
|
216
210
|
|
|
217
|
-
// `
|
|
211
|
+
// `mountManifestRoots` returns the SAME `GameSession` shape
|
|
218
212
|
// `createGameRuntime` returns (D-Z2: "the same session shape... or a thin
|
|
219
213
|
// superset") — callers keep exactly one teardown path (`session.stop()`)
|
|
220
214
|
// whether they mounted through `createGameRuntime` directly or through this
|
|
@@ -250,10 +244,9 @@ function looksAlreadyResolved(value: unknown): value is ResolvedGameManifest {
|
|
|
250
244
|
}
|
|
251
245
|
|
|
252
246
|
/**
|
|
253
|
-
* Exported (E4
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
* must always agree on what "already resolved" means.
|
|
247
|
+
* Exported (E4) so `mount-game.ts`'s `mountGameFromManifest` composer can
|
|
248
|
+
* share this exact raw-vs-resolved detection instead of re-implementing it
|
|
249
|
+
* — the two modules must always agree on what "already resolved" means.
|
|
257
250
|
*/
|
|
258
251
|
export function resolveManifest(raw: unknown): ResolvedGameManifest {
|
|
259
252
|
if (looksAlreadyResolved(raw)) return raw;
|
|
@@ -268,24 +261,23 @@ export function resolveManifest(raw: unknown): ResolvedGameManifest {
|
|
|
268
261
|
function resolveThreeAdapter(
|
|
269
262
|
world: ResolvedAdapterRoot,
|
|
270
263
|
entry: ThreeMountEntry | undefined,
|
|
271
|
-
):
|
|
264
|
+
): RootAdapter {
|
|
272
265
|
if (entry?.adapter) return entry.adapter;
|
|
273
266
|
|
|
274
267
|
if (world.adapter.type === 'module') {
|
|
275
268
|
throw new Error(
|
|
276
|
-
`
|
|
277
|
-
`("${world.adapter.module}") —
|
|
269
|
+
`mountManifestRoots: world "${world.id}" (three) declares a { module } adapter ` +
|
|
270
|
+
`("${world.adapter.module}") — mountManifestRoots never imports an arbitrary module ` +
|
|
278
271
|
'path itself (no `/@fs/`, no dev server). Import the module yourself and supply the ' +
|
|
279
|
-
`constructed adapter via entries["${world.id}"] = { kind: '
|
|
272
|
+
`constructed adapter via entries["${world.id}"] = { kind: 'three', adapter }.`,
|
|
280
273
|
);
|
|
281
274
|
}
|
|
282
275
|
if (world.adapter.type === 'ingest') {
|
|
283
276
|
throw new Error(
|
|
284
|
-
`
|
|
277
|
+
`mountManifestRoots: world "${world.id}" (three) declares an { ingest } adapter — ` +
|
|
285
278
|
"ingest roots require the editor's dev-server-backed mount machinery (an EditorStore " +
|
|
286
279
|
"plus iframe/DOM capture, see adapter-resolver.ts's resolveIngestThreeAdapter) and are " +
|
|
287
|
-
'not supported by
|
|
288
|
-
'porting aids, no hosted ingest routes).',
|
|
280
|
+
'not supported by mountManifestRoots (no porting aids, no hosted ingest routes).',
|
|
289
281
|
);
|
|
290
282
|
}
|
|
291
283
|
|
|
@@ -293,40 +285,33 @@ function resolveThreeAdapter(
|
|
|
293
285
|
if (world.entry !== undefined) {
|
|
294
286
|
if (!entry?.setup) {
|
|
295
287
|
throw new Error(
|
|
296
|
-
`
|
|
297
|
-
`— supply entries["${world.id}"] = { kind: '
|
|
298
|
-
"exported `setup` function) or { kind: '
|
|
288
|
+
`mountManifestRoots: world "${world.id}" (three) declares \`entry\` "${world.entry}" ` +
|
|
289
|
+
`— supply entries["${world.id}"] = { kind: 'three', setup } (the entry module's ` +
|
|
290
|
+
"exported `setup` function) or { kind: 'three', adapter } for full control. Got " +
|
|
299
291
|
`${entry === undefined ? 'no entry at all' : 'an entry with neither `setup` nor `adapter`'}.`,
|
|
300
292
|
);
|
|
301
293
|
}
|
|
302
294
|
return fromSetup(world.id, entry.setup);
|
|
303
295
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
scenePath: world.scene,
|
|
308
|
-
componentRegistry: entry?.componentRegistry,
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
// Unreachable in practice: `load.ts`'s `checkAdapterSceneEntryRules`
|
|
312
|
-
// requires `scene` or `entry` for every 'default' adapter world at
|
|
313
|
-
// manifest-load time — see `resolveDefaultThreeAdapter`'s identical guard.
|
|
296
|
+
// Unreachable in practice: `load.ts`'s `checkAdapterSceneEntryRules` requires
|
|
297
|
+
// `entry` for every 'default' THREE adapter world at manifest-load time (and
|
|
298
|
+
// rejects `scene` outright — the `.vscn.json` format was removed in WO-8).
|
|
314
299
|
throw new Error(
|
|
315
|
-
`
|
|
316
|
-
'
|
|
300
|
+
`mountManifestRoots: world "${world.id}" is default-three with no \`entry\` — this ` +
|
|
301
|
+
'should have been rejected by manifest validation.',
|
|
317
302
|
);
|
|
318
303
|
}
|
|
319
304
|
|
|
320
305
|
function resolvePixiAdapter(
|
|
321
306
|
world: ResolvedAdapterRoot,
|
|
322
307
|
entry: PixiMountEntry | undefined,
|
|
323
|
-
):
|
|
308
|
+
): RootAdapter<'canvas'> {
|
|
324
309
|
if (entry?.adapter) return entry.adapter;
|
|
325
310
|
throw new Error(
|
|
326
|
-
`
|
|
311
|
+
`mountManifestRoots: world "${world.id}" (canvas) has no entries["${world.id}"] — a canvas ` +
|
|
327
312
|
'world always needs a caller-supplied, already-constructed adapter ' +
|
|
328
|
-
`(entries["${world.id}"] = { kind: '
|
|
329
|
-
|
|
313
|
+
`(entries["${world.id}"] = { kind: 'canvas', adapter }, constructed from your own ` +
|
|
314
|
+
'already-imported pixi module graph) — mountManifestRoots never ' +
|
|
330
315
|
"value-imports pixi.js (mirrors create-runtime.ts's own pixi-free-core discipline).",
|
|
331
316
|
);
|
|
332
317
|
}
|
|
@@ -334,25 +319,24 @@ function resolvePixiAdapter(
|
|
|
334
319
|
function resolveReactAdapter(
|
|
335
320
|
world: ResolvedAdapterRoot,
|
|
336
321
|
entry: ReactMountEntry | undefined,
|
|
337
|
-
):
|
|
322
|
+
): RootAdapter<'dom'> {
|
|
338
323
|
if (entry?.adapter) return entry.adapter;
|
|
339
324
|
throw new Error(
|
|
340
|
-
`
|
|
341
|
-
|
|
342
|
-
`(entries["${world.id}"] = { kind: '
|
|
343
|
-
'react-dom `createRoot` + your own `<
|
|
344
|
-
"value-imports react/react-dom (mirrors create-runtime.ts's own react-free-core discipline
|
|
345
|
-
'see docs/REACT-STATE-BRIDGE.md §2-3).',
|
|
325
|
+
`mountManifestRoots: world "${world.id}" (react) has no entries["${world.id}"] — a react ` +
|
|
326
|
+
"world always needs a caller-supplied, already-constructed `RootAdapter<'dom'>` " +
|
|
327
|
+
`(entries["${world.id}"] = { kind: 'dom', adapter }, mounting via your own already-imported ` +
|
|
328
|
+
'react-dom `createRoot` + your own `<WorldProvider>`) — mountManifestRoots never ' +
|
|
329
|
+
"value-imports react/react-dom (mirrors create-runtime.ts's own react-free-core discipline).",
|
|
346
330
|
);
|
|
347
331
|
}
|
|
348
332
|
|
|
349
|
-
function
|
|
333
|
+
function buildRootMountSpec(
|
|
350
334
|
world: ResolvedAdapterRoot,
|
|
351
335
|
entry: MountEntry | undefined,
|
|
352
|
-
):
|
|
336
|
+
): RootMountSpec {
|
|
353
337
|
if (entry !== undefined && entry.kind !== world.surface) {
|
|
354
338
|
throw new Error(
|
|
355
|
-
`
|
|
339
|
+
`mountManifestRoots: entries["${world.id}"] declares kind "${entry.kind}" but the ` +
|
|
356
340
|
`manifest's root "${world.id}" uses surface "${world.surface}" — fix the entries key (or the ` +
|
|
357
341
|
'manifest) so the two agree.',
|
|
358
342
|
);
|
|
@@ -362,6 +346,7 @@ function buildWorldMountSpec(
|
|
|
362
346
|
id: world.id,
|
|
363
347
|
zOrder: world.zOrder,
|
|
364
348
|
pausable: world.pausable,
|
|
349
|
+
dev: world.dev,
|
|
365
350
|
loop: world.loop,
|
|
366
351
|
};
|
|
367
352
|
|
|
@@ -369,23 +354,23 @@ function buildWorldMountSpec(
|
|
|
369
354
|
// above (or `entry` is `undefined`) — TS can't narrow a `Record` lookup
|
|
370
355
|
// through that runtime check, so each branch casts to its own entry shape;
|
|
371
356
|
// the actual safety comes from the mismatch guard, not from the cast.
|
|
372
|
-
if (world.surface === '
|
|
357
|
+
if (world.surface === 'three') {
|
|
373
358
|
const threeEntry = entry as ThreeMountEntry | undefined;
|
|
374
|
-
return { ...base, kind: '
|
|
359
|
+
return { ...base, kind: 'three', adapter: resolveThreeAdapter(world, threeEntry) };
|
|
375
360
|
}
|
|
376
|
-
if (world.surface === '
|
|
361
|
+
if (world.surface === 'canvas') {
|
|
377
362
|
const pixiEntry = entry as PixiMountEntry | undefined;
|
|
378
|
-
return { ...base, kind: '
|
|
363
|
+
return { ...base, kind: 'canvas', adapter: resolvePixiAdapter(world, pixiEntry) };
|
|
379
364
|
}
|
|
380
|
-
if (world.surface === '
|
|
365
|
+
if (world.surface === 'dom') {
|
|
381
366
|
const reactEntry = entry as ReactMountEntry | undefined;
|
|
382
|
-
return { ...base, kind: '
|
|
367
|
+
return { ...base, kind: 'dom', adapter: resolveReactAdapter(world, reactEntry) };
|
|
383
368
|
}
|
|
384
|
-
// Exhaustiveness guard (§7.4-2, same idiom as `
|
|
369
|
+
// Exhaustiveness guard (§7.4-2, same idiom as `resolveAllRoots`'s own
|
|
385
370
|
// dispatch loop): `world.kind` is the closed `AdapterRoot['kind']` union
|
|
386
|
-
// (`z.enum(['
|
|
371
|
+
// (`z.enum(['three','canvas','react'])`), so a hypothetical 4th kind
|
|
387
372
|
// must fail to compile here, not silently fall through.
|
|
388
|
-
return assertNever(world.surface, '
|
|
373
|
+
return assertNever(world.surface, 'mountManifestRoots');
|
|
389
374
|
}
|
|
390
375
|
|
|
391
376
|
// ---------------------------------------------------------------------------
|
|
@@ -393,31 +378,49 @@ function buildWorldMountSpec(
|
|
|
393
378
|
// ---------------------------------------------------------------------------
|
|
394
379
|
|
|
395
380
|
/**
|
|
396
|
-
* Mount every world declared by a `vgai.
|
|
381
|
+
* Mount every world declared by a `vgai.project.json` manifest onto `container`,
|
|
397
382
|
* standalone — no editor, no dev server. The generalization of
|
|
398
|
-
* `examples/tri-world/src/main.ts`'s hand-built `
|
|
399
|
-
*
|
|
400
|
-
*
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
* this file performs itself.
|
|
383
|
+
* `examples/tri-world/src/main.ts`'s hand-built `RootMountSpec[]` (D-Z2). Reuses
|
|
384
|
+
* the pure `loadGameManifest` + `createGameRuntime({ roots })` and mirrors
|
|
385
|
+
* `resolveAllRoots`'s per-kind dispatch — but every entry the manifest needs
|
|
386
|
+
* beyond what a `default`- adapter scene/entry can express on its own comes from
|
|
387
|
+
* the caller's OWN already-imported module graph (`opts.entries`), never a
|
|
388
|
+
* dynamic import this file performs itself.
|
|
405
389
|
*
|
|
406
|
-
* Degrades loudly
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
390
|
+
* Degrades loudly: a missing entry, a kind/shape mismatch, a `{ module }`/`{
|
|
391
|
+
* ingest }` adapter with no caller-supplied adapter, or an empty manifest all
|
|
392
|
+
* throw a named `Error` identifying the world id, its kind, and what was
|
|
393
|
+
* expected — never a silent skip or a partially-mounted session.
|
|
394
|
+
*
|
|
395
|
+
* The ONE root that IS skipped silently is a `dev: true` dev layer when dev
|
|
396
|
+
* layers are disabled (`devLayersEnabled`) — that skip is the feature: it is
|
|
397
|
+
* what makes a dev GUI safe to leave in the game.
|
|
411
398
|
*/
|
|
412
|
-
export async function
|
|
399
|
+
export async function mountManifestRoots(opts: MountManifestOptions): Promise<GameSession> {
|
|
413
400
|
const manifest = resolveManifest(opts.manifest);
|
|
414
401
|
if (manifest.roots.length === 0) {
|
|
415
|
-
throw new Error('
|
|
402
|
+
throw new Error('mountManifestRoots: manifest declares no roots — nothing to mount.');
|
|
416
403
|
}
|
|
417
404
|
const entries = opts.entries ?? {};
|
|
418
405
|
|
|
419
|
-
|
|
420
|
-
|
|
406
|
+
// The SHIP GATE. A `dev: true` root is the game's own dev GUI; it mounts
|
|
407
|
+
// only when dev layers are enabled, so a production build carries no dev
|
|
408
|
+
// GUI. The answer comes from `devLayersEnabled` and nowhere else — see that
|
|
409
|
+
// function's header for why there is exactly one owner. Dropping the root
|
|
410
|
+
// here (rather than mounting it hidden) is deliberate: nothing of the dev
|
|
411
|
+
// layer is constructed, so its entry module's cost and its DOM never exist
|
|
412
|
+
// in a shipped build.
|
|
413
|
+
const devEnabled = devLayersEnabled(opts.devLayers);
|
|
414
|
+
const mountable = devEnabled ? manifest.roots : manifest.roots.filter((world) => !world.dev);
|
|
415
|
+
if (mountable.length === 0) {
|
|
416
|
+
throw new Error(
|
|
417
|
+
'mountManifestRoots: every root in this manifest is a dev layer (`dev: true`) and dev ' +
|
|
418
|
+
'layers are disabled for this build — there is no game to mount.',
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const roots: RootMountSpec[] = mountable.map((world) =>
|
|
423
|
+
buildRootMountSpec(world, entries[world.id]),
|
|
421
424
|
);
|
|
422
425
|
|
|
423
426
|
// D15 (T-D15.1) — resolved BEFORE `createGameRuntime` (which constructs the
|
|
@@ -440,6 +443,7 @@ export async function mountManifestWorlds(opts: MountManifestOptions): Promise<G
|
|
|
440
443
|
height: opts.height ?? manifest.resolution?.height,
|
|
441
444
|
headless: opts.headless,
|
|
442
445
|
seed: resolvedSeed,
|
|
446
|
+
playtest: opts.playtest,
|
|
443
447
|
});
|
|
444
448
|
|
|
445
449
|
// D15 (T-D15.3) — the dev-mode Math.random phase trap: only while the
|
|
@@ -462,11 +466,11 @@ export async function mountManifestWorlds(opts: MountManifestOptions): Promise<G
|
|
|
462
466
|
if (debugRegistry) {
|
|
463
467
|
// Defect 3 fix: `setRoomDeclared` (the locus-required rule — a project
|
|
464
468
|
// with a Colyseus room must declare `locus: 'client' | 'server'` on every
|
|
465
|
-
// debug command
|
|
466
|
-
//
|
|
467
|
-
//
|
|
468
|
-
//
|
|
469
|
-
//
|
|
469
|
+
// debug command) was never actually called from a real mount path — this
|
|
470
|
+
// is that wiring. `manifest.server` (`ResolvedGameManifest.server`,
|
|
471
|
+
// `manifest/load.ts`) is `{ room, module } | undefined`; its mere presence
|
|
472
|
+
// is the "this project declares a room" signal, independent of
|
|
473
|
+
// whether/when the game actually joins it.
|
|
470
474
|
if (manifest.server) {
|
|
471
475
|
debugRegistry.setRoomDeclared(true);
|
|
472
476
|
}
|