@vgai/engine 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,22 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The game-scoped debug/synthetic-player registry
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* name-collision-checked) across every world, not just the one that
|
|
2
|
+
* The game-scoped debug/synthetic-player registry. ONE registry per `Game` root —
|
|
3
|
+
* every world's `ctx.debug` (see `DebugCtxSurface`, `runtime/types.ts`) feeds the
|
|
4
|
+
* SAME registry via {@link DebugRegistry.forRoot}, so a name a game registers is
|
|
5
|
+
* visible (and name-collision-checked) across every world, not just the one that
|
|
7
6
|
* registered it. `createGame` (`runtime/game.ts`) constructs the registry
|
|
8
|
-
* alongside the state bridge and files it in the `Game -> DebugRegistry`
|
|
9
|
-
*
|
|
7
|
+
* alongside the state bridge and files it in the `Game -> DebugRegistry` WeakMap
|
|
8
|
+
* this module owns; later consumers (the react `useDebugProvider`/
|
|
10
9
|
* `useDebugCommand` hooks, editor panels) reach it via {@link getDebugRegistry}
|
|
11
10
|
* rather than threading it through every call site.
|
|
12
11
|
*
|
|
13
12
|
* Provenance note: a registration's "world" is the MOUNT's adapter id (the
|
|
14
|
-
* `fromSetup(id, ...)` / `
|
|
15
|
-
* world's entry with) rather than the `
|
|
13
|
+
* `fromSetup(id, ...)` / `SetupThreeRootConfig.id` a project already names each
|
|
14
|
+
* world's entry with) rather than the `RootInstance.id` `registerThreeRoot`
|
|
16
15
|
* assigns — that id isn't known until AFTER `mount()` resolves (`create-
|
|
17
|
-
* runtime.ts` calls `
|
|
16
|
+
* runtime.ts` calls `registerThreeRoot` with it only once `mount()` returns),
|
|
18
17
|
* which is after every `setup()`-time registration has already run. For the
|
|
19
|
-
* single-world case (nearly every project today) this is simply `'
|
|
18
|
+
* single-world case (nearly every project today) this is simply `'setup-three'`;
|
|
20
19
|
* a multi-world manifest names each world's adapter to match its declared id
|
|
21
20
|
* by convention, so collision messages stay meaningful in practice.
|
|
22
21
|
*/
|
|
@@ -46,6 +45,26 @@ export class DebugError extends Error {
|
|
|
46
45
|
const REGISTRATION_HINT =
|
|
47
46
|
'ctx.debug.registerStateProvider(name, fn) — see the template example component';
|
|
48
47
|
|
|
48
|
+
/** A thrown value flattened to JSON-SAFE fields. Every caller of a debug
|
|
49
|
+
* command is on the far side of a JSON boundary — the editor session relay
|
|
50
|
+
* (`POST /__editor/command`) and `page.evaluate` both serialize the error's
|
|
51
|
+
* `data` bag — and an `Error` stringifies to `{}` there, so a command's own
|
|
52
|
+
* refusal text ("no such waypoint") vanished and the developer at
|
|
53
|
+
* `vgai eval` saw only `debug: command "x" threw`. The text has to travel as
|
|
54
|
+
* plain strings, and in the `message` above all: that is the one field every
|
|
55
|
+
* client (`SessionError`, the CLI's own error print) actually surfaces. */
|
|
56
|
+
function describeCause(cause: unknown): {
|
|
57
|
+
causeName: string;
|
|
58
|
+
causeMessage: string;
|
|
59
|
+
causeStack?: string;
|
|
60
|
+
} {
|
|
61
|
+
if (cause instanceof Error) {
|
|
62
|
+
const described = { causeName: cause.name, causeMessage: cause.message };
|
|
63
|
+
return cause.stack === undefined ? described : { ...described, causeStack: cause.stack };
|
|
64
|
+
}
|
|
65
|
+
return { causeName: typeof cause, causeMessage: String(cause) };
|
|
66
|
+
}
|
|
67
|
+
|
|
49
68
|
/** Task 2.2 — how long `invoke()` waits for a `locus: 'server'` command's
|
|
50
69
|
* `__vgai:debugCommandResult` reply before failing loudly. */
|
|
51
70
|
const SERVER_COMMAND_TIMEOUT_MS = 10_000;
|
|
@@ -54,7 +73,7 @@ const SERVER_COMMAND_TIMEOUT_MS = 10_000;
|
|
|
54
73
|
* actuates through — the SAME three methods `InputManager` exposes
|
|
55
74
|
* (`setVirtualAction`/`tapVirtualAction`/`clearVirtualActions`, Task 1.4),
|
|
56
75
|
* typed narrowly here so this module never imports `InputManager` itself.
|
|
57
|
-
* Wired once by the hosting adapter (`
|
|
76
|
+
* Wired once by the hosting adapter (`setup-three-root-adapter.ts`, at the
|
|
58
77
|
* same seed spot as `setInputActionsSource`) — absent until then. */
|
|
59
78
|
export interface DebugVirtualInputTarget {
|
|
60
79
|
setVirtualAction(
|
|
@@ -63,9 +82,9 @@ export interface DebugVirtualInputTarget {
|
|
|
63
82
|
): { delivered: boolean; reason?: string };
|
|
64
83
|
tapVirtualAction(action: string): { delivered: boolean; reason?: string };
|
|
65
84
|
clearVirtualActions(): void;
|
|
66
|
-
/** D15/T-D15.5
|
|
67
|
-
*
|
|
68
|
-
*
|
|
85
|
+
/** D15/T-D15.5 — schedule a virtual actuation for a specific future (or
|
|
86
|
+
* current) tick, applied at the start of that tick's input phase (composes
|
|
87
|
+
* with `runTicks`). */
|
|
69
88
|
scheduleActionAtTick(
|
|
70
89
|
tick: number,
|
|
71
90
|
action: string,
|
|
@@ -85,8 +104,8 @@ export interface DebugVirtualInputTarget {
|
|
|
85
104
|
}
|
|
86
105
|
|
|
87
106
|
/**
|
|
88
|
-
* D15/T-D15.5 — the `input.trace` builtin provider's shape
|
|
89
|
-
*
|
|
107
|
+
* D15/T-D15.5 — the `input.trace` builtin provider's shape.
|
|
108
|
+
* `seed`/`fixedDt` are
|
|
90
109
|
* replay-critical metadata a future SP5 consumer needs BESIDE the raw
|
|
91
110
|
* per-tick action deltas (`ticks`, straight off `InputManager.getInputTrace()`)
|
|
92
111
|
* to know what to replay the trace AGAINST — recording deltas alone is not
|
|
@@ -132,12 +151,12 @@ interface PendingServerCommand {
|
|
|
132
151
|
* (spec §3.1's "react-world registration door" paragraph), so all of them
|
|
133
152
|
* share this one provenance string; a name collision between two DIFFERENT
|
|
134
153
|
* react components is therefore the same-world "replace + warn once" case,
|
|
135
|
-
* never the cross-world throw (which is reserved for a
|
|
154
|
+
* never the cross-world throw (which is reserved for a first-party world
|
|
136
155
|
* vs. the react door genuinely disagreeing about a name).
|
|
137
156
|
*
|
|
138
157
|
* Defect 7 fix: this used to be the bare string `'react'`, which collided in
|
|
139
158
|
* provenance with a manifest world literally named `'react'` (e.g. a
|
|
140
|
-
* `kind: '
|
|
159
|
+
* `kind: 'dom'` world whose `id` is `"react"`) — `forRoot('react')` and
|
|
141
160
|
* the react door would then be treated as the SAME world for collision
|
|
142
161
|
* purposes, which is wrong (they are genuinely different registrants that
|
|
143
162
|
* happen to share a display name). Namespaced like `'__engine__'` so no
|
|
@@ -167,14 +186,14 @@ interface CommandEntry {
|
|
|
167
186
|
|
|
168
187
|
/** What {@link createDebugRegistry} returns — the adapter half (`DebugAdapter`,
|
|
169
188
|
* for `SystemAdapters.debug`) plus the registration/lifecycle surface the
|
|
170
|
-
* hosting adapter (`
|
|
189
|
+
* hosting adapter (`setup-three-root-adapter.ts`) and `createGame` drive. */
|
|
171
190
|
export interface DebugRegistry {
|
|
172
191
|
/** The `SystemAdapters.debug` implementer — one shared instance, seeded
|
|
173
192
|
* onto every world's adapter bag. */
|
|
174
193
|
readonly adapter: DebugAdapter;
|
|
175
194
|
/** Build the `ctx.debug` surface for one world/mount — registrations made
|
|
176
195
|
* through it carry `worldId` as their provenance for collision messages. */
|
|
177
|
-
|
|
196
|
+
forRoot(worldId: string): DebugCtxSurface;
|
|
178
197
|
/** Emit from a React root through the same tick-stamped event ring as
|
|
179
198
|
* `ctx.debug.emit`. React roots have no setup context, so
|
|
180
199
|
* `useDebugEmit()` calls this dedicated door instead of reaching through
|
|
@@ -208,7 +227,7 @@ export interface DebugRegistry {
|
|
|
208
227
|
* exist yet when the registry is constructed (`createGame`, before any
|
|
209
228
|
* world mounts). Before ANY source is set, `input.actions` reads `[]`;
|
|
210
229
|
* once one or more roots have registered, the built-in `input.actions`
|
|
211
|
-
* provider reads the DEFAULT world's (see {@link
|
|
230
|
+
* provider reads the DEFAULT world's (see {@link resolveInputRootId}) —
|
|
212
231
|
* same resolution every other per-world seam on this interface uses. */
|
|
213
232
|
setInputActionsSource(worldId: string, fn: () => { name: string; valueType: string }[]): void;
|
|
214
233
|
/** D15/T-D15.5 — wire the built-in `input.trace` provider to a live
|
|
@@ -218,7 +237,7 @@ export interface DebugRegistry {
|
|
|
218
237
|
* `{version: 1, seed: null, fixedDt: null, ticks: []}`. `seed`/`fixedDt`
|
|
219
238
|
* are the two replay-critical metadata fields the design doc's format
|
|
220
239
|
* sketch (§2.c) calls for beside the raw per-tick deltas — the wiring
|
|
221
|
-
* adapter (`
|
|
240
|
+
* adapter (`setup-three-root-adapter.ts`) assembles them from
|
|
222
241
|
* `getSeededRandom(game)?.seed`/`host.game?.loop.fixedDt` alongside
|
|
223
242
|
* `InputManager.getInputTrace()`'s own `{version, ticks}`. An `engine`
|
|
224
243
|
* (package version) stamp remains a KNOWN GAP — no build-time version
|
|
@@ -239,14 +258,14 @@ export interface DebugRegistry {
|
|
|
239
258
|
* multi-world project — the bridge always got whichever world mounted
|
|
240
259
|
* LAST, the relay always got the FIRST/default world. Now every world's
|
|
241
260
|
* target is kept, keyed by `worldId`, and {@link getVirtualInputTarget}
|
|
242
|
-
* resolves ONE of them via {@link
|
|
261
|
+
* resolves ONE of them via {@link resolveInputRootId} — the SAME
|
|
243
262
|
* resolution the bridge and the relay both call through, so they can never
|
|
244
263
|
* disagree again. An explicit `worldId` reaches that world specifically. */
|
|
245
264
|
setVirtualInputTarget(worldId: string, target: DebugVirtualInputTarget): void;
|
|
246
265
|
/**
|
|
247
266
|
* Resolve and return a virtual-input target: `worldId` given and
|
|
248
267
|
* registered → that world's; omitted → the DEFAULT world's (per
|
|
249
|
-
* {@link
|
|
268
|
+
* {@link resolveInputRootId} — the manifest's first/default world when a
|
|
250
269
|
* `Game` is behind this registry, else the single registered world, else
|
|
251
270
|
* whichever registered first), consistently, for every caller (the debug
|
|
252
271
|
* bridge and the editor relay both call this — see this interface's own
|
|
@@ -277,13 +296,13 @@ export interface DebugRegistry {
|
|
|
277
296
|
/** D15/T-D15.3/.5 — the CURRENT shared game tick (the same counter the
|
|
278
297
|
* built-in `time` provider's `tick` field reads), for a per-world
|
|
279
298
|
* `InputManager.poll(tick)` call to key its `scheduleActionAtTick`
|
|
280
|
-
* numbering off — see `
|
|
299
|
+
* numbering off — see `setup-three-root-adapter.ts`'s `systems.add('input',
|
|
281
300
|
* ...)` wiring. `0` for a bare `createDebugRegistry()` test stand-in with
|
|
282
301
|
* no real `Game`/tick counter behind it (matching `getTick`'s own
|
|
283
302
|
* constructor-supplied default in that case). */
|
|
284
303
|
getGameTick(): number;
|
|
285
304
|
/** React-door registration (Task 1.5, spec §3.1's "react-world
|
|
286
|
-
* registration door") — same accumulator as `
|
|
305
|
+
* registration door") — same accumulator as `forRoot(...)
|
|
287
306
|
* .registerStateProvider`, provenance `'__react__'` (see {@link REACT_WORLD_ID}),
|
|
288
307
|
* but returns a disposer instead of requiring a separate unregister call.
|
|
289
308
|
* Calling the disposer removes the registration ONLY if it is still the
|
|
@@ -322,13 +341,13 @@ function warnOnce(
|
|
|
322
341
|
* Construct a fresh game-scoped debug registry. `getTick`/`getSimT` are
|
|
323
342
|
* suppliers (not values) so the built-in `time` provider always reads the
|
|
324
343
|
* CURRENT counters — `createGame` passes closures over its own mutable
|
|
325
|
-
* `tick`/`simT`, incremented in `runFrame`'s tail (T1.2). `
|
|
344
|
+
* `tick`/`simT`, incremented in `runFrame`'s tail (T1.2). `getDefaultRootId`
|
|
326
345
|
* (D15/T-D15.5, optional) resolves the manifest's first/default world id —
|
|
327
|
-
* `createGame` passes `() => (roots.length ?
|
|
346
|
+
* `createGame` passes `() => (roots.length ? requireDefaultRoot().id :
|
|
328
347
|
* null)`; a bare `createDebugRegistry()` test stand-in with no `Game` behind
|
|
329
348
|
* it omits it (per-world resolution then falls back to "the single
|
|
330
349
|
* registered world" or "whichever registered first" — see
|
|
331
|
-
* `
|
|
350
|
+
* `resolveInputRootId`). `getLoopLiveness` (issue #175, optional) supplies
|
|
332
351
|
* the REAL `GameLoop.liveness` — `createGame` passes `() => opts.loop.
|
|
333
352
|
* liveness`; a bare `createDebugRegistry()` test stand-in with no loop
|
|
334
353
|
* behind it omits it, and the built-in `time` provider reports `null`
|
|
@@ -338,7 +357,7 @@ function warnOnce(
|
|
|
338
357
|
export function createDebugRegistry(opts: {
|
|
339
358
|
getTick(): number;
|
|
340
359
|
getSimT(): number;
|
|
341
|
-
|
|
360
|
+
getDefaultRootId?(): string | null;
|
|
342
361
|
getLoopLiveness?(): GameLoopLiveness;
|
|
343
362
|
}): DebugRegistry {
|
|
344
363
|
const providers = new Map<string, ProviderEntry>();
|
|
@@ -356,8 +375,8 @@ export function createDebugRegistry(opts: {
|
|
|
356
375
|
|
|
357
376
|
let roomDeclared = false;
|
|
358
377
|
// D15/T-D15.5 — per-world maps (Map preserves insertion order, which
|
|
359
|
-
// `
|
|
360
|
-
// when no `
|
|
378
|
+
// `resolveInputRootId`'s "whichever registered first" fallback relies on
|
|
379
|
+
// when no `getDefaultRootId` is available to disambiguate).
|
|
361
380
|
const inputActionsSources = new Map<string, () => { name: string; valueType: string }[]>();
|
|
362
381
|
const inputTraceSources = new Map<string, () => InputTraceSnapshot>();
|
|
363
382
|
const virtualInputTargets = new Map<string, DebugVirtualInputTarget>();
|
|
@@ -375,7 +394,7 @@ export function createDebugRegistry(opts: {
|
|
|
375
394
|
* `DEBUG_INPUT_WORLD_NOT_FOUND` (a caller mistake — distinct from
|
|
376
395
|
* "nothing mounted yet", which returns `null` below instead of throwing).
|
|
377
396
|
* - `explicit` omitted: the manifest's first/default world id
|
|
378
|
-
* (`opts.
|
|
397
|
+
* (`opts.getDefaultRootId()`) iff that world has actually registered —
|
|
379
398
|
* else (no `Game`/no default resolvable, or the default world never
|
|
380
399
|
* wired one — e.g. a foreign/opaque mount) the single registered world,
|
|
381
400
|
* or, with more than one and no resolvable default, whichever registered
|
|
@@ -384,7 +403,7 @@ export function createDebugRegistry(opts: {
|
|
|
384
403
|
* instead of "whichever mounted last".
|
|
385
404
|
* - Nothing registered at all: `null`.
|
|
386
405
|
*/
|
|
387
|
-
function
|
|
406
|
+
function resolveInputRootId(explicit?: string): string | null {
|
|
388
407
|
if (explicit !== undefined) {
|
|
389
408
|
if (!virtualInputTargets.has(explicit)) {
|
|
390
409
|
throw new DebugError(
|
|
@@ -396,11 +415,11 @@ export function createDebugRegistry(opts: {
|
|
|
396
415
|
}
|
|
397
416
|
return explicit;
|
|
398
417
|
}
|
|
399
|
-
return
|
|
418
|
+
return resolveRootForSource(virtualInputTargets.keys());
|
|
400
419
|
}
|
|
401
420
|
|
|
402
421
|
/** The same "default world, else the one registered, else whichever
|
|
403
|
-
* registered first" fallback {@link
|
|
422
|
+
* registered first" fallback {@link resolveInputRootId} uses for the
|
|
404
423
|
* ACTUATION target, generalized over any per-world registration set
|
|
405
424
|
* (`inputActionsSources`/`inputTraceSources` included) — every one of
|
|
406
425
|
* these maps is keyed by the same world ids, populated at the same
|
|
@@ -408,10 +427,10 @@ export function createDebugRegistry(opts: {
|
|
|
408
427
|
* for all of them. Never throws (no explicit-id case here — the two
|
|
409
428
|
* builtin providers that call this have no way to accept a caller-chosen
|
|
410
429
|
* worldId today; see their own doc comments). */
|
|
411
|
-
function
|
|
430
|
+
function resolveRootForSource(registered: IterableIterator<string>): string | null {
|
|
412
431
|
const ids = [...registered];
|
|
413
432
|
if (ids.length === 0) return null;
|
|
414
|
-
const defaultId = opts.
|
|
433
|
+
const defaultId = opts.getDefaultRootId?.() ?? null;
|
|
415
434
|
if (defaultId !== null && ids.includes(defaultId)) return defaultId;
|
|
416
435
|
return ids[0]!;
|
|
417
436
|
}
|
|
@@ -431,11 +450,11 @@ export function createDebugRegistry(opts: {
|
|
|
431
450
|
builtin: true,
|
|
432
451
|
});
|
|
433
452
|
providers.set('input.actions', {
|
|
434
|
-
// Resolves to the DEFAULT world's action list (see `
|
|
453
|
+
// Resolves to the DEFAULT world's action list (see `resolveInputRootId`)
|
|
435
454
|
// — deterministic across every world that registers, rather than the
|
|
436
455
|
// pre-D15.5 "whichever mounted last" behavior.
|
|
437
456
|
fn: () => {
|
|
438
|
-
const worldId =
|
|
457
|
+
const worldId = resolveRootForSource(inputActionsSources.keys());
|
|
439
458
|
return (worldId ? inputActionsSources.get(worldId) : undefined)?.() ?? [];
|
|
440
459
|
},
|
|
441
460
|
tier: 'observable',
|
|
@@ -448,7 +467,7 @@ export function createDebugRegistry(opts: {
|
|
|
448
467
|
// `input.actions` immediately above.
|
|
449
468
|
providers.set('input.trace', {
|
|
450
469
|
fn: () => {
|
|
451
|
-
const worldId =
|
|
470
|
+
const worldId = resolveRootForSource(inputTraceSources.keys());
|
|
452
471
|
return (
|
|
453
472
|
(worldId ? inputTraceSources.get(worldId) : undefined)?.() ?? {
|
|
454
473
|
version: 1,
|
|
@@ -544,12 +563,11 @@ export function createDebugRegistry(opts: {
|
|
|
544
563
|
}
|
|
545
564
|
|
|
546
565
|
/** See `DebugAdapter.events`'s doc comment (`adapter/system-adapter.ts`)
|
|
547
|
-
* for the full contract — `
|
|
548
|
-
*
|
|
549
|
-
function events(
|
|
550
|
-
if (sinceSeq
|
|
551
|
-
|
|
552
|
-
return ring.filter((e) => e.tick > sinceTick);
|
|
566
|
+
* for the full contract — `seq` is the ONLY fence (the defective
|
|
567
|
+
* `sinceTick` filter was removed). */
|
|
568
|
+
function events(sinceSeq?: number): TickStampedEvent[] {
|
|
569
|
+
if (sinceSeq === undefined) return ring.slice();
|
|
570
|
+
return ring.filter((e) => e.seq > sinceSeq);
|
|
553
571
|
}
|
|
554
572
|
|
|
555
573
|
/**
|
|
@@ -728,17 +746,24 @@ export function createDebugRegistry(opts: {
|
|
|
728
746
|
try {
|
|
729
747
|
return await entry.fn(...parsedArgs);
|
|
730
748
|
} catch (cause) {
|
|
731
|
-
|
|
749
|
+
// `cause` stays for in-process readers; the flattened fields are what
|
|
750
|
+
// survive the wire (see `describeCause`).
|
|
751
|
+
const described = describeCause(cause);
|
|
752
|
+
throw new DebugError(
|
|
753
|
+
'DEBUG_COMMAND_FAILED',
|
|
754
|
+
`debug: command "${name}" threw: ${described.causeMessage}`,
|
|
755
|
+
{ cause, ...described },
|
|
756
|
+
);
|
|
732
757
|
}
|
|
733
758
|
},
|
|
734
|
-
events(
|
|
735
|
-
return events(
|
|
759
|
+
events(sinceSeq?: number) {
|
|
760
|
+
return events(sinceSeq);
|
|
736
761
|
},
|
|
737
762
|
};
|
|
738
763
|
|
|
739
764
|
return {
|
|
740
765
|
adapter,
|
|
741
|
-
|
|
766
|
+
forRoot(worldId: string): DebugCtxSurface {
|
|
742
767
|
return {
|
|
743
768
|
registerStateProvider(name, fn, providerOpts) {
|
|
744
769
|
registerStateProvider(worldId, name, fn, providerOpts?.tier ?? 'observable');
|
|
@@ -796,7 +821,7 @@ export function createDebugRegistry(opts: {
|
|
|
796
821
|
virtualInputTargets.set(worldId, target);
|
|
797
822
|
},
|
|
798
823
|
getVirtualInputTarget(worldId?: string) {
|
|
799
|
-
const resolved =
|
|
824
|
+
const resolved = resolveInputRootId(worldId);
|
|
800
825
|
return resolved !== null ? (virtualInputTargets.get(resolved) ?? null) : null;
|
|
801
826
|
},
|
|
802
827
|
setRunTicksTarget(target: RunTicksTarget) {
|
|
@@ -821,7 +846,7 @@ export function createDebugRegistry(opts: {
|
|
|
821
846
|
},
|
|
822
847
|
registerReactCommand(name, spec, fn) {
|
|
823
848
|
const token = Symbol(name);
|
|
824
|
-
// See the `
|
|
849
|
+
// See the `forRoot().registerCommand` cast above — same reason.
|
|
825
850
|
registerCommand(
|
|
826
851
|
REACT_WORLD_ID,
|
|
827
852
|
name,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE one owner of "are dev layers enabled" (`vgai.project.json`'s `dev: true`
|
|
3
|
+
* root flag). Every reader that has to answer that question calls
|
|
4
|
+
* {@link devLayersEnabled}; there is deliberately NO second source of truth —
|
|
5
|
+
* not a module-level cached boolean, not a `globalThis` flag, not a per-host
|
|
6
|
+
* copy of the `import.meta.env` read below. A dev GUI that ships to players
|
|
7
|
+
* because two places disagreed about what "dev" means is exactly the failure
|
|
8
|
+
* this single owner exists to make impossible.
|
|
9
|
+
*
|
|
10
|
+
* Ownership, stated in one place (the build rule):
|
|
11
|
+
* - OWNER: this function. It resolves the answer; nothing else derives it.
|
|
12
|
+
* - SHARERS: `mountManifestRoots` (the ship gate — a dev root only mounts
|
|
13
|
+
* when this returns true). Any future host that mounts manifest roots
|
|
14
|
+
* itself calls this too, with its own `override`.
|
|
15
|
+
* - TEARDOWN: none. This is a pure predicate over build config and one
|
|
16
|
+
* caller-supplied argument — it owns no resource, allocates nothing, and
|
|
17
|
+
* has no lifecycle to end.
|
|
18
|
+
*
|
|
19
|
+
* The three inputs, highest precedence first:
|
|
20
|
+
* 1. `override` — the explicit per-mount answer. A headless test, a capture
|
|
21
|
+
* harness, or a host that knows better passes `true`/`false` and gets
|
|
22
|
+
* exactly that. Passing `undefined` (or omitting it) means "decide for
|
|
23
|
+
* me" and falls through.
|
|
24
|
+
* 2. A dev build — `import.meta.env.DEV`. The ordinary local/editor case:
|
|
25
|
+
* dev layers are on.
|
|
26
|
+
* 3. A production build's EXPLICIT opt-in — `VITE_VGAI_DEV_LAYERS=true`.
|
|
27
|
+
* Shipping the dev GUI in a production bundle is a real, legitimate
|
|
28
|
+
* choice (an internal playtest build, a QA build), and it must be an
|
|
29
|
+
* opt-in someone had to type, never something a default drifts into.
|
|
30
|
+
*
|
|
31
|
+
* Anything else — a production build with no opt-in — is `false`.
|
|
32
|
+
*/
|
|
33
|
+
export function devLayersEnabled(override?: boolean | undefined): boolean {
|
|
34
|
+
if (override !== undefined) return override;
|
|
35
|
+
const env = import.meta.env as
|
|
36
|
+
| { DEV?: boolean | undefined; VITE_VGAI_DEV_LAYERS?: string | undefined }
|
|
37
|
+
| undefined;
|
|
38
|
+
if (env?.DEV === true) return true;
|
|
39
|
+
return env?.VITE_VGAI_DEV_LAYERS === 'true';
|
|
40
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pure, react-free frame-version cache (T7.4 slice 1
|
|
3
|
-
*
|
|
2
|
+
* Pure, react-free frame-version cache (T7.4 slice 1).
|
|
3
|
+
* `useWorldState` (`packages/editor/template/src/
|
|
4
4
|
* ui/game-state.tsx`) is a thin `useSyncExternalStore` wrapper
|
|
5
5
|
* around this: `getSnapshot` re-runs the selector once per `frameVersion`
|
|
6
6
|
* bump (`GameStateBridge`, `runtime/state-bridge.ts`), caches by version,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* exists in this repo today; see `test/frame-selector-cache.test.ts`).
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
/** Equality comparator for `FrameSelectorCache`/`
|
|
18
|
+
/** Equality comparator for `FrameSelectorCache`/`useWorldState`. Defaults to
|
|
19
19
|
* `Object.is` (T7.4 §3); pass {@link shallow} for tuples/plain objects. */
|
|
20
20
|
export type Equals<T> = (a: T, b: T) => boolean;
|
|
21
21
|
|
|
@@ -52,7 +52,7 @@ export interface FrameSelectorCache<T> {
|
|
|
52
52
|
get(frameVersion: number, compute: () => T): T;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
/** Construct a fresh per-subscriber cache. One per `
|
|
55
|
+
/** Construct a fresh per-subscriber cache. One per `useWorldState` call site
|
|
56
56
|
* (a fresh cache each mount — see the hook). */
|
|
57
57
|
export function createFrameSelectorCache<T>(equals: Equals<T> = Object.is): FrameSelectorCache<T> {
|
|
58
58
|
let hasValue = false;
|