@vgai/engine 0.5.9 → 0.5.11
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/ingest/game-contract.d.ts +7 -1
- package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
- package/dist/adapter/ingest/game-contract.js +9 -2
- package/dist/adapter/setup-three-root-adapter.d.ts.map +1 -1
- package/dist/adapter/setup-three-root-adapter.js +12 -1
- package/dist/dev/register-render-vitals.d.ts +18 -7
- package/dist/dev/register-render-vitals.d.ts.map +1 -1
- package/dist/dev/register-render-vitals.js +35 -8
- package/dist/manifest/load.d.ts +9 -0
- package/dist/manifest/load.d.ts.map +1 -1
- package/dist/manifest/load.js +1 -0
- package/dist/manifest/schema.d.ts +3 -0
- package/dist/manifest/schema.d.ts.map +1 -1
- package/dist/manifest/schema.js +44 -0
- package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
- package/dist/world3d-react/r3f-adapter.js +94 -1
- package/dist/world3d-react/world-context.d.ts +9 -0
- package/dist/world3d-react/world-context.d.ts.map +1 -1
- package/dist/world3d-react/world-context.js +1 -0
- package/package.json +1 -1
- package/schemas/engine-capabilities.json +2 -2
- package/schemas/vgai-project.schema.json +4 -0
- package/src/adapter/ingest/game-contract.ts +11 -2
- package/src/adapter/setup-three-root-adapter.ts +12 -1
- package/src/dev/register-render-vitals.ts +38 -8
- package/src/manifest/load.ts +10 -0
- package/src/manifest/schema.ts +49 -0
- package/src/world3d-react/r3f-adapter.tsx +103 -1
- package/src/world3d-react/world-context.ts +9 -0
|
@@ -107,6 +107,12 @@ export interface VgaiGameContract {
|
|
|
107
107
|
* Read the declared contract, if any. The `contractVersion` gate is the
|
|
108
108
|
* forward-compatibility hinge: a future v2 game on a v1 host is ignored
|
|
109
109
|
* (pre-contract fallbacks apply) rather than half-interpreted.
|
|
110
|
+
*
|
|
111
|
+
* `scope` is the REALM to read from, and defaults to this module's own `window`
|
|
112
|
+
* — correct for a same-realm mount, where the game's modules and the host share
|
|
113
|
+
* one global. An iframe-hosted game declares on ITS realm's window, so the host
|
|
114
|
+
* passes `iframe.contentWindow`: the contract is a property of the realm the
|
|
115
|
+
* game runs in, never of the realm that happens to be asking.
|
|
110
116
|
*/
|
|
111
|
-
export declare function readGameContract(): VgaiGameContract | null;
|
|
117
|
+
export declare function readGameContract(scope?: Window | null | undefined): VgaiGameContract | null;
|
|
112
118
|
//# sourceMappingURL=game-contract.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"game-contract.d.ts","sourceRoot":"","sources":["../../../src/adapter/ingest/game-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;;;;;GASG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,IAAI,IAAI,CAAC;IACf,KAAK,CAAC,IAAI,IAAI,CAAC;IACf,MAAM,CAAC,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACrD;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;IACjC,IAAI,IAAI,OAAO,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;IAC7B,mFAAmF;IACnF,KAAK,CAAC,EAAE,qBAAqB,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,gBAAgB;IAC/B,8EAA8E;IAC9E,eAAe,EAAE,CAAC,CAAC;IACnB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B;;;;OAIG;IACH,OAAO,CAAC,EAAE,eAAe,CAAC;CAC3B;AAED
|
|
1
|
+
{"version":3,"file":"game-contract.d.ts","sourceRoot":"","sources":["../../../src/adapter/ingest/game-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;;;;;GASG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,IAAI,IAAI,CAAC;IACf,KAAK,CAAC,IAAI,IAAI,CAAC;IACf,MAAM,CAAC,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACrD;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;IACjC,IAAI,IAAI,OAAO,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;IAC7B,mFAAmF;IACnF,KAAK,CAAC,EAAE,qBAAqB,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,gBAAgB;IAC/B,8EAA8E;IAC9E,eAAe,EAAE,CAAC,CAAC;IACnB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B;;;;OAIG;IACH,OAAO,CAAC,EAAE,eAAe,CAAC;CAC3B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,GAAE,MAAM,GAAG,IAAI,GAAG,SAAyD,GAC/E,gBAAgB,GAAG,IAAI,CAKzB"}
|
|
@@ -21,9 +21,16 @@
|
|
|
21
21
|
* Read the declared contract, if any. The `contractVersion` gate is the
|
|
22
22
|
* forward-compatibility hinge: a future v2 game on a v1 host is ignored
|
|
23
23
|
* (pre-contract fallbacks apply) rather than half-interpreted.
|
|
24
|
+
*
|
|
25
|
+
* `scope` is the REALM to read from, and defaults to this module's own `window`
|
|
26
|
+
* — correct for a same-realm mount, where the game's modules and the host share
|
|
27
|
+
* one global. An iframe-hosted game declares on ITS realm's window, so the host
|
|
28
|
+
* passes `iframe.contentWindow`: the contract is a property of the realm the
|
|
29
|
+
* game runs in, never of the realm that happens to be asking.
|
|
24
30
|
*/
|
|
25
|
-
export function readGameContract() {
|
|
26
|
-
const declared =
|
|
31
|
+
export function readGameContract(scope = typeof window === 'undefined' ? null : window) {
|
|
32
|
+
const declared = scope
|
|
33
|
+
?.vgaiGame;
|
|
27
34
|
if (!declared || declared.contractVersion !== 1)
|
|
28
35
|
return null;
|
|
29
36
|
return declared;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup-three-root-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/setup-three-root-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAmCrD,OAAO,EAAE,KAAK,mBAAmB,EAA2B,MAAM,4BAA4B,CAAC;AAO/F,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAe,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAM7F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGpE,sFAAsF;AACtF,MAAM,WAAW,oBAAoB;IACnC,gEAAgE;IAChE,KAAK,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAChC,wEAAwE;IACxE,aAAa,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC1C,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,+EAA+E;IAC/E,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAsB,SAAQ,gBAAgB;IAC7D;;;;;;;;OAQG;IACH,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC;IAC1B,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IACzC,+FAA+F;IAC/F,sBAAsB,CAAC,IAAI,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACxD,kFAAkF;IAClF,SAAS,CAAC,QAAQ,EAAE,WAAW,EAAE,aAAa,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E;;;;;;;;;OASG;IACH,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;CAChC;AAeD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,WAAW,CAErE;AAED,qBAAa,qBAAsB,YAAW,WAAW;IAE3C,OAAO,CAAC,QAAQ,CAAC,MAAM;IADnC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;gBACS,MAAM,GAAE,oBAAyB;IAKxD,KAAK,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"setup-three-root-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/setup-three-root-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAmCrD,OAAO,EAAE,KAAK,mBAAmB,EAA2B,MAAM,4BAA4B,CAAC;AAO/F,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAe,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAM7F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGpE,sFAAsF;AACtF,MAAM,WAAW,oBAAoB;IACnC,gEAAgE;IAChE,KAAK,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAChC,wEAAwE;IACxE,aAAa,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC1C,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,+EAA+E;IAC/E,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAsB,SAAQ,gBAAgB;IAC7D;;;;;;;;OAQG;IACH,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC;IAC1B,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IACzC,+FAA+F;IAC/F,sBAAsB,CAAC,IAAI,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACxD,kFAAkF;IAClF,SAAS,CAAC,QAAQ,EAAE,WAAW,EAAE,aAAa,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E;;;;;;;;;OASG;IACH,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;CAChC;AAeD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,WAAW,CAErE;AAED,qBAAa,qBAAsB,YAAW,WAAW;IAE3C,OAAO,CAAC,QAAQ,CAAC,MAAM;IADnC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;gBACS,MAAM,GAAE,oBAAyB;IAKxD,KAAK,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAu0BpE"}
|
|
@@ -497,7 +497,18 @@ export class SetupThreeRootAdapter {
|
|
|
497
497
|
// dev session that enabled it at mount keeps it enabled.
|
|
498
498
|
let renderVitals = null;
|
|
499
499
|
const seedRenderVitals = () => {
|
|
500
|
-
|
|
500
|
+
// `editorPreview ? true : undefined`: an adapter holding an editor
|
|
501
|
+
// preview is being authored IN the editor — a dev context by
|
|
502
|
+
// definition, even when the editor SPA itself is a production build.
|
|
503
|
+
// The packaged runtime bakes `import.meta.env.DEV = false` into its
|
|
504
|
+
// prebuilt bundle, which is a fact about how the EDITOR was built, not
|
|
505
|
+
// about whether this mount is a dev session — found live: a
|
|
506
|
+
// package-native project's vitals never seeded while its dev menu,
|
|
507
|
+
// capabilities, and every project-graph dev layer ran fine. The
|
|
508
|
+
// override argument is dev-layers' own documented seam for exactly
|
|
509
|
+
// this caller-knows-better case; a standalone/ship mount (no
|
|
510
|
+
// editorPreview) still resolves purely from the build env.
|
|
511
|
+
if (!host.game || headless || !devLayersEnabled(editorPreview ? true : undefined))
|
|
501
512
|
return;
|
|
502
513
|
renderVitals?.dispose();
|
|
503
514
|
host.game.profiler.enabled = true;
|
|
@@ -6,10 +6,19 @@
|
|
|
6
6
|
* through `vgai eval`, and `game.providers()`/`game.commands()` to discover
|
|
7
7
|
* them without being told they exist.
|
|
8
8
|
*
|
|
9
|
-
* Engine-owned and first-party, seeded by
|
|
10
|
-
*
|
|
11
|
-
* `
|
|
12
|
-
*
|
|
9
|
+
* Engine-owned and first-party, seeded by BOTH three-root mount paths — a game
|
|
10
|
+
* writes nothing to get these, and there is no capability to install:
|
|
11
|
+
* - `adapter/setup-three-root-adapter.ts` (the classic imperative
|
|
12
|
+
* `setup(ctx)` world), at the same spot it seeds
|
|
13
|
+
* `systemAdapters.renderDebug`;
|
|
14
|
+
* - `world3d-react/r3f-adapter.tsx` (a TSX/R3F world — the shape EVERY
|
|
15
|
+
* scaffolded project's three root has), right after fiber's first commit
|
|
16
|
+
* resolves the scene.
|
|
17
|
+
* Both is the whole point and not redundancy: they are two independent mounts
|
|
18
|
+
* onto the same `Game`, and a door wired into only one of them is a door half
|
|
19
|
+
* the engine's worlds never get (issue #1518 — the R3F half was dark
|
|
20
|
+
* everywhere, found in the field on a package-native project).
|
|
21
|
+
* The measurement half is `dev/render-vitals.ts`
|
|
13
22
|
* (derived entirely from profiler frames); the address-book half is
|
|
14
23
|
* `dev/render-census.ts` (pure walks over the live scene). This module is only
|
|
15
24
|
* the wiring between them and the registry.
|
|
@@ -27,9 +36,11 @@
|
|
|
27
36
|
* the drawing buffer halves was never fill-bound.
|
|
28
37
|
*
|
|
29
38
|
* ── GATING ──────────────────────────────────────────────────────────────────
|
|
30
|
-
* The caller gates.
|
|
31
|
-
*
|
|
32
|
-
*
|
|
39
|
+
* The caller gates. Both seeders call this only under `devLayersEnabled()`
|
|
40
|
+
* (`runtime/dev-layers.ts` — the ONE owner of "is this a dev context"), so a
|
|
41
|
+
* ship build registers nothing and pays nothing. Each also requires a `Game`
|
|
42
|
+
* shell (there is no profiler to fold without one) and a non-headless mount
|
|
43
|
+
* (nothing brackets a submission, so the readings could only ever be empty).
|
|
33
44
|
*
|
|
34
45
|
* ── RESOURCE OWNERSHIP ──────────────────────────────────────────────────────
|
|
35
46
|
* OWNER: {@link registerRenderVitals}'s caller. It allocates one
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register-render-vitals.d.ts","sourceRoot":"","sources":["../../src/dev/register-render-vitals.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"register-render-vitals.d.ts","sourceRoot":"","sources":["../../src/dev/register-render-vitals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAElE,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAOxE,wEAAwE;AACxE,eAAO,MAAM,sBAAsB,kBAAkB,CAAC;AACtD,eAAO,MAAM,qBAAqB,kBAAkB,CAAC;AACrD,eAAO,MAAM,uBAAuB,oBAAoB,CAAC;AACzD,eAAO,MAAM,qBAAqB,kBAAkB,CAAC;AACrD,eAAO,MAAM,kBAAkB,eAAe,CAAC;AAE/C;0EAC0E;AAC1E,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC9C,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACpE,aAAa,IAAI,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1E;AAED,MAAM,WAAW,wBAAwB;IACvC,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,kEAAkE;IAClE,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC;uEACmE;IACnE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IACvC,uCAAuC;IACvC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC;IAC/B;uCACmC;IACnC,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAAC;CAChD;AAMD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,wBAAwB,GAC7B,wBAAwB,GAAG,IAAI,CA8JjC"}
|
|
@@ -6,10 +6,19 @@
|
|
|
6
6
|
* through `vgai eval`, and `game.providers()`/`game.commands()` to discover
|
|
7
7
|
* them without being told they exist.
|
|
8
8
|
*
|
|
9
|
-
* Engine-owned and first-party, seeded by
|
|
10
|
-
*
|
|
11
|
-
* `
|
|
12
|
-
*
|
|
9
|
+
* Engine-owned and first-party, seeded by BOTH three-root mount paths — a game
|
|
10
|
+
* writes nothing to get these, and there is no capability to install:
|
|
11
|
+
* - `adapter/setup-three-root-adapter.ts` (the classic imperative
|
|
12
|
+
* `setup(ctx)` world), at the same spot it seeds
|
|
13
|
+
* `systemAdapters.renderDebug`;
|
|
14
|
+
* - `world3d-react/r3f-adapter.tsx` (a TSX/R3F world — the shape EVERY
|
|
15
|
+
* scaffolded project's three root has), right after fiber's first commit
|
|
16
|
+
* resolves the scene.
|
|
17
|
+
* Both is the whole point and not redundancy: they are two independent mounts
|
|
18
|
+
* onto the same `Game`, and a door wired into only one of them is a door half
|
|
19
|
+
* the engine's worlds never get (issue #1518 — the R3F half was dark
|
|
20
|
+
* everywhere, found in the field on a package-native project).
|
|
21
|
+
* The measurement half is `dev/render-vitals.ts`
|
|
13
22
|
* (derived entirely from profiler frames); the address-book half is
|
|
14
23
|
* `dev/render-census.ts` (pure walks over the live scene). This module is only
|
|
15
24
|
* the wiring between them and the registry.
|
|
@@ -27,9 +36,11 @@
|
|
|
27
36
|
* the drawing buffer halves was never fill-bound.
|
|
28
37
|
*
|
|
29
38
|
* ── GATING ──────────────────────────────────────────────────────────────────
|
|
30
|
-
* The caller gates.
|
|
31
|
-
*
|
|
32
|
-
*
|
|
39
|
+
* The caller gates. Both seeders call this only under `devLayersEnabled()`
|
|
40
|
+
* (`runtime/dev-layers.ts` — the ONE owner of "is this a dev context"), so a
|
|
41
|
+
* ship build registers nothing and pays nothing. Each also requires a `Game`
|
|
42
|
+
* shell (there is no profiler to fold without one) and a non-headless mount
|
|
43
|
+
* (nothing brackets a submission, so the readings could only ever be empty).
|
|
33
44
|
*
|
|
34
45
|
* ── RESOURCE OWNERSHIP ──────────────────────────────────────────────────────
|
|
35
46
|
* OWNER: {@link registerRenderVitals}'s caller. It allocates one
|
|
@@ -69,8 +80,24 @@ export function registerRenderVitals(deps) {
|
|
|
69
80
|
const taken = registry.adapter
|
|
70
81
|
.providers()
|
|
71
82
|
.some((provider) => provider.name === RENDER_VITALS_PROVIDER);
|
|
72
|
-
if (taken)
|
|
83
|
+
if (taken) {
|
|
84
|
+
// Loud, by name. This branch used to return `null` in silence, and a
|
|
85
|
+
// silent decline is indistinguishable from a door that was never seeded —
|
|
86
|
+
// which is precisely how issue #1518 stayed a mystery through a field
|
|
87
|
+
// session: every hypothesis about WHY the door was closed had to be
|
|
88
|
+
// guessed, because nothing on any path said anything. It costs one line to
|
|
89
|
+
// make the one legitimate decline explain itself, and the same line names
|
|
90
|
+
// the mount/teardown OVERLAP case (a re-mount seeding before the outgoing
|
|
91
|
+
// mount's `strip()` runs), where the second world would otherwise lose the
|
|
92
|
+
// door and no log would mention it.
|
|
93
|
+
console.warn(`[render-vitals] world "${worldId}" did not get the ${RENDER_VITALS_PROVIDER} door: ` +
|
|
94
|
+
'another live world already owns it (the names are fixed, so the first three root to ' +
|
|
95
|
+
'mount wins). Its draws still fold into the readings — the profiler is game-scoped — ' +
|
|
96
|
+
'but render.census/families/toggle address the owning root. If this world IS the ' +
|
|
97
|
+
'current one, the previous mount was torn down AFTER this one seeded and its ' +
|
|
98
|
+
'strip() took the door with it.');
|
|
73
99
|
return null;
|
|
100
|
+
}
|
|
74
101
|
const vitals = createRenderVitals(profiler);
|
|
75
102
|
const debug = registry.forRoot(worldId);
|
|
76
103
|
debug.registerStateProvider(RENDER_VITALS_PROVIDER, () => vitals.read());
|
package/dist/manifest/load.d.ts
CHANGED
|
@@ -15,6 +15,15 @@ export type ResolvedAdapter = {
|
|
|
15
15
|
readonly surface: 'three' | 'canvas' | 'dom';
|
|
16
16
|
readonly strategy: IngestStrategy;
|
|
17
17
|
readonly entryHtml: string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Project-relative ES module declaring `window.vgaiGame` beside a
|
|
20
|
+
* pristine copy. OPTIONAL rather than required-and-possibly-undefined
|
|
21
|
+
* (the shape its siblings above use) because it is read by exactly one
|
|
22
|
+
* mount route: every other construction site of this type — the ingest
|
|
23
|
+
* routes' own fixtures included — has nothing to say about it, and
|
|
24
|
+
* requiring the key would only make them all say `undefined`.
|
|
25
|
+
*/
|
|
26
|
+
readonly contractShim?: string | undefined;
|
|
18
27
|
readonly assets: Record<string, string> | undefined;
|
|
19
28
|
readonly domStubs: string[] | undefined;
|
|
20
29
|
readonly captureTimeoutMs: number | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../../src/manifest/load.ts"],"names":[],"mappings":"AAeA,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,aAAa,EAElB,KAAK,IAAI,EACV,MAAM,UAAU,CAAC;AAMlB,wCAAwC;AACxC,MAAM,MAAM,eAAe,GACvB;IACE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;CAC9C,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;CAC9C,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAYxB,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG,aAAa,GAAG,cAAc,CAAC;IACnE,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;IAC7C,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACpD,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,2CAA2C;IAC3C,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC,CAAC;AAEN,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC;IAC7C,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B;mEAC+D;IAC/D,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa,CAAC;IACvC,qEAAqE;IACrE,QAAQ,CAAC,YAAY,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;KAAE,CAAC;CACxE;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,eAAe,EAAE,CAAC,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IAChF,QAAQ,CAAC,UAAU,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IACrF,QAAQ,CAAC,SAAS,EACd;QACE,QAAQ,CAAC,SAAS,CAAC,EACf;YACE,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,CAAC;YACnE,QAAQ,CAAC,MAAM,CAAC,EACZ,SAAS;gBACP,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;gBACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBACvB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;aACnC,EAAE,GACH,SAAS,CAAC;SACf,GACD,SAAS,CAAC;KACf,GACD,SAAS,CAAC;IACd,6FAA6F;IAC7F,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,CAAC;IACpE;;;iBAGa;IACb,QAAQ,CAAC,WAAW,EAChB;QAAE,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,GAC7E,SAAS,CAAC;IACd;;;4EAGwE;IACxE,QAAQ,CAAC,MAAM,EACX;QACE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAC5C,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAC1C,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAC3C,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KAC5C,GACD,SAAS,CAAC;IACd;;;mDAG+C;IAC/C,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,CAAC;CAC3C;AAOD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;CAY/B,CAAC;AAYF,uGAAuG;AACvG,eAAO,MAAM,qBAAqB,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../../src/manifest/load.ts"],"names":[],"mappings":"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"}
|
package/dist/manifest/load.js
CHANGED
|
@@ -125,6 +125,7 @@ function resolveAdapter(root) {
|
|
|
125
125
|
surface: adapter.surface,
|
|
126
126
|
strategy: ingest.strategy,
|
|
127
127
|
entryHtml: ingest.entryHtml,
|
|
128
|
+
contractShim: ingest.contractShim,
|
|
128
129
|
assets: ingest.assets,
|
|
129
130
|
domStubs: ingest.domStubs,
|
|
130
131
|
captureTimeoutMs: ingest.captureTimeoutMs,
|
|
@@ -45,6 +45,7 @@ export declare const RootAdapterSchema: z.ZodPipe<z.ZodTransform<unknown, unknow
|
|
|
45
45
|
"opaque-embed": "opaque-embed";
|
|
46
46
|
}>;
|
|
47
47
|
entryHtml: z.ZodOptional<z.ZodString>;
|
|
48
|
+
contractShim: z.ZodOptional<z.ZodString>;
|
|
48
49
|
assets: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
49
50
|
domStubs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
50
51
|
captureTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
@@ -85,6 +86,7 @@ export declare const AdapterRootSchema: z.ZodObject<{
|
|
|
85
86
|
"opaque-embed": "opaque-embed";
|
|
86
87
|
}>;
|
|
87
88
|
entryHtml: z.ZodOptional<z.ZodString>;
|
|
89
|
+
contractShim: z.ZodOptional<z.ZodString>;
|
|
88
90
|
assets: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
89
91
|
domStubs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
90
92
|
captureTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
@@ -194,6 +196,7 @@ export declare const GameManifestSchema: z.ZodObject<{
|
|
|
194
196
|
"opaque-embed": "opaque-embed";
|
|
195
197
|
}>;
|
|
196
198
|
entryHtml: z.ZodOptional<z.ZodString>;
|
|
199
|
+
contractShim: z.ZodOptional<z.ZodString>;
|
|
197
200
|
assets: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
198
201
|
domStubs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
199
202
|
captureTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/manifest/schema.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAG,CAAU,CAAC;AAMhD,eAAO,MAAM,oBAAoB;;;;;EAK/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAMlE,eAAO,MAAM,UAAU;;;;;;;EAOrB,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAsF9C,eAAO,MAAM,iBAAiB
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/manifest/schema.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAG,CAAU,CAAC;AAMhD,eAAO,MAAM,oBAAoB;;;;;EAK/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAMlE,eAAO,MAAM,UAAU;;;;;;;EAOrB,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAsF9C,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAmP3B,CAAC;AACJ,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAM5D,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAoEnB,CAAC;AACZ,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAkB5D,eAAO,MAAM,eAAe;;;;;EAMzB,CAAC;AACJ,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,eAAO,MAAM,qBAAqB;;;;EAEa,CAAC;AAChD,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;kBAqC7B,CAAC;AACJ,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAMhE,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA+RyD,CAAC;AAEzF,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
|
package/dist/manifest/schema.js
CHANGED
|
@@ -151,6 +151,24 @@ export const RootAdapterSchema = z
|
|
|
151
151
|
'introspection), for both canvas and three roots. Legal but NOT wired to a ' +
|
|
152
152
|
'mount for iframe-reachable (a named error at resolve names this — use `bundleUrl` ' +
|
|
153
153
|
'instead, the wired reachable route).'),
|
|
154
|
+
contractShim: z
|
|
155
|
+
.string()
|
|
156
|
+
.optional()
|
|
157
|
+
.describe("Project-relative path to a HOST-ADDED ES module that declares this game's " +
|
|
158
|
+
'`window.vgaiGame` contract (adapter/ingest/game-contract.ts) WITHOUT editing a ' +
|
|
159
|
+
'vendored byte — the pristine-copy door. The host injects it as a ' +
|
|
160
|
+
'`<script type="module">` into the assembled iframe document AFTER the game`s own ' +
|
|
161
|
+
'importmap (moving a module load ahead of an import map invalidates the map) and ' +
|
|
162
|
+
'BEFORE the game`s entry module, so deferred module scripts run it first. ' +
|
|
163
|
+
'TIMING CONTRACT, which a shim author must obey: because it runs before the ' +
|
|
164
|
+
'game`s entry, it MUST assign `window.vgaiGame` SYNCHRONOUSLY at top level — the ' +
|
|
165
|
+
'host waits for that assignment and fails the mount by name if it never arrives. ' +
|
|
166
|
+
'Anything that needs the game`s own modules must be LAZY: a dynamic `import()` ' +
|
|
167
|
+
'INSIDE each verb/provider closure, resolved at call time. Importing a game module ' +
|
|
168
|
+
'eagerly from the shim would evaluate it ahead of the game`s own entry and reorder ' +
|
|
169
|
+
'its module side effects. Read by the vendored/self-hosted three route only ' +
|
|
170
|
+
'(strategy `iframe-reachable` with `entryHtml`); declaring it elsewhere is a ' +
|
|
171
|
+
'schema error rather than a silently dead field.'),
|
|
154
172
|
assets: z
|
|
155
173
|
.record(z.string(), z.string())
|
|
156
174
|
.optional()
|
|
@@ -226,6 +244,32 @@ export const RootAdapterSchema = z
|
|
|
226
244
|
})
|
|
227
245
|
.describe('Ingest adapter configuration for an unmodified game (a repo-vendored game or your ' +
|
|
228
246
|
'own external folder)')
|
|
247
|
+
// `contractShim` is injected into the assembled ENTRY DOCUMENT, so
|
|
248
|
+
// the ONLY route that can read it is the self-hosted one
|
|
249
|
+
// (`iframe-reachable` + `entryHtml`,
|
|
250
|
+
// ingest-self-hosted-three-adapter.ts). An `opaque-embed` entry
|
|
251
|
+
// document is hosted sandboxed with no host wiring, and a `bundleUrl`
|
|
252
|
+
// mount has no game document at all — under either, a declared shim
|
|
253
|
+
// would be a silently dead declaration, the exact failure the
|
|
254
|
+
// dead-field-under-wrong-variant checks below exist to prevent. Its
|
|
255
|
+
// own `.superRefine` rather than a branch inside the next one: they
|
|
256
|
+
// check unrelated invariants, and folding them together only made
|
|
257
|
+
// one function harder to read.
|
|
258
|
+
.superRefine((ingest, ctx) => {
|
|
259
|
+
if (ingest.contractShim === undefined ||
|
|
260
|
+
(ingest.strategy === 'iframe-reachable' && ingest.entryHtml !== undefined)) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
ctx.addIssue({
|
|
264
|
+
code: z.ZodIssueCode.custom,
|
|
265
|
+
message: '`contractShim` is read ONLY by the self-hosted entry-document route, so it ' +
|
|
266
|
+
"requires strategy 'iframe-reachable' together with `entryHtml` (got strategy " +
|
|
267
|
+
`"${ingest.strategy}"${ingest.entryHtml === undefined ? ' and no `entryHtml`' : ''}) — ` +
|
|
268
|
+
"the host injects the shim module into the GAME'S OWN entry HTML " +
|
|
269
|
+
'(ingest-self-hosted-three-adapter.ts), and no other mount has such a document.',
|
|
270
|
+
path: ['contractShim'],
|
|
271
|
+
});
|
|
272
|
+
})
|
|
229
273
|
.superRefine((ingest, ctx) => {
|
|
230
274
|
// The six iframe-reachable-multi fields only make sense for the
|
|
231
275
|
// iframe-reachable-multi mount — reject them under any other
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"r3f-adapter.d.ts","sourceRoot":"","sources":["../../src/world3d-react/r3f-adapter.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AASH,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,KAAK,EAAoB,WAAW,EAAoB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"r3f-adapter.d.ts","sourceRoot":"","sources":["../../src/world3d-react/r3f-adapter.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AASH,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,KAAK,EAAoB,WAAW,EAAoB,MAAM,YAAY,CAAC;AAMlF,OAAO,EAA4B,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAYvF,sEAAsE;AACtE,MAAM,WAAW,uBAAuB;IACtC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB;;uBAEmB;IACnB,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5B;;;;0CAIsC;IACtC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClD;;;iDAG6C;IAC7C,QAAQ,CAAC,QAAQ,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC;CACrD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,WAAW,CAqc9E"}
|
|
@@ -20,7 +20,10 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { advance, createRoot, extend, events as pointerEvents, } from '@react-three/fiber';
|
|
22
22
|
import { createElement } from 'react';
|
|
23
|
+
import { registerRenderVitals } from '../dev/register-render-vitals';
|
|
24
|
+
import { RENDER_SUBMIT_PHASE } from '../dev/render-vitals';
|
|
23
25
|
import { WorldProvider } from '../react/world-state';
|
|
26
|
+
import { devLayersEnabled } from '../runtime/dev-layers';
|
|
24
27
|
import { EngineBridge } from './engine-bridge';
|
|
25
28
|
import { applyWorldRendererConfig } from './renderer-config';
|
|
26
29
|
import { createR3FRootContext } from './world-context';
|
|
@@ -279,6 +282,52 @@ export function createR3FAdapter(options) {
|
|
|
279
282
|
// `RootState` for precisely this reason, so reading it per frame is the
|
|
280
283
|
// supported way to see the live root. It is a plain map read.
|
|
281
284
|
const live = () => state.get();
|
|
285
|
+
// --- Live render vitals (issue #1504), for an R3F world -----------------
|
|
286
|
+
// The SAME door the classic adapter seeds
|
|
287
|
+
// (`../adapter/setup-three-root-adapter.ts`), seeded here for the same
|
|
288
|
+
// reason: a running game must be able to explain its own frame cost
|
|
289
|
+
// through the debug registry, with no capability to install and nothing
|
|
290
|
+
// for a game to write. It has to be seeded in BOTH adapters because they
|
|
291
|
+
// are two independent mount paths onto the same `Game` — and a TSX/R3F
|
|
292
|
+
// world is the only shape a scaffolded project's three root has, so a
|
|
293
|
+
// door wired only into the classic path is a door no scaffolded project
|
|
294
|
+
// ever gets (issue #1518: `game.state('render.vitals')` answered "no
|
|
295
|
+
// state provider registered" in the field while every other dev layer of
|
|
296
|
+
// the same world worked).
|
|
297
|
+
//
|
|
298
|
+
// The gate is the same three conditions, minus the one that cannot
|
|
299
|
+
// arise: `devLayersEnabled()` (`../runtime/dev-layers.ts`, the ONE owner
|
|
300
|
+
// of "is this a dev context" — a ship build registers nothing and pays
|
|
301
|
+
// nothing), and a `Game` shell (the readings are folded out of that
|
|
302
|
+
// game's profiler frames). "Not headless" needs no term here: a headless
|
|
303
|
+
// mount returned above, before fiber was ever configured.
|
|
304
|
+
//
|
|
305
|
+
// Two module instances, one answer: under the packaged editor this
|
|
306
|
+
// module is served out of the PROJECT's own Vite graph (see
|
|
307
|
+
// `packages/editor/vite-plugin-react-world-runtime.ts` — the packaged
|
|
308
|
+
// shell imports `resolveR3FEntryAdapter` through the project-rooted
|
|
309
|
+
// server precisely so fiber/react/three identity is the project's), so
|
|
310
|
+
// `import.meta.env.DEV` here is the project server's `true`, not the
|
|
311
|
+
// prebuilt shell's `false`.
|
|
312
|
+
//
|
|
313
|
+
// OWNER of the registration+fold: this mount, ended by `dispose()` below
|
|
314
|
+
// (the fold) and `runtime.dispose()`'s scoped `strip(id)` (the
|
|
315
|
+
// registrations) — the same split the classic adapter documents.
|
|
316
|
+
let renderVitals = null;
|
|
317
|
+
if (host.game && devLayersEnabled()) {
|
|
318
|
+
// Same auto-enable as the classic adapter: the readings are folded out
|
|
319
|
+
// of profiler frames, and the profiler is a flag the editor already
|
|
320
|
+
// sets on play — under the dev gate this is the same cost arriving a
|
|
321
|
+
// little earlier, not a new one.
|
|
322
|
+
host.game.profiler.enabled = true;
|
|
323
|
+
renderVitals = registerRenderVitals({
|
|
324
|
+
registry: runtime.debugRegistry,
|
|
325
|
+
worldId: id,
|
|
326
|
+
profiler: host.game.profiler,
|
|
327
|
+
scene: live().scene,
|
|
328
|
+
renderer: host.renderer,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
282
331
|
// The engine drives every `useFrame` through the mounted world's
|
|
283
332
|
// `update(dt)` hook, never off a raw host-loop callback. That is the
|
|
284
333
|
// whole pause story: `runFrameImpl` (`runtime/game.ts`) calls
|
|
@@ -350,7 +399,44 @@ export function createR3FAdapter(options) {
|
|
|
350
399
|
// then fiber's advance (useFrame callbacks + the actual render see
|
|
351
400
|
// the fresh state). Both are host-gated: neither runs while frozen.
|
|
352
401
|
systems.run(dt);
|
|
353
|
-
|
|
402
|
+
const profiler = host.game?.profiler;
|
|
403
|
+
if (!profiler?.enabled) {
|
|
404
|
+
advance(elapsed, true, current);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
// Issue #1504's measurement, produced where the draw actually
|
|
408
|
+
// happens. `advance()` under `frameloop: 'never'` is what calls
|
|
409
|
+
// `gl.render(scene, camera)`, so this bracket — not a phase span
|
|
410
|
+
// inside `systems` — is the frame's CPU submission cost, and it is
|
|
411
|
+
// also what makes this frame a PRESENTATION as far as the vitals
|
|
412
|
+
// fold is concerned (`../dev/render-vitals.ts`: no bracket, no
|
|
413
|
+
// display frame). The profiler's phase clock is a stack, so
|
|
414
|
+
// bracketing here cannot truncate an enclosing phase.
|
|
415
|
+
const info = host.renderer.info;
|
|
416
|
+
const passesBefore = info?.render.frame ?? 0;
|
|
417
|
+
profiler.beginPhase();
|
|
418
|
+
try {
|
|
419
|
+
advance(elapsed, true, current);
|
|
420
|
+
}
|
|
421
|
+
finally {
|
|
422
|
+
profiler.endPhase(RENDER_SUBMIT_PHASE);
|
|
423
|
+
}
|
|
424
|
+
// Checked, not assumed — a duck-typed/design-time renderer supplies
|
|
425
|
+
// only what a mount needs, and a mount with no counters must report
|
|
426
|
+
// NOTHING rather than throw once per frame (the classic adapter's
|
|
427
|
+
// `postFrame` pays for the same assumption for the same reason).
|
|
428
|
+
// `gpuMs` is null: this path runs no GPU timer, and the profiler
|
|
429
|
+
// keeps the previous reading rather than inventing a zero.
|
|
430
|
+
if (info) {
|
|
431
|
+
profiler.reportRender({
|
|
432
|
+
gpuMs: null,
|
|
433
|
+
drawCalls: info.render.calls,
|
|
434
|
+
triangles: info.render.triangles,
|
|
435
|
+
geometries: info.memory.geometries,
|
|
436
|
+
textures: info.memory.textures,
|
|
437
|
+
renderPasses: info.render.frame - passesBefore,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
354
440
|
},
|
|
355
441
|
resize(width, height) {
|
|
356
442
|
live().setSize(width, height);
|
|
@@ -359,6 +445,13 @@ export function createR3FAdapter(options) {
|
|
|
359
445
|
// Unmount FIRST so component effect cleanups run, then tear the
|
|
360
446
|
// runtime down (scoped debug-registry strip).
|
|
361
447
|
root.unmount();
|
|
448
|
+
// The vitals fold owns two `profiler.subscribe` registrations, and
|
|
449
|
+
// `runtime.dispose()`'s `strip(id)` removes the REGISTRATIONS only —
|
|
450
|
+
// this is the one path that ends the subscriptions, exactly as the
|
|
451
|
+
// classic adapter's teardown documents. Before the strip, so a
|
|
452
|
+
// subscription can never outlive the door it feeds.
|
|
453
|
+
renderVitals?.dispose();
|
|
454
|
+
renderVitals = null;
|
|
362
455
|
runtime.dispose();
|
|
363
456
|
restoreRendererConfig();
|
|
364
457
|
},
|
|
@@ -35,6 +35,7 @@ import type * as THREE from 'three';
|
|
|
35
35
|
import type { ThreeHostContext } from '../adapter';
|
|
36
36
|
import type { SystemAdapters } from '../adapter/system-adapter';
|
|
37
37
|
import { createSystemRunner } from '../core/system-runner';
|
|
38
|
+
import { type DebugRegistry } from '../runtime/debug-registry';
|
|
38
39
|
import type { GameContext } from '../runtime/types';
|
|
39
40
|
/** The classic adapter's default input-map path — the conventional location
|
|
40
41
|
* every scaffolded project ships (`public/inputmaps/default.inputmap.json`). */
|
|
@@ -99,6 +100,14 @@ export interface R3FRootRuntime {
|
|
|
99
100
|
/** The mounted world's `SystemAdapters` bag (`mounted.systems`) —
|
|
100
101
|
* `ctx.registerSystemAdapter` writes into it; `debug` is pre-seeded. */
|
|
101
102
|
readonly systemAdapters: SystemAdapters;
|
|
103
|
+
/** The registry every registration of this world's lands in — the game's
|
|
104
|
+
* own when a `Game` hosts it, a private mount-local one otherwise. Exposed
|
|
105
|
+
* (rather than re-derived by the caller) because `createR3FAdapter` seeds
|
|
106
|
+
* the render-vitals door onto THIS registry after fiber's first commit
|
|
107
|
+
* resolves the scene, and two `getDebugRegistry` calls that are "obviously"
|
|
108
|
+
* the same object is exactly the second source of truth the classic
|
|
109
|
+
* adapter's single `debugRegistry` local avoids. */
|
|
110
|
+
readonly debugRegistry: DebugRegistry;
|
|
102
111
|
/** Resolves once the input map load settles (immediately when skipped).
|
|
103
112
|
* Never rejects — a failed load reports loudly and resolves. */
|
|
104
113
|
readonly inputMapReady: Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"world-context.d.ts","sourceRoot":"","sources":["../../src/world3d-react/world-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAgCK;AAEL,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AACpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAOhE,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"world-context.d.ts","sourceRoot":"","sources":["../../src/world3d-react/world-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAgCK;AAEL,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AACpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAOhE,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAEL,KAAK,aAAa,EAEnB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;iFACiF;AACjF,eAAO,MAAM,sBAAsB,qCAAqC,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,MAAM,cAAc,GAAG,IAAI,CAC/B,WAAW,EACT,OAAO,GACP,QAAQ,GACR,YAAY,GACZ,OAAO,GACP,QAAQ,GACR,SAAS,GACT,OAAO,GACP,OAAO,GACP,QAAQ,GACR,MAAM,GACN,OAAO,GACP,UAAU,GACV,uBAAuB,CAC1B,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC;gFAC4E;IAC5E,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACnD;AAED;+EAC+E;AAC/E,MAAM,WAAW,cAAc;IAC7B;6EACyE;IACzE,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;IAC7B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;IACxD;6EACyE;IACzE,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC;;;;;;yDAMqD;IACrD,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC;qEACiE;IACjE,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC;;;4DAGwD;IACxD,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;IAC/D;;gFAE4E;IAC5E,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,qBAAqB,GAC7B,cAAc,CAsKhB"}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@vgai/engine",
|
|
3
3
|
"author": "Volter AI, Inc.",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
|
-
"version": "0.5.
|
|
5
|
+
"version": "0.5.11",
|
|
6
6
|
"description": "Readable TypeScript game engine and universal host for Three.js, PixiJS, and React games.",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"game-engine",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
],
|
|
16
16
|
"notes": "Grepped packages/engine/src for ccd/CCD/setCcdEnabled: zero matches. The engine constructs no rigid bodies itself — games build their own descs via ctx.RAPIER — and nothing engine-side ever enables ccd. Workaround: keep dynamic-body per-step travel small relative to the thinnest collider, or add intermediate colliders; a game may also call setCcdEnabled on its own bodies directly.",
|
|
17
17
|
"evidenceHashes": {
|
|
18
|
-
"packages/engine/src/adapter/setup-three-root-adapter.ts": "
|
|
18
|
+
"packages/engine/src/adapter/setup-three-root-adapter.ts": "f1b599b9fd25dfacd1c155ce83418734c984ac843d7173f573947fceb860cf51"
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
{
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"notes": "Track P shipped 2026-07-03; proven: bubbo-bubbo/puzzling-potions/flappy-pixi. Tiers/limits: ARCHITECTURE-CORE.md.",
|
|
74
74
|
"evidenceHashes": {
|
|
75
75
|
"vendor/games/verify-unaltered.mjs": "df875b09d698b3cfc03443ff3dba4929f5f08ea8913009d4fae253d478282312",
|
|
76
|
-
"packages/engine/src/manifest/schema.ts": "
|
|
76
|
+
"packages/engine/src/manifest/schema.ts": "d689135c4702e09be1de6a66c8625e64be10ca80deb988f30430678fa77b88db"
|
|
77
77
|
}
|
|
78
78
|
},
|
|
79
79
|
{
|
|
@@ -114,6 +114,10 @@
|
|
|
114
114
|
"description": "Entry HTML path, project-relative. WIRED for opaque-embed (D-W2): the file`s bytes are read verbatim through the project-static route and hosted in a sandboxed iframe (embed-only, no scene introspection), for both canvas and three roots. Legal but NOT wired to a mount for iframe-reachable (a named error at resolve names this — use `bundleUrl` instead, the wired reachable route).",
|
|
115
115
|
"type": "string"
|
|
116
116
|
},
|
|
117
|
+
"contractShim": {
|
|
118
|
+
"description": "Project-relative path to a HOST-ADDED ES module that declares this game's `window.vgaiGame` contract (adapter/ingest/game-contract.ts) WITHOUT editing a vendored byte — the pristine-copy door. The host injects it as a `<script type=\"module\">` into the assembled iframe document AFTER the game`s own importmap (moving a module load ahead of an import map invalidates the map) and BEFORE the game`s entry module, so deferred module scripts run it first. TIMING CONTRACT, which a shim author must obey: because it runs before the game`s entry, it MUST assign `window.vgaiGame` SYNCHRONOUSLY at top level — the host waits for that assignment and fails the mount by name if it never arrives. Anything that needs the game`s own modules must be LAZY: a dynamic `import()` INSIDE each verb/provider closure, resolved at call time. Importing a game module eagerly from the shim would evaluate it ahead of the game`s own entry and reorder its module side effects. Read by the vendored/self-hosted three route only (strategy `iframe-reachable` with `entryHtml`); declaring it elsewhere is a schema error rather than a silently dead field.",
|
|
119
|
+
"type": "string"
|
|
120
|
+
},
|
|
117
121
|
"assets": {
|
|
118
122
|
"description": "Path-substring -> served-URL rewrites for this ingested game (IngestGame.assets today)",
|
|
119
123
|
"type": "object",
|
|
@@ -113,9 +113,18 @@ export interface VgaiGameContract {
|
|
|
113
113
|
* Read the declared contract, if any. The `contractVersion` gate is the
|
|
114
114
|
* forward-compatibility hinge: a future v2 game on a v1 host is ignored
|
|
115
115
|
* (pre-contract fallbacks apply) rather than half-interpreted.
|
|
116
|
+
*
|
|
117
|
+
* `scope` is the REALM to read from, and defaults to this module's own `window`
|
|
118
|
+
* — correct for a same-realm mount, where the game's modules and the host share
|
|
119
|
+
* one global. An iframe-hosted game declares on ITS realm's window, so the host
|
|
120
|
+
* passes `iframe.contentWindow`: the contract is a property of the realm the
|
|
121
|
+
* game runs in, never of the realm that happens to be asking.
|
|
116
122
|
*/
|
|
117
|
-
export function readGameContract(
|
|
118
|
-
|
|
123
|
+
export function readGameContract(
|
|
124
|
+
scope: Window | null | undefined = typeof window === 'undefined' ? null : window,
|
|
125
|
+
): VgaiGameContract | null {
|
|
126
|
+
const declared = (scope as unknown as { vgaiGame?: VgaiGameContract } | null | undefined)
|
|
127
|
+
?.vgaiGame;
|
|
119
128
|
if (!declared || declared.contractVersion !== 1) return null;
|
|
120
129
|
return declared;
|
|
121
130
|
}
|
|
@@ -604,7 +604,18 @@ export class SetupThreeRootAdapter implements RootAdapter {
|
|
|
604
604
|
// dev session that enabled it at mount keeps it enabled.
|
|
605
605
|
let renderVitals: RenderVitalsRegistration | null = null;
|
|
606
606
|
const seedRenderVitals = (): void => {
|
|
607
|
-
|
|
607
|
+
// `editorPreview ? true : undefined`: an adapter holding an editor
|
|
608
|
+
// preview is being authored IN the editor — a dev context by
|
|
609
|
+
// definition, even when the editor SPA itself is a production build.
|
|
610
|
+
// The packaged runtime bakes `import.meta.env.DEV = false` into its
|
|
611
|
+
// prebuilt bundle, which is a fact about how the EDITOR was built, not
|
|
612
|
+
// about whether this mount is a dev session — found live: a
|
|
613
|
+
// package-native project's vitals never seeded while its dev menu,
|
|
614
|
+
// capabilities, and every project-graph dev layer ran fine. The
|
|
615
|
+
// override argument is dev-layers' own documented seam for exactly
|
|
616
|
+
// this caller-knows-better case; a standalone/ship mount (no
|
|
617
|
+
// editorPreview) still resolves purely from the build env.
|
|
618
|
+
if (!host.game || headless || !devLayersEnabled(editorPreview ? true : undefined)) return;
|
|
608
619
|
renderVitals?.dispose();
|
|
609
620
|
host.game.profiler.enabled = true;
|
|
610
621
|
renderVitals = registerRenderVitals({
|
|
@@ -6,10 +6,19 @@
|
|
|
6
6
|
* through `vgai eval`, and `game.providers()`/`game.commands()` to discover
|
|
7
7
|
* them without being told they exist.
|
|
8
8
|
*
|
|
9
|
-
* Engine-owned and first-party, seeded by
|
|
10
|
-
*
|
|
11
|
-
* `
|
|
12
|
-
*
|
|
9
|
+
* Engine-owned and first-party, seeded by BOTH three-root mount paths — a game
|
|
10
|
+
* writes nothing to get these, and there is no capability to install:
|
|
11
|
+
* - `adapter/setup-three-root-adapter.ts` (the classic imperative
|
|
12
|
+
* `setup(ctx)` world), at the same spot it seeds
|
|
13
|
+
* `systemAdapters.renderDebug`;
|
|
14
|
+
* - `world3d-react/r3f-adapter.tsx` (a TSX/R3F world — the shape EVERY
|
|
15
|
+
* scaffolded project's three root has), right after fiber's first commit
|
|
16
|
+
* resolves the scene.
|
|
17
|
+
* Both is the whole point and not redundancy: they are two independent mounts
|
|
18
|
+
* onto the same `Game`, and a door wired into only one of them is a door half
|
|
19
|
+
* the engine's worlds never get (issue #1518 — the R3F half was dark
|
|
20
|
+
* everywhere, found in the field on a package-native project).
|
|
21
|
+
* The measurement half is `dev/render-vitals.ts`
|
|
13
22
|
* (derived entirely from profiler frames); the address-book half is
|
|
14
23
|
* `dev/render-census.ts` (pure walks over the live scene). This module is only
|
|
15
24
|
* the wiring between them and the registry.
|
|
@@ -27,9 +36,11 @@
|
|
|
27
36
|
* the drawing buffer halves was never fill-bound.
|
|
28
37
|
*
|
|
29
38
|
* ── GATING ──────────────────────────────────────────────────────────────────
|
|
30
|
-
* The caller gates.
|
|
31
|
-
*
|
|
32
|
-
*
|
|
39
|
+
* The caller gates. Both seeders call this only under `devLayersEnabled()`
|
|
40
|
+
* (`runtime/dev-layers.ts` — the ONE owner of "is this a dev context"), so a
|
|
41
|
+
* ship build registers nothing and pays nothing. Each also requires a `Game`
|
|
42
|
+
* shell (there is no profiler to fold without one) and a non-headless mount
|
|
43
|
+
* (nothing brackets a submission, so the readings could only ever be empty).
|
|
33
44
|
*
|
|
34
45
|
* ── RESOURCE OWNERSHIP ──────────────────────────────────────────────────────
|
|
35
46
|
* OWNER: {@link registerRenderVitals}'s caller. It allocates one
|
|
@@ -112,7 +123,26 @@ export function registerRenderVitals(
|
|
|
112
123
|
const taken = registry.adapter
|
|
113
124
|
.providers()
|
|
114
125
|
.some((provider) => provider.name === RENDER_VITALS_PROVIDER);
|
|
115
|
-
if (taken)
|
|
126
|
+
if (taken) {
|
|
127
|
+
// Loud, by name. This branch used to return `null` in silence, and a
|
|
128
|
+
// silent decline is indistinguishable from a door that was never seeded —
|
|
129
|
+
// which is precisely how issue #1518 stayed a mystery through a field
|
|
130
|
+
// session: every hypothesis about WHY the door was closed had to be
|
|
131
|
+
// guessed, because nothing on any path said anything. It costs one line to
|
|
132
|
+
// make the one legitimate decline explain itself, and the same line names
|
|
133
|
+
// the mount/teardown OVERLAP case (a re-mount seeding before the outgoing
|
|
134
|
+
// mount's `strip()` runs), where the second world would otherwise lose the
|
|
135
|
+
// door and no log would mention it.
|
|
136
|
+
console.warn(
|
|
137
|
+
`[render-vitals] world "${worldId}" did not get the ${RENDER_VITALS_PROVIDER} door: ` +
|
|
138
|
+
'another live world already owns it (the names are fixed, so the first three root to ' +
|
|
139
|
+
'mount wins). Its draws still fold into the readings — the profiler is game-scoped — ' +
|
|
140
|
+
'but render.census/families/toggle address the owning root. If this world IS the ' +
|
|
141
|
+
'current one, the previous mount was torn down AFTER this one seeded and its ' +
|
|
142
|
+
'strip() took the door with it.',
|
|
143
|
+
);
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
116
146
|
|
|
117
147
|
const vitals = createRenderVitals(profiler);
|
|
118
148
|
const debug = registry.forRoot(worldId);
|
package/src/manifest/load.ts
CHANGED
|
@@ -57,6 +57,15 @@ export type ResolvedAdapter =
|
|
|
57
57
|
readonly surface: 'three' | 'canvas' | 'dom';
|
|
58
58
|
readonly strategy: IngestStrategy;
|
|
59
59
|
readonly entryHtml: string | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Project-relative ES module declaring `window.vgaiGame` beside a
|
|
62
|
+
* pristine copy. OPTIONAL rather than required-and-possibly-undefined
|
|
63
|
+
* (the shape its siblings above use) because it is read by exactly one
|
|
64
|
+
* mount route: every other construction site of this type — the ingest
|
|
65
|
+
* routes' own fixtures included — has nothing to say about it, and
|
|
66
|
+
* requiring the key would only make them all say `undefined`.
|
|
67
|
+
*/
|
|
68
|
+
readonly contractShim?: string | undefined;
|
|
60
69
|
readonly assets: Record<string, string> | undefined;
|
|
61
70
|
readonly domStubs: string[] | undefined;
|
|
62
71
|
readonly captureTimeoutMs: number | undefined;
|
|
@@ -274,6 +283,7 @@ function resolveAdapter(root: AdapterRoot): ResolvedAdapter {
|
|
|
274
283
|
surface: adapter.surface,
|
|
275
284
|
strategy: ingest.strategy,
|
|
276
285
|
entryHtml: ingest.entryHtml,
|
|
286
|
+
contractShim: ingest.contractShim,
|
|
277
287
|
assets: ingest.assets,
|
|
278
288
|
domStubs: ingest.domStubs,
|
|
279
289
|
captureTimeoutMs: ingest.captureTimeoutMs,
|
package/src/manifest/schema.ts
CHANGED
|
@@ -174,6 +174,26 @@ export const RootAdapterSchema = z
|
|
|
174
174
|
'mount for iframe-reachable (a named error at resolve names this — use `bundleUrl` ' +
|
|
175
175
|
'instead, the wired reachable route).',
|
|
176
176
|
),
|
|
177
|
+
contractShim: z
|
|
178
|
+
.string()
|
|
179
|
+
.optional()
|
|
180
|
+
.describe(
|
|
181
|
+
"Project-relative path to a HOST-ADDED ES module that declares this game's " +
|
|
182
|
+
'`window.vgaiGame` contract (adapter/ingest/game-contract.ts) WITHOUT editing a ' +
|
|
183
|
+
'vendored byte — the pristine-copy door. The host injects it as a ' +
|
|
184
|
+
'`<script type="module">` into the assembled iframe document AFTER the game`s own ' +
|
|
185
|
+
'importmap (moving a module load ahead of an import map invalidates the map) and ' +
|
|
186
|
+
'BEFORE the game`s entry module, so deferred module scripts run it first. ' +
|
|
187
|
+
'TIMING CONTRACT, which a shim author must obey: because it runs before the ' +
|
|
188
|
+
'game`s entry, it MUST assign `window.vgaiGame` SYNCHRONOUSLY at top level — the ' +
|
|
189
|
+
'host waits for that assignment and fails the mount by name if it never arrives. ' +
|
|
190
|
+
'Anything that needs the game`s own modules must be LAZY: a dynamic `import()` ' +
|
|
191
|
+
'INSIDE each verb/provider closure, resolved at call time. Importing a game module ' +
|
|
192
|
+
'eagerly from the shim would evaluate it ahead of the game`s own entry and reorder ' +
|
|
193
|
+
'its module side effects. Read by the vendored/self-hosted three route only ' +
|
|
194
|
+
'(strategy `iframe-reachable` with `entryHtml`); declaring it elsewhere is a ' +
|
|
195
|
+
'schema error rather than a silently dead field.',
|
|
196
|
+
),
|
|
177
197
|
assets: z
|
|
178
198
|
.record(z.string(), z.string())
|
|
179
199
|
.optional()
|
|
@@ -269,6 +289,35 @@ export const RootAdapterSchema = z
|
|
|
269
289
|
'Ingest adapter configuration for an unmodified game (a repo-vendored game or your ' +
|
|
270
290
|
'own external folder)',
|
|
271
291
|
)
|
|
292
|
+
// `contractShim` is injected into the assembled ENTRY DOCUMENT, so
|
|
293
|
+
// the ONLY route that can read it is the self-hosted one
|
|
294
|
+
// (`iframe-reachable` + `entryHtml`,
|
|
295
|
+
// ingest-self-hosted-three-adapter.ts). An `opaque-embed` entry
|
|
296
|
+
// document is hosted sandboxed with no host wiring, and a `bundleUrl`
|
|
297
|
+
// mount has no game document at all — under either, a declared shim
|
|
298
|
+
// would be a silently dead declaration, the exact failure the
|
|
299
|
+
// dead-field-under-wrong-variant checks below exist to prevent. Its
|
|
300
|
+
// own `.superRefine` rather than a branch inside the next one: they
|
|
301
|
+
// check unrelated invariants, and folding them together only made
|
|
302
|
+
// one function harder to read.
|
|
303
|
+
.superRefine((ingest, ctx) => {
|
|
304
|
+
if (
|
|
305
|
+
ingest.contractShim === undefined ||
|
|
306
|
+
(ingest.strategy === 'iframe-reachable' && ingest.entryHtml !== undefined)
|
|
307
|
+
) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
ctx.addIssue({
|
|
311
|
+
code: z.ZodIssueCode.custom,
|
|
312
|
+
message:
|
|
313
|
+
'`contractShim` is read ONLY by the self-hosted entry-document route, so it ' +
|
|
314
|
+
"requires strategy 'iframe-reachable' together with `entryHtml` (got strategy " +
|
|
315
|
+
`"${ingest.strategy}"${ingest.entryHtml === undefined ? ' and no `entryHtml`' : ''}) — ` +
|
|
316
|
+
"the host injects the shim module into the GAME'S OWN entry HTML " +
|
|
317
|
+
'(ingest-self-hosted-three-adapter.ts), and no other mount has such a document.',
|
|
318
|
+
path: ['contractShim'],
|
|
319
|
+
});
|
|
320
|
+
})
|
|
272
321
|
.superRefine((ingest, ctx) => {
|
|
273
322
|
// The six iframe-reachable-multi fields only make sense for the
|
|
274
323
|
// iframe-reachable-multi mount — reject them under any other
|
|
@@ -28,11 +28,23 @@ import {
|
|
|
28
28
|
} from '@react-three/fiber';
|
|
29
29
|
import { createElement, type ReactNode } from 'react';
|
|
30
30
|
import type { MountedThreeRoot, RootAdapter, ThreeHostContext } from '../adapter';
|
|
31
|
+
import { type RenderVitalsRegistration, registerRenderVitals } from '../dev/register-render-vitals';
|
|
32
|
+
import { RENDER_SUBMIT_PHASE } from '../dev/render-vitals';
|
|
31
33
|
import { WorldProvider } from '../react/world-state';
|
|
34
|
+
import { devLayersEnabled } from '../runtime/dev-layers';
|
|
32
35
|
import { EngineBridge, type EngineBridgeValue } from './engine-bridge';
|
|
33
36
|
import { applyWorldRendererConfig, type WorldRendererConfig } from './renderer-config';
|
|
34
37
|
import { createR3FRootContext } from './world-context';
|
|
35
38
|
|
|
39
|
+
/** The slice of `WebGLRenderer.info` the vitals reporter reads. Declared
|
|
40
|
+
* structurally rather than imported from `three`, per this module's own
|
|
41
|
+
* identity rule (see the header): a duck-typed host renderer legitimately has
|
|
42
|
+
* no `info` at all, which is why every read of it is guarded. */
|
|
43
|
+
interface RendererCounters {
|
|
44
|
+
readonly render: { readonly calls: number; readonly triangles: number; readonly frame: number };
|
|
45
|
+
readonly memory: { readonly geometries: number; readonly textures: number };
|
|
46
|
+
}
|
|
47
|
+
|
|
36
48
|
/** What {@link createR3FAdapter} needs to build one `RootAdapter`. */
|
|
37
49
|
export interface CreateR3FAdapterOptions {
|
|
38
50
|
/** Stable id (telemetry/registry/conformance) — `RootAdapter.id`. */
|
|
@@ -336,6 +348,53 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
|
|
|
336
348
|
// supported way to see the live root. It is a plain map read.
|
|
337
349
|
const live = (): RootState => state.get();
|
|
338
350
|
|
|
351
|
+
// --- Live render vitals (issue #1504), for an R3F world -----------------
|
|
352
|
+
// The SAME door the classic adapter seeds
|
|
353
|
+
// (`../adapter/setup-three-root-adapter.ts`), seeded here for the same
|
|
354
|
+
// reason: a running game must be able to explain its own frame cost
|
|
355
|
+
// through the debug registry, with no capability to install and nothing
|
|
356
|
+
// for a game to write. It has to be seeded in BOTH adapters because they
|
|
357
|
+
// are two independent mount paths onto the same `Game` — and a TSX/R3F
|
|
358
|
+
// world is the only shape a scaffolded project's three root has, so a
|
|
359
|
+
// door wired only into the classic path is a door no scaffolded project
|
|
360
|
+
// ever gets (issue #1518: `game.state('render.vitals')` answered "no
|
|
361
|
+
// state provider registered" in the field while every other dev layer of
|
|
362
|
+
// the same world worked).
|
|
363
|
+
//
|
|
364
|
+
// The gate is the same three conditions, minus the one that cannot
|
|
365
|
+
// arise: `devLayersEnabled()` (`../runtime/dev-layers.ts`, the ONE owner
|
|
366
|
+
// of "is this a dev context" — a ship build registers nothing and pays
|
|
367
|
+
// nothing), and a `Game` shell (the readings are folded out of that
|
|
368
|
+
// game's profiler frames). "Not headless" needs no term here: a headless
|
|
369
|
+
// mount returned above, before fiber was ever configured.
|
|
370
|
+
//
|
|
371
|
+
// Two module instances, one answer: under the packaged editor this
|
|
372
|
+
// module is served out of the PROJECT's own Vite graph (see
|
|
373
|
+
// `packages/editor/vite-plugin-react-world-runtime.ts` — the packaged
|
|
374
|
+
// shell imports `resolveR3FEntryAdapter` through the project-rooted
|
|
375
|
+
// server precisely so fiber/react/three identity is the project's), so
|
|
376
|
+
// `import.meta.env.DEV` here is the project server's `true`, not the
|
|
377
|
+
// prebuilt shell's `false`.
|
|
378
|
+
//
|
|
379
|
+
// OWNER of the registration+fold: this mount, ended by `dispose()` below
|
|
380
|
+
// (the fold) and `runtime.dispose()`'s scoped `strip(id)` (the
|
|
381
|
+
// registrations) — the same split the classic adapter documents.
|
|
382
|
+
let renderVitals: RenderVitalsRegistration | null = null;
|
|
383
|
+
if (host.game && devLayersEnabled()) {
|
|
384
|
+
// Same auto-enable as the classic adapter: the readings are folded out
|
|
385
|
+
// of profiler frames, and the profiler is a flag the editor already
|
|
386
|
+
// sets on play — under the dev gate this is the same cost arriving a
|
|
387
|
+
// little earlier, not a new one.
|
|
388
|
+
host.game.profiler.enabled = true;
|
|
389
|
+
renderVitals = registerRenderVitals({
|
|
390
|
+
registry: runtime.debugRegistry,
|
|
391
|
+
worldId: id,
|
|
392
|
+
profiler: host.game.profiler,
|
|
393
|
+
scene: live().scene,
|
|
394
|
+
renderer: host.renderer,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
339
398
|
// The engine drives every `useFrame` through the mounted world's
|
|
340
399
|
// `update(dt)` hook, never off a raw host-loop callback. That is the
|
|
341
400
|
// whole pause story: `runFrameImpl` (`runtime/game.ts`) calls
|
|
@@ -408,7 +467,43 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
|
|
|
408
467
|
// then fiber's advance (useFrame callbacks + the actual render see
|
|
409
468
|
// the fresh state). Both are host-gated: neither runs while frozen.
|
|
410
469
|
systems.run(dt);
|
|
411
|
-
|
|
470
|
+
const profiler = host.game?.profiler;
|
|
471
|
+
if (!profiler?.enabled) {
|
|
472
|
+
advance(elapsed, true, current);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
// Issue #1504's measurement, produced where the draw actually
|
|
476
|
+
// happens. `advance()` under `frameloop: 'never'` is what calls
|
|
477
|
+
// `gl.render(scene, camera)`, so this bracket — not a phase span
|
|
478
|
+
// inside `systems` — is the frame's CPU submission cost, and it is
|
|
479
|
+
// also what makes this frame a PRESENTATION as far as the vitals
|
|
480
|
+
// fold is concerned (`../dev/render-vitals.ts`: no bracket, no
|
|
481
|
+
// display frame). The profiler's phase clock is a stack, so
|
|
482
|
+
// bracketing here cannot truncate an enclosing phase.
|
|
483
|
+
const info = host.renderer.info as RendererCounters | undefined;
|
|
484
|
+
const passesBefore = info?.render.frame ?? 0;
|
|
485
|
+
profiler.beginPhase();
|
|
486
|
+
try {
|
|
487
|
+
advance(elapsed, true, current);
|
|
488
|
+
} finally {
|
|
489
|
+
profiler.endPhase(RENDER_SUBMIT_PHASE);
|
|
490
|
+
}
|
|
491
|
+
// Checked, not assumed — a duck-typed/design-time renderer supplies
|
|
492
|
+
// only what a mount needs, and a mount with no counters must report
|
|
493
|
+
// NOTHING rather than throw once per frame (the classic adapter's
|
|
494
|
+
// `postFrame` pays for the same assumption for the same reason).
|
|
495
|
+
// `gpuMs` is null: this path runs no GPU timer, and the profiler
|
|
496
|
+
// keeps the previous reading rather than inventing a zero.
|
|
497
|
+
if (info) {
|
|
498
|
+
profiler.reportRender({
|
|
499
|
+
gpuMs: null,
|
|
500
|
+
drawCalls: info.render.calls,
|
|
501
|
+
triangles: info.render.triangles,
|
|
502
|
+
geometries: info.memory.geometries,
|
|
503
|
+
textures: info.memory.textures,
|
|
504
|
+
renderPasses: info.render.frame - passesBefore,
|
|
505
|
+
});
|
|
506
|
+
}
|
|
412
507
|
},
|
|
413
508
|
resize(width: number, height: number): void {
|
|
414
509
|
live().setSize(width, height);
|
|
@@ -417,6 +512,13 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
|
|
|
417
512
|
// Unmount FIRST so component effect cleanups run, then tear the
|
|
418
513
|
// runtime down (scoped debug-registry strip).
|
|
419
514
|
root.unmount();
|
|
515
|
+
// The vitals fold owns two `profiler.subscribe` registrations, and
|
|
516
|
+
// `runtime.dispose()`'s `strip(id)` removes the REGISTRATIONS only —
|
|
517
|
+
// this is the one path that ends the subscriptions, exactly as the
|
|
518
|
+
// classic adapter's teardown documents. Before the strip, so a
|
|
519
|
+
// subscription can never outlive the door it feeds.
|
|
520
|
+
renderVitals?.dispose();
|
|
521
|
+
renderVitals = null;
|
|
420
522
|
runtime.dispose();
|
|
421
523
|
restoreRendererConfig();
|
|
422
524
|
},
|
|
@@ -132,6 +132,14 @@ export interface R3FRootRuntime {
|
|
|
132
132
|
/** The mounted world's `SystemAdapters` bag (`mounted.systems`) —
|
|
133
133
|
* `ctx.registerSystemAdapter` writes into it; `debug` is pre-seeded. */
|
|
134
134
|
readonly systemAdapters: SystemAdapters;
|
|
135
|
+
/** The registry every registration of this world's lands in — the game's
|
|
136
|
+
* own when a `Game` hosts it, a private mount-local one otherwise. Exposed
|
|
137
|
+
* (rather than re-derived by the caller) because `createR3FAdapter` seeds
|
|
138
|
+
* the render-vitals door onto THIS registry after fiber's first commit
|
|
139
|
+
* resolves the scene, and two `getDebugRegistry` calls that are "obviously"
|
|
140
|
+
* the same object is exactly the second source of truth the classic
|
|
141
|
+
* adapter's single `debugRegistry` local avoids. */
|
|
142
|
+
readonly debugRegistry: DebugRegistry;
|
|
135
143
|
/** Resolves once the input map load settles (immediately when skipped).
|
|
136
144
|
* Never rejects — a failed load reports loudly and resolves. */
|
|
137
145
|
readonly inputMapReady: Promise<void>;
|
|
@@ -288,6 +296,7 @@ export function createR3FRootContext(
|
|
|
288
296
|
ctx,
|
|
289
297
|
systems,
|
|
290
298
|
systemAdapters,
|
|
299
|
+
debugRegistry,
|
|
291
300
|
inputMapReady,
|
|
292
301
|
setSceneCamera(scene, camera): void {
|
|
293
302
|
const sameScene = ctxRaw['scene'] === scene && sceneIndex !== null;
|