@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
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* the ONE game-scoped registry (`getDebugRegistry(host.game)`), so a
|
|
12
12
|
* component's effect can `ctx.debug.registerStateProvider(...)` and be
|
|
13
13
|
* visible to `vgai eval`/the editor's debug panels.
|
|
14
|
-
* 2. **`ctx.input` is the game-owned `InputManager
|
|
14
|
+
* 2. **`ctx.input` is the game-owned `InputManager` when a Game hosts the
|
|
15
|
+
* root** (`host.game.input` — the
|
|
15
16
|
* same instance every sibling root shares), and this module registers the
|
|
16
17
|
* per-world debug-registry seams (`setVirtualInputTarget`/
|
|
17
18
|
* `setInputActionsSource`/`setInputTraceSource`) that make `game.input.*`
|
|
@@ -19,13 +20,14 @@
|
|
|
19
20
|
* `DEBUG_INPUT_UNAVAILABLE`. The project's input map loads through
|
|
20
21
|
* `Game.loadInputMap` (the game-owned, load-once path every root shares).
|
|
21
22
|
* A missing/unparseable map degrades LOUDLY without failing the mount — a
|
|
22
|
-
* canvas world with no declared actions is legal.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
23
|
+
* canvas world with no declared actions is legal. A bare/foreign mount
|
|
24
|
+
* receives `null`, visibly in the type, rather than a throwing getter.
|
|
25
|
+
* 3. **Absent subsystems are absent from the type.** A canvas world builds no
|
|
26
|
+
* THREE scene/camera, Rapier world, composer, first-party audio/particles/
|
|
27
|
+
* debug-draw, or Three-flavoured `AssetCache`. Its explicit
|
|
28
|
+
* {@link CanvasGameContext} cannot name those fields, so misuse fails at
|
|
29
|
+
* the call site during typechecking instead of from a throwing getter at
|
|
30
|
+
* runtime.
|
|
29
31
|
*
|
|
30
32
|
* The Pixi handles themselves are deliberately NOT re-exposed here: a component
|
|
31
33
|
* reaches the live `Application` (and therefore `app.stage`, `app.renderer`,
|
|
@@ -42,6 +44,7 @@ import {
|
|
|
42
44
|
DEFAULT_SEEDED_RANDOM_SEED,
|
|
43
45
|
getSeededRandom,
|
|
44
46
|
} from '../core/seeded-random';
|
|
47
|
+
import type { SimClock } from '../core/sim-clock';
|
|
45
48
|
import { createSimClock, getSimClock, type SimClockInternal } from '../core/sim-clock';
|
|
46
49
|
import { createSystemRunner } from '../core/system-runner';
|
|
47
50
|
import {
|
|
@@ -49,7 +52,7 @@ import {
|
|
|
49
52
|
type DebugRegistry,
|
|
50
53
|
getDebugRegistry,
|
|
51
54
|
} from '../runtime/debug-registry';
|
|
52
|
-
import type {
|
|
55
|
+
import type { ComponentContextBase } from '../runtime/types';
|
|
53
56
|
|
|
54
57
|
/** The conventional input-map location every scaffolded project ships. Shared
|
|
55
58
|
* verbatim with the three lane — a project has ONE input map, whatever surface
|
|
@@ -70,12 +73,20 @@ export interface CanvasRootContextOptions {
|
|
|
70
73
|
readonly inputMapPath?: string | null | undefined;
|
|
71
74
|
}
|
|
72
75
|
|
|
76
|
+
/** The engine services a Canvas component genuinely receives. Pixi's own
|
|
77
|
+
* `useApplication()` and `Assets` remain the doors to rendering and assets.
|
|
78
|
+
* `disposeAfter` is excluded because it accepts a `THREE.Object3D`; Canvas
|
|
79
|
+
* code schedules `displayObject.destroy()` with `clock.after()` instead. */
|
|
80
|
+
export type CanvasGameContext = ComponentContextBase & {
|
|
81
|
+
readonly clock: Omit<SimClock, 'disposeAfter'>;
|
|
82
|
+
};
|
|
83
|
+
|
|
73
84
|
/** What {@link createCanvasRootContext} returns — the engine runtime one canvas
|
|
74
85
|
* world owns, plus the wiring hooks the adapter's `mount()` drives. */
|
|
75
86
|
export interface CanvasRootRuntime {
|
|
76
|
-
/** The
|
|
77
|
-
*
|
|
78
|
-
readonly ctx:
|
|
87
|
+
/** The explicit Canvas context this world's components read through
|
|
88
|
+
* `useGameContext`. See {@link CanvasGameContext}. */
|
|
89
|
+
readonly ctx: CanvasGameContext;
|
|
79
90
|
/** The world-local phase runner `mount().update(dt)` ticks. */
|
|
80
91
|
readonly systems: ReturnType<typeof createSystemRunner>;
|
|
81
92
|
/** The mounted world's `SystemAdapters` bag (`mounted.systems`) —
|
|
@@ -95,32 +106,6 @@ export interface CanvasRootRuntime {
|
|
|
95
106
|
dispose(): void;
|
|
96
107
|
}
|
|
97
108
|
|
|
98
|
-
/** Install a throwing getter for a `GameContext` field this world genuinely
|
|
99
|
-
* does not build — a component reaching for it fails LOUDLY at its own call
|
|
100
|
-
* site, naming the limitation, instead of reading `undefined` and breaking
|
|
101
|
-
* somewhere downstream. Non-enumerable so spreads/serialization of the ctx
|
|
102
|
-
* never trip it. */
|
|
103
|
-
function defineAbsentCtxField(
|
|
104
|
-
target: Record<string, unknown>,
|
|
105
|
-
worldId: string,
|
|
106
|
-
key: string,
|
|
107
|
-
why: string,
|
|
108
|
-
): void {
|
|
109
|
-
Object.defineProperty(target, key, {
|
|
110
|
-
configurable: true,
|
|
111
|
-
enumerable: false,
|
|
112
|
-
get(): never {
|
|
113
|
-
throw new Error(
|
|
114
|
-
`canvas world "${worldId}": ctx.${key} is not available — ${why} ` +
|
|
115
|
-
'(A canvas world ctx provides: input, systems, clock, debug, random, game, roots, ' +
|
|
116
|
-
"registerSystemAdapter. The Pixi runtime itself comes from @pixi/react's " +
|
|
117
|
-
'useApplication() — app.stage, app.renderer, app.ticker — and assets from Pixi`s own ' +
|
|
118
|
-
'Assets.)',
|
|
119
|
-
);
|
|
120
|
-
},
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
109
|
/**
|
|
125
110
|
* Build the engine runtime for one canvas world from a `CanvasHostContext`.
|
|
126
111
|
* React-free on purpose: everything here is plain engine wiring the adapter
|
|
@@ -145,13 +130,14 @@ export function createCanvasRootContext(
|
|
|
145
130
|
|
|
146
131
|
const ctxRaw: Record<string, unknown> = {
|
|
147
132
|
systems,
|
|
133
|
+
input: host.game?.input ?? null,
|
|
148
134
|
registerSystemAdapter: (kind: keyof SystemAdapters, adapter: SystemAdapters[typeof kind]) => {
|
|
149
135
|
// biome-ignore lint/suspicious/noExplicitAny: same per-key record write the three lane performs; correct by construction
|
|
150
136
|
(systemAdapters as any)[kind] = adapter;
|
|
151
137
|
host.game?.notifySystemAdaptersChanged();
|
|
152
138
|
},
|
|
153
139
|
};
|
|
154
|
-
const ctx = ctxRaw as unknown as
|
|
140
|
+
const ctx = ctxRaw as unknown as CanvasGameContext;
|
|
155
141
|
if (host.game) {
|
|
156
142
|
ctx.game = host.game;
|
|
157
143
|
ctx.roots = host.game.roots;
|
|
@@ -188,7 +174,7 @@ export function createCanvasRootContext(
|
|
|
188
174
|
);
|
|
189
175
|
},
|
|
190
176
|
});
|
|
191
|
-
|
|
177
|
+
ctxRaw['clock'] = clock;
|
|
192
178
|
|
|
193
179
|
// --- ctx.input + the debug-registry input seams — the game-owned
|
|
194
180
|
// InputManager, polled by the game-scoped runner each frame; this world
|
|
@@ -198,7 +184,6 @@ export function createCanvasRootContext(
|
|
|
198
184
|
if (host.game) {
|
|
199
185
|
const game = host.game;
|
|
200
186
|
const input = game.input;
|
|
201
|
-
ctxRaw['input'] = input;
|
|
202
187
|
debugRegistry.setInputActionsSource(id, () =>
|
|
203
188
|
input.actionNames().map((name) => ({ name, valueType: input.getActionValueType(name) })),
|
|
204
189
|
);
|
|
@@ -239,68 +224,8 @@ export function createCanvasRootContext(
|
|
|
239
224
|
);
|
|
240
225
|
});
|
|
241
226
|
}
|
|
242
|
-
} else {
|
|
243
|
-
defineAbsentCtxField(
|
|
244
|
-
ctxRaw,
|
|
245
|
-
id,
|
|
246
|
-
'input',
|
|
247
|
-
'input is game-owned and this mount has no Game host (host.game is absent — bare ' +
|
|
248
|
-
'harness/foreign host); mount through the vgai host to get the shared InputManager.',
|
|
249
|
-
);
|
|
250
227
|
}
|
|
251
228
|
|
|
252
|
-
// --- Loud absence for everything a canvas world genuinely does not build. ---
|
|
253
|
-
const threeWhy =
|
|
254
|
-
'a canvas world renders through PixiJS and owns no THREE scene graph; the world root is ' +
|
|
255
|
-
"the Pixi stage, reached with @pixi/react's useApplication().app.stage.";
|
|
256
|
-
defineAbsentCtxField(ctxRaw, id, 'scene', threeWhy);
|
|
257
|
-
defineAbsentCtxField(ctxRaw, id, 'camera', threeWhy);
|
|
258
|
-
defineAbsentCtxField(
|
|
259
|
-
ctxRaw,
|
|
260
|
-
id,
|
|
261
|
-
'sceneIndex',
|
|
262
|
-
'the live scene index walks a THREE.Scene; a canvas world has none (walk the Pixi display ' +
|
|
263
|
-
'tree from useApplication().app.stage instead).',
|
|
264
|
-
);
|
|
265
|
-
const rapierWhy =
|
|
266
|
-
'canvas worlds do not build the first-party Rapier runtime; drive physics from inside the ' +
|
|
267
|
-
'tree with the 2D physics library of your choice.';
|
|
268
|
-
defineAbsentCtxField(ctxRaw, id, 'rapierWorld', rapierWhy);
|
|
269
|
-
defineAbsentCtxField(ctxRaw, id, 'rapier', rapierWhy);
|
|
270
|
-
defineAbsentCtxField(ctxRaw, id, 'physics', rapierWhy);
|
|
271
|
-
defineAbsentCtxField(ctxRaw, id, 'collisions', rapierWhy);
|
|
272
|
-
defineAbsentCtxField(
|
|
273
|
-
ctxRaw,
|
|
274
|
-
id,
|
|
275
|
-
'assets',
|
|
276
|
-
'the canvas host context carries no AssetCache (that cache is a GLTF/texture loader ' +
|
|
277
|
-
"resolving THREE objects); load textures/spritesheets with Pixi's own `Assets`.",
|
|
278
|
-
);
|
|
279
|
-
defineAbsentCtxField(
|
|
280
|
-
ctxRaw,
|
|
281
|
-
id,
|
|
282
|
-
'composer',
|
|
283
|
-
'a canvas world has no postprocessing EffectComposer; use Pixi filters on a Container.',
|
|
284
|
-
);
|
|
285
|
-
defineAbsentCtxField(
|
|
286
|
-
ctxRaw,
|
|
287
|
-
id,
|
|
288
|
-
'audio',
|
|
289
|
-
'canvas worlds build no first-party audio context; use Web Audio directly (or @pixi/sound).',
|
|
290
|
-
);
|
|
291
|
-
defineAbsentCtxField(
|
|
292
|
-
ctxRaw,
|
|
293
|
-
id,
|
|
294
|
-
'particles',
|
|
295
|
-
'canvas worlds build no three.quarks particle runtime; use a Pixi particle container.',
|
|
296
|
-
);
|
|
297
|
-
defineAbsentCtxField(
|
|
298
|
-
ctxRaw,
|
|
299
|
-
id,
|
|
300
|
-
'debugDraw',
|
|
301
|
-
'canvas worlds build no debug-draw helper; draw into a Pixi `Graphics` in the tree.',
|
|
302
|
-
);
|
|
303
|
-
|
|
304
229
|
return {
|
|
305
230
|
ctx,
|
|
306
231
|
systems,
|
package/src/data/curve.ts
CHANGED
|
@@ -59,7 +59,7 @@ export interface CurveAxes {
|
|
|
59
59
|
y: { min: number; max: number; label?: string };
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
/** How the marker rides out to the emitted JSON Schema — the editor's
|
|
62
|
+
/** How the marker rides out to the emitted JSON Schema — the editor's detection key, the exact shape `dataRef` uses with `x-vgai-ref`. */
|
|
63
63
|
const CURVE_META_KEY = 'x-vgai-curve';
|
|
64
64
|
|
|
65
65
|
function axisLabel(axes: CurveAxes, axis: 'x' | 'y'): string {
|
|
@@ -155,7 +155,7 @@ export function curve(axes: CurveAxes): z.ZodType<CurveValue> {
|
|
|
155
155
|
* Read a curve field's declared axes back off a LIVE Zod schema (as opposed to
|
|
156
156
|
* the emitted JSON Schema, which the editor's Data panel reads instead).
|
|
157
157
|
*
|
|
158
|
-
* This is the dev-
|
|
158
|
+
* This is the dev-tools capability's detection door: it derives its rows by
|
|
159
159
|
* walking the game's own `TuningSchema` object, so it needs the axes from the
|
|
160
160
|
* schema in hand. Wrappers (`.optional()`, `.default(…)`, `.describe(…)` chains
|
|
161
161
|
* that re-wrap) hide the metadata behind an inner type, so a bounded unwrap
|
package/src/dev/instruments.ts
CHANGED
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
* game registry.
|
|
26
26
|
*
|
|
27
27
|
* ── THE DOOR ────────────────────────────────────────────────────────────────
|
|
28
|
-
* This module is IMPLEMENTATION ONLY. It publishes nothing itself: the dev-
|
|
29
|
-
* capability's own registry adopts this list (`
|
|
28
|
+
* This module is IMPLEMENTATION ONLY. It publishes nothing itself: the dev-tools
|
|
29
|
+
* capability's own registry adopts this list (`installDevToolsInstruments`) and
|
|
30
30
|
* publishes each instrument through the SAME door every declaration in that
|
|
31
31
|
* capability uses — one provider per reading (`instrument.timescale`), one
|
|
32
|
-
* command per action (`instrument.timescale.set`), plus the `dev-
|
|
32
|
+
* command per action (`instrument.timescale.set`), plus the `dev-tools` index
|
|
33
33
|
* provider. That is deliberate and load-bearing: the registry IS the product,
|
|
34
34
|
* so every reader of an instrument reaches the SAME registration.
|
|
35
35
|
*
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
* ── RESOURCE OWNERSHIP, STATED ONCE ─────────────────────────────────────────
|
|
44
44
|
* {@link createDevInstruments} owns everything it allocates: the frame-time
|
|
45
45
|
* ring buffer and its ONE `game.onRenderStep` subscription. SHARER: the
|
|
46
|
-
* dev-
|
|
46
|
+
* dev-tools capability's `DevToolsBridge`, which creates one set per mounted
|
|
47
47
|
* game. TEARDOWN: the returned {@link DevInstrumentSet.dispose} — the ONE path
|
|
48
48
|
* that ends it. Nothing else here holds a resource; the time-scale, pause and
|
|
49
49
|
* collider instruments only drive state that `Game` already owns.
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
* the drawing buffer halves was never fill-bound.
|
|
37
37
|
*
|
|
38
38
|
* ── GATING ──────────────────────────────────────────────────────────────────
|
|
39
|
-
* The caller gates. Both seeders call this only under `
|
|
40
|
-
* (`runtime/dev-
|
|
39
|
+
* The caller gates. Both seeders call this only under `devBuildEnabled()`
|
|
40
|
+
* (`runtime/dev-build.ts` — the ONE owner of "is this a dev context"), so a
|
|
41
41
|
* ship build registers nothing and pays nothing. Each also requires a `Game`
|
|
42
42
|
* shell (there is no profiler to fold without one) and a non-headless mount
|
|
43
43
|
* (nothing brackets a submission, so the readings could only ever be empty).
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* require someone to ASK, and both require that someone to already know that
|
|
7
7
|
* static batching exists, is possible here, and is spelled `<Frozen>`. An
|
|
8
8
|
* agent building a game does not know any of that, so the measurement has to
|
|
9
|
-
* do the routing itself — the same idiom as
|
|
9
|
+
* do the routing itself — the same idiom as dev-tools' unconfigured-section
|
|
10
10
|
* warning: the reading names the exact edit.
|
|
11
11
|
*
|
|
12
12
|
* ── THE DECISION IS PURE, THE SCHEDULE IS NOT ───────────────────────────────
|
|
@@ -146,7 +146,7 @@ export function decideStaticBatchAdvisory(
|
|
|
146
146
|
* Once per PAGE, not once per module evaluation — a hot reload re-runs this
|
|
147
147
|
* module, and an advisory that reappears on every save is one that gets muted
|
|
148
148
|
* along with everything else on the console. Same mechanism, and the same
|
|
149
|
-
* reason, as
|
|
149
|
+
* reason, as dev-tools' unconfigured-section warning.
|
|
150
150
|
*/
|
|
151
151
|
const WARNED_KEY = '__vgaiStaticBatchAdvised';
|
|
152
152
|
|
package/src/ecs/user-data.ts
CHANGED
|
@@ -111,6 +111,8 @@
|
|
|
111
111
|
* JSX props for renderer/editor integration.
|
|
112
112
|
* - `constraints` — live project-owned spatial constraints projected
|
|
113
113
|
* into the shared Inspector and viewport.
|
|
114
|
+
* - `authoringSubject` — transient identity/inspection for an ecosystem-native
|
|
115
|
+
* subject represented by an Object3D proxy.
|
|
114
116
|
* - `splineControlPoint` — index of a spline control-point drag handle.
|
|
115
117
|
* - `vcDirIdx` — view-cube face direction index (0=+X,1=-X,2=+Y,...).
|
|
116
118
|
*/
|
|
@@ -118,6 +120,7 @@
|
|
|
118
120
|
import type * as THREE from 'three';
|
|
119
121
|
import type { ParticleSystem } from 'three.quarks';
|
|
120
122
|
import type { ConstraintMark } from '../adapter/constraint';
|
|
123
|
+
import type { Object3DAuthoringSubjectMark } from '../adapter/object3d-authoring-subject';
|
|
121
124
|
import type { RapierEditableBody } from '../adapter/rapier-physics-adapter';
|
|
122
125
|
import type { ReflectionProbeMark } from '../adapter/reflection-probe';
|
|
123
126
|
import type { AnimationRuntimeInspection } from '../animation/runtime-inspection';
|
|
@@ -197,6 +200,7 @@ export interface UserDataSchema {
|
|
|
197
200
|
skeletonEnabled: boolean;
|
|
198
201
|
envObject: boolean;
|
|
199
202
|
constraints: readonly ConstraintMark[];
|
|
203
|
+
authoringSubject: Object3DAuthoringSubjectMark;
|
|
200
204
|
reflectionProbe: ReflectionProbeMark;
|
|
201
205
|
vcDirIdx: number;
|
|
202
206
|
/** W1a navmesh path-probe preview polyline: the routed waypoints, mirrored
|
|
@@ -252,6 +256,7 @@ export const UserDataKeys = {
|
|
|
252
256
|
skeletonEnabled: 'skeletonEnabled',
|
|
253
257
|
envObject: 'envObject',
|
|
254
258
|
constraints: 'constraints',
|
|
259
|
+
authoringSubject: 'authoringSubject',
|
|
255
260
|
reflectionProbe: 'reflectionProbe',
|
|
256
261
|
vcDirIdx: 'vcDirIdx',
|
|
257
262
|
navPathPoints: 'navPathPoints',
|
|
@@ -310,6 +310,9 @@ export class InputManager {
|
|
|
310
310
|
private mouseButtonsJustUp = new Set<number>();
|
|
311
311
|
private mouseDeltaX = 0;
|
|
312
312
|
private mouseDeltaY = 0;
|
|
313
|
+
/** Last `mousemove` client position. Device-poll only — action maps read delta, not this. */
|
|
314
|
+
private mouseClientX = 0;
|
|
315
|
+
private mouseClientY = 0;
|
|
313
316
|
private gamepads: (Gamepad | null)[] = [];
|
|
314
317
|
// Gamepad button edge tracking (P1.6c). Keys are `${gamepadIndex}:${buttonIndex}`.
|
|
315
318
|
// `Prev` is last frame's pressed set; `Down` is this frame's; the just-down/up
|
|
@@ -568,6 +571,8 @@ export class InputManager {
|
|
|
568
571
|
if (e.movementX !== 0 || e.movementY !== 0) this.lastActiveDevice = 'mouse';
|
|
569
572
|
this.mouseDeltaX += e.movementX;
|
|
570
573
|
this.mouseDeltaY += e.movementY;
|
|
574
|
+
this.mouseClientX = e.clientX;
|
|
575
|
+
this.mouseClientY = e.clientY;
|
|
571
576
|
};
|
|
572
577
|
|
|
573
578
|
/** F2 — real window blur: drop focus and flush held REAL-device state
|
|
@@ -674,10 +679,32 @@ export class InputManager {
|
|
|
674
679
|
* data reaches `isPressed`/`isJustPressed`/`isJustReleased`.
|
|
675
680
|
*/
|
|
676
681
|
async loadMap(url: string) {
|
|
682
|
+
await this.fetchMap(url, false);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* `loadMap`'s OPTIONAL sibling: apply the map at `url` when the project
|
|
687
|
+
* ships one and report `false` — SILENTLY — when it does not exist.
|
|
688
|
+
*
|
|
689
|
+
* The host loads the conventional map path at mount for every three/canvas
|
|
690
|
+
* root, including the scaffolded shape that has declared no actions yet, so
|
|
691
|
+
* "no map on disk" is the ordinary state of a brand-new project and must not
|
|
692
|
+
* print an error on every boot. Everything else stays exactly as loud as
|
|
693
|
+
* `loadMap`: a map that EXISTS and is malformed (or any non-404 failure)
|
|
694
|
+
* still logs and throws.
|
|
695
|
+
*/
|
|
696
|
+
async loadMapIfPresent(url: string): Promise<boolean> {
|
|
697
|
+
return this.fetchMap(url, true);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/** Shared fetch/validate/apply for {@link loadMap} and
|
|
701
|
+
* {@link loadMapIfPresent}; `optional` decides only what a 404 means. */
|
|
702
|
+
private async fetchMap(url: string, optional: boolean): Promise<boolean> {
|
|
677
703
|
let data: InputMapFile;
|
|
678
704
|
try {
|
|
679
705
|
const res = await fetch(resolveUrl(url));
|
|
680
706
|
if (!res.ok) {
|
|
707
|
+
if (optional && res.status === 404) return false;
|
|
681
708
|
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
682
709
|
}
|
|
683
710
|
const json = await res.json();
|
|
@@ -692,6 +719,7 @@ export class InputManager {
|
|
|
692
719
|
throw err instanceof AssetParseError ? err : new Error(message);
|
|
693
720
|
}
|
|
694
721
|
this.applyParsedMap(data);
|
|
722
|
+
return true;
|
|
695
723
|
}
|
|
696
724
|
|
|
697
725
|
/**
|
|
@@ -1690,6 +1718,99 @@ export class InputManager {
|
|
|
1690
1718
|
return { x: this.mouseDeltaX, y: this.mouseDeltaY };
|
|
1691
1719
|
}
|
|
1692
1720
|
|
|
1721
|
+
/**
|
|
1722
|
+
* Device-poll door (not an action binding). Every getter checks
|
|
1723
|
+
* {@link inputActive} at READ time so a poll while play is off or the
|
|
1724
|
+
* tab is unfocused is inert — the same predicate `onKeyDown` / `onMouseMove`
|
|
1725
|
+
* already use. Inventing an implicit `registerAction` per key would define
|
|
1726
|
+
* bindings the project's own `.inputmap` never named.
|
|
1727
|
+
*/
|
|
1728
|
+
isDeviceKeyPressed(code: string): boolean {
|
|
1729
|
+
if (!this.inputActive) return false;
|
|
1730
|
+
return this.keysDown.has(code);
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
isDeviceKeyJustPressed(code: string): boolean {
|
|
1734
|
+
if (!this.inputActive) return false;
|
|
1735
|
+
return this.keysJustDown.has(code);
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
isDeviceKeyJustReleased(code: string): boolean {
|
|
1739
|
+
if (!this.inputActive) return false;
|
|
1740
|
+
return this.keysJustUp.has(code);
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
/** `button` is the DOM `MouseEvent.button` index (0 left, 1 middle, 2 right). */
|
|
1744
|
+
isDeviceMouseButtonPressed(button: number): boolean {
|
|
1745
|
+
if (!this.inputActive) return false;
|
|
1746
|
+
return this.mouseButtons.has(button);
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
isDeviceMouseButtonJustPressed(button: number): boolean {
|
|
1750
|
+
if (!this.inputActive) return false;
|
|
1751
|
+
return this.mouseButtonsJustDown.has(button);
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
isDeviceMouseButtonJustReleased(button: number): boolean {
|
|
1755
|
+
if (!this.inputActive) return false;
|
|
1756
|
+
return this.mouseButtonsJustUp.has(button);
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
/**
|
|
1760
|
+
* This-frame mouse delta in DOM space (Y +down). Callers that need Unity
|
|
1761
|
+
* `Pointer.delta` (Y-up) flip Y themselves.
|
|
1762
|
+
*/
|
|
1763
|
+
getDeviceMouseDelta(): { x: number; y: number } {
|
|
1764
|
+
if (!this.inputActive) return { x: 0, y: 0 };
|
|
1765
|
+
return { x: this.mouseDeltaX, y: this.mouseDeltaY };
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
/** Last `mousemove` client position in DOM space (origin top-left, Y +down). */
|
|
1769
|
+
getDeviceMousePosition(): { x: number; y: number } {
|
|
1770
|
+
if (!this.inputActive) return { x: 0, y: 0 };
|
|
1771
|
+
return { x: this.mouseClientX, y: this.mouseClientY };
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
hasDeviceGamepad(): boolean {
|
|
1775
|
+
if (!this.inputActive) return false;
|
|
1776
|
+
return this.gamepads.some((pad) => pad !== null);
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
isDeviceGamepadButtonPressed(button: number): boolean {
|
|
1780
|
+
if (!this.inputActive) return false;
|
|
1781
|
+
const index = this.firstConnectedGamepadIndex();
|
|
1782
|
+
if (index < 0) return false;
|
|
1783
|
+
return this.gamepadButtonsDown.has(`${index}:${button}`);
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
isDeviceGamepadButtonJustPressed(button: number): boolean {
|
|
1787
|
+
if (!this.inputActive) return false;
|
|
1788
|
+
const index = this.firstConnectedGamepadIndex();
|
|
1789
|
+
if (index < 0) return false;
|
|
1790
|
+
return this.gamepadButtonsJustDown.has(`${index}:${button}`);
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
isDeviceGamepadButtonJustReleased(button: number): boolean {
|
|
1794
|
+
if (!this.inputActive) return false;
|
|
1795
|
+
const index = this.firstConnectedGamepadIndex();
|
|
1796
|
+
if (index < 0) return false;
|
|
1797
|
+
return this.gamepadButtonsJustUp.has(`${index}:${button}`);
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
getDeviceGamepadAxis(axis: number): number {
|
|
1801
|
+
if (!this.inputActive) return 0;
|
|
1802
|
+
const index = this.firstConnectedGamepadIndex();
|
|
1803
|
+
if (index < 0) return 0;
|
|
1804
|
+
return this.gamepads[index]?.axes[axis] ?? 0;
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
private firstConnectedGamepadIndex(): number {
|
|
1808
|
+
for (let i = 0; i < this.gamepads.length; i++) {
|
|
1809
|
+
if (this.gamepads[i] !== null) return i;
|
|
1810
|
+
}
|
|
1811
|
+
return -1;
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1693
1814
|
/**
|
|
1694
1815
|
* F4 (spec §12 "Schema and Examples" — the F3 prompt-switching gap): which
|
|
1695
1816
|
* device family most recently produced real input, or `null` before any
|
|
@@ -2295,6 +2416,30 @@ export class InputManager {
|
|
|
2295
2416
|
element.addEventListener('click', this.onPointerLockClick);
|
|
2296
2417
|
}
|
|
2297
2418
|
|
|
2419
|
+
/**
|
|
2420
|
+
* Release pointer lock on the element {@link requestPointerLock} wired.
|
|
2421
|
+
*
|
|
2422
|
+
* Godot's `Input.mouse_mode = MOUSE_MODE_VISIBLE` is this call: drop the
|
|
2423
|
+
* lock if this manager holds it, and unwire the click-to-lock listener so a
|
|
2424
|
+
* later click does not recapture. Idempotent. A host with no
|
|
2425
|
+
* `document.exitPointerLock` (headless) still unwires — the lock never
|
|
2426
|
+
* existed there.
|
|
2427
|
+
*
|
|
2428
|
+
* Only the lock THIS manager requested is released. An editor viewport
|
|
2429
|
+
* holding lock on a different element is not ours to drop.
|
|
2430
|
+
*/
|
|
2431
|
+
exitPointerLock() {
|
|
2432
|
+
const el = this.pointerLockElement;
|
|
2433
|
+
if (el) {
|
|
2434
|
+
el.removeEventListener('click', this.onPointerLockClick);
|
|
2435
|
+
this.pointerLockElement = null;
|
|
2436
|
+
}
|
|
2437
|
+
this.pointerLocked = false;
|
|
2438
|
+
const doc = typeof document !== 'undefined' ? document : undefined;
|
|
2439
|
+
if (!doc || typeof doc.exitPointerLock !== 'function') return;
|
|
2440
|
+
if (el && doc.pointerLockElement === el) doc.exitPointerLock();
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2298
2443
|
/** Clean up event listeners */
|
|
2299
2444
|
dispose() {
|
|
2300
2445
|
if (this.disposed) return;
|
package/src/manifest/load.ts
CHANGED
|
@@ -79,9 +79,6 @@ export interface ResolvedAdapterRoot {
|
|
|
79
79
|
readonly entry: string | undefined;
|
|
80
80
|
readonly zOrder: number;
|
|
81
81
|
readonly pausable: boolean;
|
|
82
|
-
/** `dev: true` — this root is a DEV LAYER, not shipped game content. See
|
|
83
|
-
* `manifest/schema.ts`'s `dev` field for the four readers. */
|
|
84
|
-
readonly dev: boolean;
|
|
85
82
|
readonly loop: 'gated' | 'self-driven';
|
|
86
83
|
}
|
|
87
84
|
|
|
@@ -256,9 +253,6 @@ function checkEngineVersionPin(version: string): void {
|
|
|
256
253
|
* resolution; `entry` drives the entry cross-field rule and is
|
|
257
254
|
* carried through; `zOrder` drives the final sort; `pausable`/`loop` are
|
|
258
255
|
* carried through as-is (their consumers are the runtime/CLI, T3.2/T3.3);
|
|
259
|
-
* `dev` is carried through for the four dev-layer readers (`mountManifestRoots`'s
|
|
260
|
-
* ship gate, the host's topmost stacking pass, the play compositor's capture
|
|
261
|
-
* exclusion, and the editor's chrome-not-content seam);
|
|
262
256
|
* `description` (T3.3
|
|
263
257
|
* slice 3) is carried through as-is — its consumer today is
|
|
264
258
|
* `resolveIngestDescriptor` (packages/editor/src/adapter-resolver.ts), which
|
|
@@ -276,7 +270,6 @@ function resolveRoot(root: AdapterRoot): ResolvedAdapterRoot {
|
|
|
276
270
|
entry: root.entry,
|
|
277
271
|
zOrder: root.zOrder,
|
|
278
272
|
pausable: root.pausable,
|
|
279
|
-
dev: root.dev,
|
|
280
273
|
loop: root.loop,
|
|
281
274
|
};
|
|
282
275
|
}
|
package/src/manifest/schema.ts
CHANGED
|
@@ -315,19 +315,6 @@ export const AdapterRootSchema = z
|
|
|
315
315
|
.boolean()
|
|
316
316
|
.default(true)
|
|
317
317
|
.describe('Whether play-mode pause/step applies to this root'),
|
|
318
|
-
dev: z
|
|
319
|
-
.boolean()
|
|
320
|
-
.default(false)
|
|
321
|
-
.describe(
|
|
322
|
-
"Marks this root as a DEV LAYER: the game's own dev GUI, not shipped game content. " +
|
|
323
|
-
'Four readers act on it. `mountManifestRoots` mounts it only when dev layers are ' +
|
|
324
|
-
'enabled (`devLayersEnabled`, runtime/dev-layers.ts), so a production build carries ' +
|
|
325
|
-
'no dev GUI; the host stacks every dev root ABOVE every non-dev root (a dev layer ' +
|
|
326
|
-
'sits topmost, and as a DOM layer it steals no input from the game while closed); ' +
|
|
327
|
-
'the play compositor EXCLUDES dev layers from a capture unless the capture asks for ' +
|
|
328
|
-
'them; and the editor mounts a dev root as edit-time CHROME rather than authorable ' +
|
|
329
|
-
'content (no hierarchy entry, no OID stamping, no JSX write-back, not selectable).',
|
|
330
|
-
),
|
|
331
318
|
loop: z
|
|
332
319
|
.enum(['gated', 'self-driven'])
|
|
333
320
|
.default('gated')
|
|
@@ -475,17 +462,9 @@ export const GameManifestSchema = z
|
|
|
475
462
|
// root): a second root of the same medium parsed cleanly and was then
|
|
476
463
|
// silently never mounted, which is the failure mode this rejects.
|
|
477
464
|
//
|
|
478
|
-
// DEV LAYERS ARE COUNTED SEPARATELY. A `dev: true` root is not content
|
|
479
|
-
// competing for the medium — it is the game's dev GUI stacked above
|
|
480
|
-
// everything (`create-runtime.ts`'s stacking pass), and the editor
|
|
481
|
-
// mounts it as chrome, not as the one dom document. Counting it with
|
|
482
|
-
// the content roots would make the ratified shape — a game with a HUD
|
|
483
|
-
// and a dev panel, both `dom` — unrepresentable. The one-per-medium
|
|
484
|
-
// rule still applies WITHIN the dev band, for the same
|
|
485
|
-
// silently-never-mounted reason.
|
|
486
465
|
const idsByMedium = new Map<string, string[]>();
|
|
487
466
|
for (const root of roots) {
|
|
488
|
-
const medium =
|
|
467
|
+
const medium = rootMedium(root);
|
|
489
468
|
const ids = idsByMedium.get(medium);
|
|
490
469
|
if (ids) ids.push(root.id);
|
|
491
470
|
else idsByMedium.set(medium, [root.id]);
|
|
@@ -507,9 +486,7 @@ export const GameManifestSchema = z
|
|
|
507
486
|
"The Game's explicit, non-empty adapter-root composition. There is no implicit root. " +
|
|
508
487
|
'Root ids are unique, and a project declares at most ONE world root per medium — ' +
|
|
509
488
|
'one `three`, one `dom`, one canvas-surface root (first-party canvas, canvas ingest ' +
|
|
510
|
-
'and canvas module are the same medium).
|
|
511
|
-
'band: a dev layer is chrome stacked above the game, not content competing for the ' +
|
|
512
|
-
'medium, so a game may declare a `dom` HUD and a `dom` dev layer — but not two of either.',
|
|
489
|
+
'and canvas module are the same medium).',
|
|
513
490
|
),
|
|
514
491
|
authoring: z
|
|
515
492
|
.object({
|