@vgai/engine 0.5.16 → 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.
Files changed (99) hide show
  1. package/dist/adapter/adapter-module.d.ts +1 -16
  2. package/dist/adapter/adapter-module.d.ts.map +1 -1
  3. package/dist/adapter/adapter-module.js +1 -11
  4. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts +0 -2
  5. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
  6. package/dist/adapter/finders/scenes-from-entrypoint-selection.js +3 -6
  7. package/dist/adapter/root-adapter.d.ts +4 -0
  8. package/dist/adapter/root-adapter.d.ts.map +1 -1
  9. package/dist/adapter/root-seam-contract.d.ts +5 -0
  10. package/dist/adapter/root-seam-contract.d.ts.map +1 -1
  11. package/dist/adapter/root-seam-contract.js +1 -0
  12. package/dist/adapter/setup-three-root-adapter.js +9 -9
  13. package/dist/canvas-react/pixi-react-adapter.d.ts.map +1 -1
  14. package/dist/canvas-react/pixi-react-adapter.js +92 -41
  15. package/dist/data/curve.d.ts +1 -1
  16. package/dist/data/curve.js +2 -2
  17. package/dist/dev/instruments.d.ts +4 -4
  18. package/dist/dev/instruments.js +4 -4
  19. package/dist/dev/register-render-vitals.d.ts +2 -2
  20. package/dist/dev/register-render-vitals.js +2 -2
  21. package/dist/dev/static-batch-advisor.d.ts +1 -1
  22. package/dist/dev/static-batch-advisor.js +2 -2
  23. package/dist/input/input-manager.d.ts +15 -0
  24. package/dist/input/input-manager.d.ts.map +1 -1
  25. package/dist/input/input-manager.js +22 -0
  26. package/dist/manifest/load.d.ts +0 -3
  27. package/dist/manifest/load.d.ts.map +1 -1
  28. package/dist/manifest/load.js +0 -4
  29. package/dist/manifest/schema.d.ts +0 -2
  30. package/dist/manifest/schema.d.ts.map +1 -1
  31. package/dist/manifest/schema.js +2 -23
  32. package/dist/pixi/authoring.d.ts +33 -0
  33. package/dist/pixi/authoring.d.ts.map +1 -1
  34. package/dist/pixi/authoring.js +86 -6
  35. package/dist/react/world-state.d.ts +12 -0
  36. package/dist/react/world-state.d.ts.map +1 -1
  37. package/dist/react/world-state.js +14 -0
  38. package/dist/render/soft-particle-depth.d.ts +3 -3
  39. package/dist/render/soft-particle-depth.d.ts.map +1 -1
  40. package/dist/render/soft-particle-depth.js +8 -13
  41. package/dist/runtime/create-runtime.d.ts +2 -16
  42. package/dist/runtime/create-runtime.d.ts.map +1 -1
  43. package/dist/runtime/create-runtime.js +36 -38
  44. package/dist/runtime/dev-build.d.ts +37 -0
  45. package/dist/runtime/dev-build.d.ts.map +1 -0
  46. package/dist/runtime/dev-build.js +49 -0
  47. package/dist/runtime/game.d.ts +9 -1
  48. package/dist/runtime/game.d.ts.map +1 -1
  49. package/dist/runtime/game.js +25 -1
  50. package/dist/runtime/mount-manifest.d.ts +0 -13
  51. package/dist/runtime/mount-manifest.d.ts.map +1 -1
  52. package/dist/runtime/mount-manifest.js +1 -20
  53. package/dist/world3d-react/engine-bridge.d.ts.map +1 -1
  54. package/dist/world3d-react/engine-bridge.js +28 -3
  55. package/dist/world3d-react/index.d.ts +1 -1
  56. package/dist/world3d-react/index.js +1 -1
  57. package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
  58. package/dist/world3d-react/r3f-adapter.js +89 -14
  59. package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
  60. package/dist/world3d-react/r3f-root-factory.js +12 -2
  61. package/dist/world3d-react/rapier-physics-bridge.d.ts +4 -4
  62. package/dist/world3d-react/rapier-physics-bridge.js +3 -3
  63. package/dist/world3d-react/world-context.d.ts +43 -0
  64. package/dist/world3d-react/world-context.d.ts.map +1 -1
  65. package/dist/world3d-react/world-context.js +89 -46
  66. package/dist-config/config.js.map +1 -1
  67. package/package.json +1 -1
  68. package/schemas/engine-capabilities.json +3 -3
  69. package/schemas/vgai-project.schema.json +1 -6
  70. package/src/adapter/adapter-module.ts +1 -25
  71. package/src/adapter/finders/scenes-from-entrypoint-selection.ts +3 -8
  72. package/src/adapter/root-adapter.ts +4 -0
  73. package/src/adapter/root-seam-contract.ts +1 -0
  74. package/src/adapter/setup-three-root-adapter.ts +9 -9
  75. package/src/canvas-react/pixi-react-adapter.tsx +98 -53
  76. package/src/data/curve.ts +2 -2
  77. package/src/dev/instruments.ts +4 -4
  78. package/src/dev/register-render-vitals.ts +2 -2
  79. package/src/dev/static-batch-advisor.ts +2 -2
  80. package/src/input/input-manager.ts +23 -0
  81. package/src/manifest/load.ts +0 -7
  82. package/src/manifest/schema.ts +2 -25
  83. package/src/pixi/authoring.ts +98 -6
  84. package/src/react/world-state.tsx +15 -0
  85. package/src/render/soft-particle-depth.ts +8 -13
  86. package/src/runtime/create-runtime.ts +38 -52
  87. package/src/runtime/dev-build.ts +49 -0
  88. package/src/runtime/game.ts +31 -2
  89. package/src/runtime/mount-manifest.ts +1 -32
  90. package/src/world3d-react/engine-bridge.ts +28 -3
  91. package/src/world3d-react/index.ts +1 -1
  92. package/src/world3d-react/r3f-adapter.tsx +87 -18
  93. package/src/world3d-react/r3f-root-factory.tsx +12 -2
  94. package/src/world3d-react/rapier-physics-bridge.tsx +4 -4
  95. package/src/world3d-react/world-context.ts +100 -51
  96. package/dist/runtime/dev-layers.d.ts +0 -34
  97. package/dist/runtime/dev-layers.d.ts.map +0 -1
  98. package/dist/runtime/dev-layers.js +0 -46
  99. package/src/runtime/dev-layers.ts +0 -46
@@ -147,6 +147,103 @@ export interface R3FRootRuntime {
147
147
  dispose(): void;
148
148
  }
149
149
 
150
+ /**
151
+ * The GAME-SCOPED input wiring one three root owns — and the half that has
152
+ * nothing to do with the vgai runtime context.
153
+ *
154
+ * Two things happen here, both of them HOST work performed from outside the
155
+ * world's own tree:
156
+ *
157
+ * 1. the project's input map is loaded through `Game.loadInputMap` (game-owned,
158
+ * load-once), so the actions the project declared exist;
159
+ * 2. this root's seams are registered on the game-scoped debug registry
160
+ * (`setVirtualInputTarget`/`setInputActionsSource`/`setInputTraceSource`),
161
+ * which is what makes `game.input.hold/tap/...` (the bot/`vgai eval` input
162
+ * doctrine) and the built-in `input.actions`/`input.trace` providers
163
+ * resolve to this world instead of throwing `DEBUG_INPUT_UNAVAILABLE`.
164
+ *
165
+ * It is called on BOTH three-root paths, and that is the point: a world that
166
+ * mounts as a default-exported component receives no vgai runtime context
167
+ * (`engineRuntime: false` — see `r3f-root-factory.tsx`), but "no context in the
168
+ * tree" was never meant to say "the host does not wire this game's input". It
169
+ * was read that way, and the cost was measured on a fresh scaffold: the
170
+ * declared map never loaded and every `game.input.*` door threw, so the QA
171
+ * bot, autoplay routes and `vgai eval` could not drive the ONE world shape
172
+ * `vgai create` produces. Same split, same reasoning, as `WorldProvider`
173
+ * riding `host.game` rather than the runtime (see `r3f-adapter.tsx`).
174
+ *
175
+ * `optionalInputMap` is the CONVENTIONAL-PATH probe used when the project
176
+ * never named a map: an absent file is the ordinary state of a brand-new
177
+ * project, so it resolves quietly instead of printing an error on every boot.
178
+ * A map that exists and fails to parse is as loud as ever.
179
+ *
180
+ * LIFETIME, honestly: `registry.strip(id)` runs on the mount's dispose (both
181
+ * paths), but it walks providers/commands only — the three seams set here
182
+ * survive it, keyed by this world's id, until a remount of the same id
183
+ * overwrites them. That residue is inert rather than hidden: every closure
184
+ * below reads GAME-scoped state (`game.input`, `game.loop.fixedDt`, the game's
185
+ * seed), so a stale entry actuates exactly what a live one would. See the
186
+ * dispose comment in `r3f-adapter.tsx` for the full accounting.
187
+ */
188
+ export function wireGameInputSeams(
189
+ host: ThreeHostContext,
190
+ registry: DebugRegistry,
191
+ options: {
192
+ readonly id: string;
193
+ readonly inputMapPath?: string | null | undefined;
194
+ readonly optionalInputMap?: boolean | undefined;
195
+ },
196
+ ): Promise<void> {
197
+ const { id, inputMapPath = DEFAULT_INPUT_MAP_PATH, optionalInputMap = false } = options;
198
+ if (!host.game) return Promise.resolve();
199
+ const game = host.game;
200
+ const input = game.input;
201
+ registry.setInputActionsSource(id, () =>
202
+ input.actionNames().map((name) => ({ name, valueType: input.getActionValueType(name) })),
203
+ );
204
+ registry.setInputTraceSource(id, () => {
205
+ const raw = input.getInputTrace();
206
+ return {
207
+ version: raw.version,
208
+ seed: getSeededRandom(game)?.seed ?? null,
209
+ fixedDt: game.loop.fixedDt,
210
+ ticks: raw.ticks,
211
+ };
212
+ });
213
+ registry.setVirtualInputTarget(id, {
214
+ setVirtualAction: (action, value) => input.setVirtualAction(action, value),
215
+ tapVirtualAction: (action) => input.tapVirtualAction(action),
216
+ clearVirtualActions: () => input.clearVirtualActions(),
217
+ scheduleActionAtTick: (tick, action, value) => input.scheduleActionAtTick(tick, action, value),
218
+ startInputRecording: () => input.startInputRecording(),
219
+ stopInputRecording: () => input.stopInputRecording(),
220
+ isInputRecording: () => input.isInputRecording(),
221
+ injectAxis: (sourceId, value) => input.injectAxis(sourceId, value),
222
+ injectVector2: (sourceId, value) => input.injectVector2(sourceId, value),
223
+ injectPointerDelta: (sourceId, delta) => input.injectPointerDelta(sourceId, delta),
224
+ injectPointerPosition: (sourceId, value) => input.injectPointerPosition(sourceId, value),
225
+ });
226
+ if (host.headless || inputMapPath === null) return Promise.resolve();
227
+ // Load-once through the game-owned path (competing paths across roots throw
228
+ // THERE, loudly). A FAILED load (missing/bad file) must not fail the mount:
229
+ // a three world with no declared actions is legal. It degrades loudly
230
+ // instead — naming exactly what breaks.
231
+ // The non-optional call passes ONE argument, exactly as it always has — a
232
+ // trailing `undefined` is a different call to any observer of it.
233
+ const load = optionalInputMap
234
+ ? game.loadInputMap(inputMapPath, { optional: true })
235
+ : game.loadInputMap(inputMapPath);
236
+ return load.catch((err: unknown) => {
237
+ // biome-ignore lint/suspicious/noConsole: deliberate loud degrade — the documented alternative to failing the mount (see comment above)
238
+ console.error(
239
+ `three world "${id}": failed to load input map "${inputMapPath}" — declared input ` +
240
+ 'actions and `game.input.*` (bot/virtual input) will not work until a valid map ' +
241
+ 'loads. Ship one at the conventional path or pass `inputMapPath` to ' +
242
+ `createR3FAdapter (null to opt out). Cause: ${err instanceof Error ? err.message : String(err)}`,
243
+ );
244
+ });
245
+ }
246
+
150
247
  /**
151
248
  * Build the engine runtime for one R3F world from a `ThreeHostContext` — the same
152
249
  * wiring, at the same layer, as the classic `SetupThreeRootAdapter.mount()`
@@ -216,57 +313,9 @@ export function createR3FRootContext(
216
313
  gameClock ?? createSimClock({ dispose: (obj) => disposeDebrisSubtree(obj, []) });
217
314
  ctx.clock = clock;
218
315
 
219
- // --- ctx.input + the debug-registry input seams (gap 3) — the game-owned
220
- // InputManager, polled by the game-scoped runner each frame; this world
221
- // registers the SAME per-world seams the classic adapter registers so
222
- // `game.input.*` (virtual input) and the built-in `input.actions`/
223
- // `input.trace` providers resolve to it. ---
224
- let inputMapReady: Promise<void> = Promise.resolve();
225
- if (host.game) {
226
- const game = host.game;
227
- const input = game.input;
228
- debugRegistry.setInputActionsSource(id, () =>
229
- input.actionNames().map((name) => ({ name, valueType: input.getActionValueType(name) })),
230
- );
231
- debugRegistry.setInputTraceSource(id, () => {
232
- const raw = input.getInputTrace();
233
- return {
234
- version: raw.version,
235
- seed: getSeededRandom(game)?.seed ?? null,
236
- fixedDt: game.loop.fixedDt,
237
- ticks: raw.ticks,
238
- };
239
- });
240
- debugRegistry.setVirtualInputTarget(id, {
241
- setVirtualAction: (action, value) => input.setVirtualAction(action, value),
242
- tapVirtualAction: (action) => input.tapVirtualAction(action),
243
- clearVirtualActions: () => input.clearVirtualActions(),
244
- scheduleActionAtTick: (tick, action, value) =>
245
- input.scheduleActionAtTick(tick, action, value),
246
- startInputRecording: () => input.startInputRecording(),
247
- stopInputRecording: () => input.stopInputRecording(),
248
- isInputRecording: () => input.isInputRecording(),
249
- injectAxis: (sourceId, value) => input.injectAxis(sourceId, value),
250
- injectVector2: (sourceId, value) => input.injectVector2(sourceId, value),
251
- injectPointerDelta: (sourceId, delta) => input.injectPointerDelta(sourceId, delta),
252
- injectPointerPosition: (sourceId, value) => input.injectPointerPosition(sourceId, value),
253
- });
254
- if (!host.headless && inputMapPath !== null) {
255
- // Load-once through the game-owned path (classic parity — competing
256
- // paths across roots throw THERE, loudly). A FAILED load (missing/bad
257
- // file) must not fail this mount: an R3F tree with no declared actions
258
- // is legal. It degrades loudly instead — naming exactly what breaks.
259
- inputMapReady = game.loadInputMap(inputMapPath).catch((err: unknown) => {
260
- // biome-ignore lint/suspicious/noConsole: deliberate loud degrade — the documented alternative to failing the mount (see comment above)
261
- console.error(
262
- `R3F world "${id}": failed to load input map "${inputMapPath}" — declared input ` +
263
- 'actions and `game.input.*` (bot/virtual input) will not work until a valid map ' +
264
- 'loads. Ship one at the conventional path or pass `inputMapPath` to ' +
265
- `createR3FAdapter (null to opt out). Cause: ${err instanceof Error ? err.message : String(err)}`,
266
- );
267
- });
268
- }
269
- }
316
+ // --- ctx.input + the debug-registry input seams (gap 3) — see
317
+ // `wireGameInputSeams` below; `ctx.input` is that same game-owned manager. ---
318
+ const inputMapReady = wireGameInputSeams(host, debugRegistry, { id, inputMapPath });
270
319
 
271
320
  return {
272
321
  ctx,
@@ -1,34 +0,0 @@
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 declare function devLayersEnabled(override?: boolean | undefined): boolean;
34
- //# sourceMappingURL=dev-layers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dev-layers.d.ts","sourceRoot":"","sources":["../../src/runtime/dev-layers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,OAAO,CAaxE"}
@@ -1,46 +0,0 @@
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) {
34
- if (override !== undefined)
35
- return override;
36
- // `import.meta` is cast whole, not just its `.env`: this module is reachable
37
- // from programs whose tsconfig does not pull in `vite/client` (the session
38
- // client's, for one, which reaches the three adapter transitively), and there
39
- // `ImportMeta` has no declared `env` at all. The cast keeps the single owner
40
- // of the dev answer importable from ANY program rather than forcing every
41
- // downstream tsconfig to adopt Vite's ambient types.
42
- const env = import.meta.env;
43
- if (env?.DEV === true)
44
- return true;
45
- return env?.VITE_VGAI_DEV_LAYERS === 'true';
46
- }
@@ -1,46 +0,0 @@
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
- // `import.meta` is cast whole, not just its `.env`: this module is reachable
36
- // from programs whose tsconfig does not pull in `vite/client` (the session
37
- // client's, for one, which reaches the three adapter transitively), and there
38
- // `ImportMeta` has no declared `env` at all. The cast keeps the single owner
39
- // of the dev answer importable from ANY program rather than forcing every
40
- // downstream tsconfig to adopt Vite's ambient types.
41
- const env = (import.meta as unknown as { env?: unknown }).env as
42
- | { DEV?: boolean | undefined; VITE_VGAI_DEV_LAYERS?: string | undefined }
43
- | undefined;
44
- if (env?.DEV === true) return true;
45
- return env?.VITE_VGAI_DEV_LAYERS === 'true';
46
- }