@vgai/engine 0.5.7 → 0.5.9

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 (62) hide show
  1. package/dist/adapter/ingest/contract-debug-adapter.d.ts +35 -0
  2. package/dist/adapter/ingest/contract-debug-adapter.d.ts.map +1 -0
  3. package/dist/adapter/ingest/contract-debug-adapter.js +90 -0
  4. package/dist/adapter/ingest/game-contract.d.ts +60 -1
  5. package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
  6. package/dist/adapter/ingest/game-contract.js +1 -1
  7. package/dist/adapter/setup-three-root-adapter.d.ts.map +1 -1
  8. package/dist/adapter/setup-three-root-adapter.js +94 -2
  9. package/dist/core/game-scoped-slot.d.ts +14 -0
  10. package/dist/core/game-scoped-slot.d.ts.map +1 -0
  11. package/dist/core/game-scoped-slot.js +24 -0
  12. package/dist/core/seeded-random.d.ts.map +1 -1
  13. package/dist/core/seeded-random.js +3 -2
  14. package/dist/core/sim-clock.d.ts.map +1 -1
  15. package/dist/core/sim-clock.js +3 -2
  16. package/dist/dev/performance-profiler.d.ts +13 -7
  17. package/dist/dev/performance-profiler.d.ts.map +1 -1
  18. package/dist/dev/performance-profiler.js +31 -3
  19. package/dist/dev/register-render-vitals.d.ts +95 -0
  20. package/dist/dev/register-render-vitals.d.ts.map +1 -0
  21. package/dist/dev/register-render-vitals.js +182 -0
  22. package/dist/dev/render-census.d.ts +135 -0
  23. package/dist/dev/render-census.d.ts.map +1 -0
  24. package/dist/dev/render-census.js +257 -0
  25. package/dist/dev/render-vitals.d.ts +181 -0
  26. package/dist/dev/render-vitals.d.ts.map +1 -0
  27. package/dist/dev/render-vitals.js +232 -0
  28. package/dist/dev/static-batch-advisor.d.ts +106 -0
  29. package/dist/dev/static-batch-advisor.d.ts.map +1 -0
  30. package/dist/dev/static-batch-advisor.js +141 -0
  31. package/dist/render/render-batch-system.d.ts.map +1 -1
  32. package/dist/render/render-batch-system.js +7 -18
  33. package/dist/render/structural-signature.d.ts +148 -0
  34. package/dist/render/structural-signature.d.ts.map +1 -0
  35. package/dist/render/structural-signature.js +193 -0
  36. package/dist/runtime/debug-registry.d.ts +2 -2
  37. package/dist/runtime/debug-registry.d.ts.map +1 -1
  38. package/dist/runtime/debug-registry.js +4 -3
  39. package/dist/runtime/dev-layers.d.ts.map +1 -1
  40. package/dist/runtime/dev-layers.js +6 -0
  41. package/dist/runtime/game.js +2 -2
  42. package/dist/runtime/gameplay-rng-trap.d.ts.map +1 -1
  43. package/dist/runtime/gameplay-rng-trap.js +2 -1
  44. package/package.json +1 -1
  45. package/schemas/engine-capabilities.json +5 -5
  46. package/src/adapter/ingest/contract-debug-adapter.ts +110 -0
  47. package/src/adapter/ingest/game-contract.ts +63 -1
  48. package/src/adapter/setup-three-root-adapter.ts +94 -2
  49. package/src/core/game-scoped-slot.ts +28 -0
  50. package/src/core/seeded-random.ts +3 -2
  51. package/src/core/sim-clock.ts +3 -2
  52. package/src/dev/performance-profiler.ts +47 -12
  53. package/src/dev/register-render-vitals.ts +249 -0
  54. package/src/dev/render-census.ts +351 -0
  55. package/src/dev/render-vitals.ts +338 -0
  56. package/src/dev/static-batch-advisor.ts +186 -0
  57. package/src/render/render-batch-system.ts +16 -19
  58. package/src/render/structural-signature.ts +231 -0
  59. package/src/runtime/debug-registry.ts +4 -3
  60. package/src/runtime/dev-layers.ts +7 -1
  61. package/src/runtime/game.ts +2 -2
  62. package/src/runtime/gameplay-rng-trap.ts +4 -2
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Projects a game's DECLARED system surface (`window.vgaiGame.systems`, see
3
+ * `game-contract.ts`) onto the host's existing {@link DebugAdapter} — the one
4
+ * seam `game.commands()` / `game.state()` / `game.providers()` already read
5
+ * through for first-party content (`command-listener.ts`'s
6
+ * `dispatchBridgeMethod` → `getActiveSystems().debug`).
7
+ *
8
+ * Why a projection rather than a second door: an agent driving an ingested
9
+ * game must not have to learn a parallel vocabulary. A first-party game
10
+ * registers verbs with `ctx.debug.registerCommand`; an ingested game declares
11
+ * them in its entry shim. Both arrive at the SAME `DebugAdapter`, so every
12
+ * consumer downstream — the CLI's `vgai eval`, `@vgai/e2e`'s `GameClient`, the
13
+ * editor's Debug Console and State Watch panels — works unchanged and unaware
14
+ * of the provenance.
15
+ *
16
+ * What this deliberately does NOT do:
17
+ * - **Validate arguments.** The first-party path validates against a declared
18
+ * Zod tuple; a shim beside a foreign game is plain JS and has none. A verb
19
+ * validates itself and throws its own error, which surfaces wrapped as
20
+ * `DEBUG_COMMAND_FAILED` — the host never invents a schema to check against.
21
+ * - **Fabricate an event ring.** There is no tick loop behind an ingested
22
+ * game's declarations, so `events()` is empty. That is accurate (nothing was
23
+ * emitted), not a stub standing in for a missing feature.
24
+ */
25
+ import type { DebugAdapter } from '../system-adapter';
26
+ import type { VgaiGameSystems } from './game-contract';
27
+ /**
28
+ * Build a {@link DebugAdapter} over a declared system surface, or `null` when
29
+ * the game declared no verbs and no state. `null` is the honest floor: the
30
+ * bridge then reports `DEBUG_ADAPTER_UNAVAILABLE` exactly as it did before,
31
+ * rather than serving an empty adapter that reads as "this game has no state"
32
+ * when the truth is "this game declared nothing".
33
+ */
34
+ export declare function createContractDebugAdapter(systems: VgaiGameSystems | undefined | null): DebugAdapter | null;
35
+ //# sourceMappingURL=contract-debug-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-debug-adapter.d.ts","sourceRoot":"","sources":["../../../src/adapter/ingest/contract-debug-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAsC,MAAM,mBAAmB,CAAC;AAC1F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,eAAe,GAAG,SAAS,GAAG,IAAI,GAC1C,YAAY,GAAG,IAAI,CAuErB"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Projects a game's DECLARED system surface (`window.vgaiGame.systems`, see
3
+ * `game-contract.ts`) onto the host's existing {@link DebugAdapter} — the one
4
+ * seam `game.commands()` / `game.state()` / `game.providers()` already read
5
+ * through for first-party content (`command-listener.ts`'s
6
+ * `dispatchBridgeMethod` → `getActiveSystems().debug`).
7
+ *
8
+ * Why a projection rather than a second door: an agent driving an ingested
9
+ * game must not have to learn a parallel vocabulary. A first-party game
10
+ * registers verbs with `ctx.debug.registerCommand`; an ingested game declares
11
+ * them in its entry shim. Both arrive at the SAME `DebugAdapter`, so every
12
+ * consumer downstream — the CLI's `vgai eval`, `@vgai/e2e`'s `GameClient`, the
13
+ * editor's Debug Console and State Watch panels — works unchanged and unaware
14
+ * of the provenance.
15
+ *
16
+ * What this deliberately does NOT do:
17
+ * - **Validate arguments.** The first-party path validates against a declared
18
+ * Zod tuple; a shim beside a foreign game is plain JS and has none. A verb
19
+ * validates itself and throws its own error, which surfaces wrapped as
20
+ * `DEBUG_COMMAND_FAILED` — the host never invents a schema to check against.
21
+ * - **Fabricate an event ring.** There is no tick loop behind an ingested
22
+ * game's declarations, so `events()` is empty. That is accurate (nothing was
23
+ * emitted), not a stub standing in for a missing feature.
24
+ */
25
+ import { DebugError } from '../../runtime/debug-registry';
26
+ /**
27
+ * Build a {@link DebugAdapter} over a declared system surface, or `null` when
28
+ * the game declared no verbs and no state. `null` is the honest floor: the
29
+ * bridge then reports `DEBUG_ADAPTER_UNAVAILABLE` exactly as it did before,
30
+ * rather than serving an empty adapter that reads as "this game has no state"
31
+ * when the truth is "this game declared nothing".
32
+ */
33
+ export function createContractDebugAdapter(systems) {
34
+ const commandList = systems?.commands ?? [];
35
+ const providerList = systems?.state ?? [];
36
+ if (commandList.length === 0 && providerList.length === 0)
37
+ return null;
38
+ // Snapshot into name-keyed maps once. A later duplicate shadows an earlier
39
+ // one and the listing de-duplicates with it, so `commands()` can never
40
+ // advertise a name `invoke()` would resolve differently.
41
+ const byCommand = new Map(commandList.map((c) => [c.name, c]));
42
+ const byProvider = new Map(providerList.map((p) => [p.name, p]));
43
+ return {
44
+ providers: () => [...byProvider.values()].map((p) => ({ name: p.name, tier: p.tier ?? 'observable' })),
45
+ state: (name) => {
46
+ const provider = byProvider.get(name);
47
+ if (!provider) {
48
+ throw new DebugError('STATE_PROVIDER_NOT_FOUND', `No state provider named "${name}" is declared by this game.`, { registered: [...byProvider.keys()] });
49
+ }
50
+ return provider.read();
51
+ },
52
+ stateAll: () => {
53
+ const out = {};
54
+ for (const [name, provider] of byProvider) {
55
+ // One throwing provider must not cost the caller every other reading —
56
+ // the same per-key isolation the first-party registry gives.
57
+ try {
58
+ out[name] = provider.read();
59
+ }
60
+ catch (err) {
61
+ out[name] = { __error: String(err) };
62
+ }
63
+ }
64
+ return out;
65
+ },
66
+ commands: () => [...byCommand.values()].map((c) => ({
67
+ name: c.name,
68
+ description: c.description,
69
+ argsJsonSchema: c.argsJsonSchema,
70
+ // An ingested game runs entirely in the browser realm the host mounted
71
+ // it in; there is no server leg for its verbs to route to.
72
+ locus: 'client',
73
+ })),
74
+ invoke: async (name, args) => {
75
+ const command = byCommand.get(name);
76
+ if (!command) {
77
+ throw new DebugError('DEBUG_COMMAND_NOT_REGISTERED', `No command named "${name}" is declared by this game.`, { registered: [...byCommand.keys()] });
78
+ }
79
+ try {
80
+ return await command.run(...args);
81
+ }
82
+ catch (err) {
83
+ throw new DebugError('DEBUG_COMMAND_FAILED', `Command "${name}" threw: ${String(err)}`, {
84
+ name,
85
+ });
86
+ }
87
+ },
88
+ events: () => [],
89
+ };
90
+ }
@@ -15,7 +15,7 @@
15
15
  * endpoint by endpoint: capture infers what it can (the scene), the game
16
16
  * declares what inference can't reach (its DOM root, its session lifecycle).
17
17
  *
18
- * Origin (root), F17+F21 (lifecycle).
18
+ * Origin (root), F17+F21 (lifecycle), WO-SYS1 (systems).
19
19
  */
20
20
  /**
21
21
  * Session lifecycle endpoints. Declaring `start` means "I support cold
@@ -32,6 +32,59 @@ export interface VgaiGameLifecycle {
32
32
  pause?(): void;
33
33
  resume?(): void;
34
34
  }
35
+ /**
36
+ * One invokable verb the game exposes. Deliberately the SAME listing shape a
37
+ * first-party game gets from `ctx.debug.registerCommand`
38
+ * (`DebugCommandInfo`/`DebugAdapter.invoke` in `adapter/system-adapter.ts`) —
39
+ * an ingested game's verbs must enumerate through `game.commands()` and run
40
+ * through `game.command(...)` with no second vocabulary for agents to learn.
41
+ *
42
+ * `run` receives the positional argument list exactly as invoked. There is no
43
+ * host-side validation: the contract is plain JS declared beside a foreign
44
+ * game, so it cannot carry a Zod tuple the way `registerCommand` does.
45
+ * `argsJsonSchema` is therefore DOCUMENTATION for listing surfaces, not a
46
+ * gate — a verb validates its own arguments and throws its own errors.
47
+ */
48
+ export interface VgaiGameCommand {
49
+ name: string;
50
+ description?: string;
51
+ /** JSON-Schema projection of the argument list, when the game declares one. */
52
+ argsJsonSchema?: unknown;
53
+ run(...args: unknown[]): unknown | Promise<unknown>;
54
+ }
55
+ /**
56
+ * One named, JSON-serializable state read — the `ctx.debug.registerStateProvider`
57
+ * shape. `tier` carries the same meaning as the first-party one: `observable`
58
+ * is a value the game already computes, `assisted` is one the shim derives.
59
+ */
60
+ export interface VgaiGameStateProvider {
61
+ name: string;
62
+ tier?: 'observable' | 'assisted';
63
+ read(): unknown;
64
+ }
65
+ /**
66
+ * The game's SYSTEM surface: what its content actually IS, as opposed to how
67
+ * it renders. An ingested game's content is not always its scene graph — in a
68
+ * system-driven game the authorable content lives in the game's own model and
69
+ * is reached through the game's own verbs, which `root`/`lifecycle` could not
70
+ * express.
71
+ *
72
+ * Both members are read by exactly one consumer,
73
+ * `adapter/ingest/contract-debug-adapter.ts`, which projects them onto the
74
+ * host's ordinary `DebugAdapter` (`adapter/system-adapter.ts`) — so
75
+ * `game.commands()`/`game.state()` reach an ingested game through the same
76
+ * bridge first-party content uses, with no second vocabulary for agents to
77
+ * learn. That is deliberate scope: this
78
+ * interface describes what a foreign game's entry shim may declare, so every
79
+ * member here is a promise the host must already keep. A member nothing reads
80
+ * would be a shim author implementing a hook that is never called.
81
+ */
82
+ export interface VgaiGameSystems {
83
+ /** Verbs, enumerated by `game.commands()` and run by `game.command(...)`. */
84
+ commands?: VgaiGameCommand[];
85
+ /** State reads, enumerated by `game.providers()` and read by `game.state(...)`. */
86
+ state?: VgaiGameStateProvider[];
87
+ }
35
88
  export interface VgaiGameContract {
36
89
  /** Bump only on breaking shape changes; additive endpoints keep version 1. */
37
90
  contractVersion: 1;
@@ -43,6 +96,12 @@ export interface VgaiGameContract {
43
96
  */
44
97
  root?: HTMLElement;
45
98
  lifecycle?: VgaiGameLifecycle;
99
+ /**
100
+ * The game's own systems. Absent means "this game declared no system
101
+ * surface" — every dependent editor surface then shows a named absence, not
102
+ * an empty pretend-palette.
103
+ */
104
+ systems?: VgaiGameSystems;
46
105
  }
47
106
  /**
48
107
  * Read the declared contract, if any. The `contractVersion` gate is the
@@ -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,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;CAC/B;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,gBAAgB,GAAG,IAAI,CAI1D"}
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;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,gBAAgB,GAAG,IAAI,CAI1D"}
@@ -15,7 +15,7 @@
15
15
  * endpoint by endpoint: capture infers what it can (the scene), the game
16
16
  * declares what inference can't reach (its DOM root, its session lifecycle).
17
17
  *
18
- * Origin (root), F17+F21 (lifecycle).
18
+ * Origin (root), F17+F21 (lifecycle), WO-SYS1 (systems).
19
19
  */
20
20
  /**
21
21
  * Read the declared contract, if any. The `contractVersion` gate is the
@@ -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;AAiCrD,OAAO,EAAE,KAAK,mBAAmB,EAA2B,MAAM,4BAA4B,CAAC;AAM/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;CAmuBpE"}
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;CA4zBpE"}
@@ -26,8 +26,10 @@ import { createSeededRandom, DEFAULT_SEEDED_RANDOM_SEED, getSeededRandom, } from
26
26
  import { createSimClock, getSimClock } from '../core/sim-clock';
27
27
  import { createSystemRunner } from '../core/system-runner';
28
28
  import { createDebugDraw } from '../dev/debug-draw';
29
+ import { registerRenderVitals } from '../dev/register-render-vitals';
29
30
  import { createRenderDebugAdapter, frameCaptureContextFor, } from '../dev/render-debug-adapter';
30
31
  import { collectRenderMemory } from '../dev/render-memory';
32
+ import { RENDER_SUBMIT_PHASE } from '../dev/render-vitals';
31
33
  import { createWebGLFrameCapture } from '../dev/webgl-frame-capture';
32
34
  import { createWebGLGpuTimer } from '../dev/webgl-gpu-timer';
33
35
  import { createSceneIndex } from '../ecs/scene-index';
@@ -41,6 +43,7 @@ import { updateSceneLODs } from '../render/lod';
41
43
  import { disposeSparkRendererWhenIdle, SPARK_DISCOVERY_INTERVAL_MS, sceneHasGaussianSplat, shouldDiscoverGaussianSplat, } from '../render/spark-renderer-lifecycle';
42
44
  import { ViewportShadingRenderer } from '../render/viewport-shading';
43
45
  import { createDebugRegistry, getDebugRegistry, } from '../runtime/debug-registry';
46
+ import { devLayersEnabled } from '../runtime/dev-layers';
44
47
  import { disposeDebrisSubtree } from '../runtime/game';
45
48
  import { setupAudio } from '../setup/setup-audio';
46
49
  import { setupParticles } from '../setup/setup-particles';
@@ -150,6 +153,10 @@ export class SetupThreeRootAdapter {
150
153
  : null;
151
154
  const viewportShading = new ViewportShadingRenderer();
152
155
  let viewportShadingMode = 'solid';
156
+ /** Top-level `renderer.render()` calls the last presentation cost — written
157
+ * by the render system, read by `postFrame`. See both for why the count is
158
+ * latched rather than differenced at report time. */
159
+ let lastPresentationPasses = 0;
153
160
  // --- System runner (engine-level systems) ---
154
161
  const systems = createSystemRunner(host.game?.profiler.systemObserver, 'three');
155
162
  // D15/T-D15.3/.5: pass the LIVE shared game tick (not a locally-counted
@@ -201,7 +208,8 @@ export class SetupThreeRootAdapter {
201
208
  }, { name: 'materials-and-particles' });
202
209
  if (!headless) {
203
210
  systems.add('render', (dt) => {
204
- if (host.game?.profiler.enabled)
211
+ const profiler = host.game?.profiler;
212
+ if (profiler?.enabled)
205
213
  gpuTimer?.begin();
206
214
  if (sparkRenderer)
207
215
  scene.add(sparkRenderer);
@@ -211,6 +219,21 @@ export class SetupThreeRootAdapter {
211
219
  // patched context — in the SAME finally as gpuTimer.end(). Spark is
212
220
  // added before this so its draws fall inside the captured frame.
213
221
  renderDebugWiring?.beforeRender();
222
+ // Issue #1504: CPU render submission, bracketed as its OWN profiler
223
+ // phase nested inside the frame's enclosing `render` phase. This is
224
+ // the frame's only isolated measurement of "how long did it take to
225
+ // hand the draws to the driver" — the `render` phase around it also
226
+ // contains physics-debug redraw and every other world's render work,
227
+ // and a system span would key the reading to this adapter's system
228
+ // NAME. The profiler's phase clock is a stack precisely so this
229
+ // bracket cannot truncate its parent (`dev/performance-profiler.ts`).
230
+ // It is also what makes a frame a PRESENTATION as far as the vitals
231
+ // fold is concerned: no draw, no bracket, no display frame.
232
+ profiler?.beginPhase();
233
+ // `info.render.frame` counts top-level renderer.render() calls and
234
+ // survives `info.reset()`, so its delta across the draw is how many
235
+ // passes the composer chain (plus shadows) actually cost.
236
+ const passesBefore = renderer.info.render.frame;
214
237
  try {
215
238
  viewportShading.render(scene, viewportShadingMode, () => composer.render(dt),
216
239
  // Scene authoring can temporarily attach editor infrastructure
@@ -219,6 +242,8 @@ export class SetupThreeRootAdapter {
219
242
  (mesh) => camera.layers.test(mesh.layers));
220
243
  }
221
244
  finally {
245
+ profiler?.endPhase(RENDER_SUBMIT_PHASE);
246
+ lastPresentationPasses = renderer.info.render.frame - passesBefore;
222
247
  renderDebugWiring?.afterRender();
223
248
  sparkRenderer?.removeFromParent();
224
249
  gpuTimer?.end();
@@ -239,13 +264,25 @@ export class SetupThreeRootAdapter {
239
264
  const postFrame = () => {
240
265
  if (ownsInput)
241
266
  input.endFrame();
242
- if (host.game?.profiler.enabled) {
267
+ // `renderer.info` is checked, not assumed: a headless/stub renderer
268
+ // supplies only what the runtime needs to mount, and a mount with no
269
+ // counters must report NOTHING rather than throw once per frame. This
270
+ // used to be unreachable because only the editor ever enabled the
271
+ // profiler; the engine enables it itself under the dev gate now (see the
272
+ // render-vitals seed below), so the assumption has to be paid for.
273
+ if (host.game?.profiler.enabled && renderer.info) {
243
274
  host.game.profiler.reportRender({
244
275
  gpuMs: gpuTimer?.poll() ?? null,
245
276
  drawCalls: renderer.info.render.calls,
246
277
  triangles: renderer.info.render.triangles,
247
278
  geometries: renderer.info.memory.geometries,
248
279
  textures: renderer.info.memory.textures,
280
+ // The LAST presentation's pass count, not a delta taken here: under
281
+ // the display-rate loop this hook runs once per fixed SUBSTEP, so a
282
+ // delta measured at this point would read as 0 on every substep that
283
+ // did not draw. Same one-frame-warm semantics as the renderer.info
284
+ // counters beside it.
285
+ renderPasses: lastPresentationPasses,
249
286
  });
250
287
  }
251
288
  };
@@ -431,6 +468,48 @@ export class SetupThreeRootAdapter {
431
468
  ctx.random =
432
469
  (host.game ? getSeededRandom(host.game) : null) ??
433
470
  createSeededRandom(DEFAULT_SEEDED_RANDOM_SEED);
471
+ // --- Live render vitals (issue #1504) -------------------------------------
472
+ // Engine-owned and first-party, seeded HERE like `renderDebug` above: a
473
+ // running game must be able to explain its own frame cost through the debug
474
+ // registry, with no capability to install and nothing for a game to write.
475
+ //
476
+ // THE GATE, in one place. Three conditions, and the reason for each:
477
+ // 1. `devLayersEnabled()` — the ONE owner of "is this a dev/editor
478
+ // context" (`runtime/dev-layers.ts`). A ship build registers nothing,
479
+ // subscribes to nothing, and never enables the profiler.
480
+ // 2. a `Game` shell exists — the readings are folded out of that game's
481
+ // profiler frames, and a bare mount has no profiler to fold.
482
+ // 3. not headless — a headless mount registers no render system at all,
483
+ // so it never brackets a submission and never presents a frame. Its
484
+ // vitals could only ever read empty, and registering a door that is
485
+ // structurally incapable of answering is worse than not having one.
486
+ //
487
+ // The enabled-gating question, resolved: the profiler is AUTO-ENABLED here
488
+ // rather than given a second, cheaper always-on counter tier. A counter
489
+ // tier would be a parallel measurement path — exactly the side ledger this
490
+ // work exists to avoid — and the profiler already has the one property
491
+ // that made the choice: `enabled` is a plain flag whose cost is per-frame
492
+ // bookkeeping the editor ALREADY pays (it sets `profiler.enabled = true`
493
+ // on play, `CenterDocuments.tsx`), so under the dev gate this is not a new
494
+ // cost, it is the same cost arriving a little earlier. Nothing here ever
495
+ // turns it OFF: the profiler's other owners (the Performance panel,
496
+ // `render-control.ts`'s perfSample) save and restore the prior state, so a
497
+ // dev session that enabled it at mount keeps it enabled.
498
+ let renderVitals = null;
499
+ const seedRenderVitals = () => {
500
+ if (!host.game || headless || !devLayersEnabled())
501
+ return;
502
+ renderVitals?.dispose();
503
+ host.game.profiler.enabled = true;
504
+ renderVitals = registerRenderVitals({
505
+ registry: debugRegistry,
506
+ worldId: this.id,
507
+ profiler: host.game.profiler,
508
+ scene,
509
+ renderer,
510
+ });
511
+ };
512
+ seedRenderVitals();
434
513
  // Snapshot of the engine-owned adapter kinds, taken right after seeding
435
514
  // and before any setup() has had a chance to run. `hotReload`/`disposeGame`
436
515
  // use this to strip every GAME-registered kind (including an override of a
@@ -623,6 +702,13 @@ export class SetupThreeRootAdapter {
623
702
  // roots remain alive during root replacement. A bare standalone adapter
624
703
  // owns the registry and can clear it globally.
625
704
  safeStep('debugRegistry.strip', () => host.game ? debugRegistry.strip(this.id) : debugRegistry.strip());
705
+ // The strip above removes the vitals REGISTRATIONS (their provenance is
706
+ // this world). This ends the fold's profiler subscription — the one
707
+ // resource `registerRenderVitals` allocates that a strip cannot reach.
708
+ safeStep('renderVitals.dispose', () => {
709
+ renderVitals?.dispose();
710
+ renderVitals = null;
711
+ });
626
712
  // Release analyser taps BEFORE the audio teardown below. A consumer
627
713
  // (the editor's meter poll) should dispose its own handle, but Stop must
628
714
  // never leak taps regardless.
@@ -735,6 +821,12 @@ export class SetupThreeRootAdapter {
735
821
  // every react-door registration (whose `useEffect` cleanup never
736
822
  // re-fires to restore them after someone else's strip).
737
823
  safeStep('debugRegistry.strip', () => debugRegistry.strip(this.id));
824
+ // The strip above takes the ENGINE's own vitals registrations with it
825
+ // (they carry this world's provenance, which is what makes the game's
826
+ // own re-seed silent). Re-seed them before the incoming setup runs, so a
827
+ // warm restart does not silently cost a game its render vitals — and so
828
+ // the fold starts clean rather than carrying the outgoing game's frames.
829
+ safeStep('renderVitals re-seed', seedRenderVitals);
738
830
  currentCleanup = await newSetup(ctx, newEditorPreview);
739
831
  };
740
832
  return {
@@ -0,0 +1,14 @@
1
+ /**
2
+ * A non-enumerable slot carried by the game object itself.
3
+ *
4
+ * Packaged editor play can legitimately involve two copies of `@vgai/engine`:
5
+ * the editor bundle creates the Game, while the project's Vite graph creates
6
+ * its root adapter. Module-local WeakMaps cannot cross that boundary. A
7
+ * registry-backed symbol can, while keeping the internal value off the public
8
+ * string-keyed Game surface.
9
+ */
10
+ export declare function createGameScopedSlot<T>(name: string): {
11
+ set(owner: object, value: T): void;
12
+ get(owner: object): T | null;
13
+ };
14
+ //# sourceMappingURL=game-scoped-slot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"game-scoped-slot.d.ts","sourceRoot":"","sources":["../../src/core/game-scoped-slot.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG;IACrD,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IACnC,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC;CAC9B,CAeA"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * A non-enumerable slot carried by the game object itself.
3
+ *
4
+ * Packaged editor play can legitimately involve two copies of `@vgai/engine`:
5
+ * the editor bundle creates the Game, while the project's Vite graph creates
6
+ * its root adapter. Module-local WeakMaps cannot cross that boundary. A
7
+ * registry-backed symbol can, while keeping the internal value off the public
8
+ * string-keyed Game surface.
9
+ */
10
+ export function createGameScopedSlot(name) {
11
+ const key = Symbol.for(`@vgai/engine/game-scoped/${name}`);
12
+ return {
13
+ set(owner, value) {
14
+ Object.defineProperty(owner, key, {
15
+ configurable: true,
16
+ enumerable: false,
17
+ value,
18
+ });
19
+ },
20
+ get(owner) {
21
+ return owner[key] ?? null;
22
+ },
23
+ };
24
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"seeded-random.d.ts","sourceRoot":"","sources":["../../src/core/seeded-random.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAIH,0EAA0E;AAC1E,eAAO,MAAM,eAAe,aAAa,CAAC;AAE1C;;;;;;gFAMgF;AAChF,eAAO,MAAM,0BAA0B,aAAa,CAAC;AAErD;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,MAAM,CAAC;IACX,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,MAAM,CAAC;IACnC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAsBD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,YAAY,CAgDpE;AAeD;yEACyE;AACzE,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAE9E;AAED;;;2DAG2D;AAC3D,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAElE"}
1
+ {"version":3,"file":"seeded-random.d.ts","sourceRoot":"","sources":["../../src/core/seeded-random.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,0EAA0E;AAC1E,eAAO,MAAM,eAAe,aAAa,CAAC;AAE1C;;;;;;gFAMgF;AAChF,eAAO,MAAM,0BAA0B,aAAa,CAAC;AAErD;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,MAAM,CAAC;IACX,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,MAAM,CAAC;IACnC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAsBD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,YAAY,CAgDpE;AAeD;yEACyE;AACzE,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAE9E;AAED;;;2DAG2D;AAC3D,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAElE"}
@@ -26,6 +26,7 @@
26
26
  * stay orthogonal; only the generator function itself is shared).
27
27
  */
28
28
  import { createMulberry32 } from '../runtime/render-seed';
29
+ import { createGameScopedSlot } from './game-scoped-slot';
29
30
  /** The name `ctx.random()` (called with no `.stream(...)`) draws from. */
30
31
  export const GAMEPLAY_STREAM = 'gameplay';
31
32
  /** The fixed fallback seed a `SeededRandom` boots with when no explicit seed
@@ -98,7 +99,7 @@ export function createSeededRandom(initialSeed) {
98
99
  }
99
100
  // ---------------------------------------------------------------------------
100
101
  // Game-scoped registry — mirrors `runtime/debug-registry.ts`'s
101
- // `registerDebugRegistry`/`getDebugRegistry` WeakMap pattern exactly, but
102
+ // `registerDebugRegistry`/`getDebugRegistry` game-slot pattern exactly, but
102
103
  // keyed on a bare `object` (not `Game`) so this module never needs to import
103
104
  // `runtime/game.ts` even as a type — `core/` stays independent of `runtime/`
104
105
  // except for the one explicit, documented `render-seed.ts` reuse above.
@@ -106,7 +107,7 @@ export function createSeededRandom(initialSeed) {
106
107
  // (the `GameInternal` shell) as the key, exactly like it does for
107
108
  // `registerDebugRegistry(gameInternal, debugRegistry)`.
108
109
  // ---------------------------------------------------------------------------
109
- const registryByOwner = new WeakMap();
110
+ const registryByOwner = createGameScopedSlot('seeded-random');
110
111
  /** Called once by `createGame`, right after both the seeded-random surface
111
112
  * and the Game shell object exist — mirrors `registerDebugRegistry`. */
112
113
  export function registerSeededRandom(owner, random) {
@@ -1 +1 @@
1
- {"version":3,"file":"sim-clock.d.ts","sourceRoot":"","sources":["../../src/core/sim-clock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiHG;AAEH,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAEpC,sFAAsF;AACtF,MAAM,WAAW,cAAc;IAC7B,4DAA4D;IAC5D,MAAM,IAAI,IAAI,CAAC;CAChB;AAED,iDAAiD;AACjD,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAC3C;AAED,qEAAqE;AACrE,MAAM,WAAW,QAAQ;IACvB,wEAAwE;IACxE,GAAG,IAAI,MAAM,CAAC;IACd,2EAA2E;IAC3E,OAAO,IAAI,MAAM,CAAC;IAClB;;;;;OAKG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE;;;OAGG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,IAAI,EAAE,IAAI,CAAC,EAAE,kBAAkB,GAAG,cAAc,CAAC;IAClF;;;;;OAKG;IACH,YAAY,CAAC,GAAG,EAAE,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,cAAc,CAAC;CACpE;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAiB,SAAQ,QAAQ;IAChD;;;;;OAKG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;;OAKG;IACH,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC;CACjD;AAyBD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,gBAAgB,CAiJzE;AAWD,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAE7E;AAED;;;;;;;;+EAQ+E;AAC/E,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAElE"}
1
+ {"version":3,"file":"sim-clock.d.ts","sourceRoot":"","sources":["../../src/core/sim-clock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiHG;AAEH,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAGpC,sFAAsF;AACtF,MAAM,WAAW,cAAc;IAC7B,4DAA4D;IAC5D,MAAM,IAAI,IAAI,CAAC;CAChB;AAED,iDAAiD;AACjD,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAC3C;AAED,qEAAqE;AACrE,MAAM,WAAW,QAAQ;IACvB,wEAAwE;IACxE,GAAG,IAAI,MAAM,CAAC;IACd,2EAA2E;IAC3E,OAAO,IAAI,MAAM,CAAC;IAClB;;;;;OAKG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE;;;OAGG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,IAAI,EAAE,IAAI,CAAC,EAAE,kBAAkB,GAAG,cAAc,CAAC;IAClF;;;;;OAKG;IACH,YAAY,CAAC,GAAG,EAAE,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,cAAc,CAAC;CACpE;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAiB,SAAQ,QAAQ;IAChD;;;;;OAKG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;;OAKG;IACH,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC;CACjD;AAyBD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,gBAAgB,CAiJzE;AAWD,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAE7E;AAED;;;;;;;;+EAQ+E;AAC/E,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAElE"}
@@ -112,6 +112,7 @@
112
112
  * - **No wall clock.** This module reads no system timer and creates no
113
113
  * host timeout; every time value comes from the runtime's own accumulator.
114
114
  */
115
+ import { createGameScopedSlot } from './game-scoped-slot';
115
116
  const NOOP_HANDLE = { cancel() { } };
116
117
  /** `fetch`-shaped abort rejection: a `DOMException` named `AbortError`. */
117
118
  function abortError(message) {
@@ -268,11 +269,11 @@ export function createSimClock(options) {
268
269
  }
269
270
  // ---------------------------------------------------------------------------
270
271
  // Game-scoped registry — mirrors `core/seeded-random.ts`'s
271
- // `registerSeededRandom`/`getSeededRandom` WeakMap pattern exactly, keyed on a
272
+ // `registerSeededRandom`/`getSeededRandom` slot pattern exactly, keyed on a
272
273
  // bare `object` (not `Game`) so `core/` never imports `runtime/`. `createGame`
273
274
  // is the one real registrant, passing the `GameInternal` shell as the key.
274
275
  // ---------------------------------------------------------------------------
275
- const clockByOwner = new WeakMap();
276
+ const clockByOwner = createGameScopedSlot('sim-clock');
276
277
  /** Called once by `createGame`, right after the clock and the Game shell exist. */
277
278
  export function registerSimClock(owner, clock) {
278
279
  clockByOwner.set(owner, clock);
@@ -33,6 +33,15 @@ export interface PerformanceRenderStats {
33
33
  readonly triangles: number;
34
34
  readonly geometries: number;
35
35
  readonly textures: number;
36
+ /**
37
+ * How many top-level `renderer.render()` submissions the last presentation
38
+ * took (`renderer.info.render.frame`'s delta across one draw) — a composer
39
+ * chain, a shadow pass and a post stack each cost one. `null` (never a
40
+ * fabricated 0, same rule as `gpuMs`) when no reporter counted them: a
41
+ * reporter that omits `renderPasses` is saying it does not know, which is
42
+ * different from saying nothing was drawn.
43
+ */
44
+ readonly renderPasses: number | null;
36
45
  }
37
46
  export interface PerformanceFrame {
38
47
  readonly id: number;
@@ -71,13 +80,7 @@ export interface PerformanceSnapshot {
71
80
  readonly phases: readonly PerformanceTiming[];
72
81
  readonly systems: readonly PerformanceTiming[];
73
82
  readonly components: readonly PerformanceTiming[];
74
- readonly render: {
75
- readonly gpuMs: number | null;
76
- readonly drawCalls: number;
77
- readonly triangles: number;
78
- readonly geometries: number;
79
- readonly textures: number;
80
- };
83
+ readonly render: PerformanceRenderStats;
81
84
  }
82
85
  /** Game-owned, bounded external store for browser-native performance diagnostics. */
83
86
  export declare function createPerformanceProfiler(initiallyEnabled?: boolean): {
@@ -101,6 +104,9 @@ export declare function createPerformanceProfiler(initiallyEnabled?: boolean): {
101
104
  triangles: number;
102
105
  geometries: number;
103
106
  textures: number;
107
+ /** Omit when this reporter does not count passes — see
108
+ * {@link PerformanceRenderStats.renderPasses}. */
109
+ renderPasses?: number;
104
110
  }): void;
105
111
  endFrame(): void;
106
112
  subscribe(listener: () => void): () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"performance-profiler.d.ts","sourceRoot":"","sources":["../../src/dev/performance-profiler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"performance-profiler.d.ts","sourceRoot":"","sources":["../../src/dev/performance-profiler.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED,yEAAyE;AACzE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;;;OAOG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC9C;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;IAC7C;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,CAAC;IAChD;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC;CACzC;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC7C,QAAQ,CAAC,MAAM,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC/C,QAAQ,CAAC,UAAU,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAClD,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC;CACzC;AAUD,qFAAqF;AACrF,wBAAgB,yBAAyB,CAAC,gBAAgB,UAAQ;aA8E3C,OAAO;;;;;;;mBA8CX,MAAM;;;yBAqBF,MAAM,SAAS,MAAM,QAAQ,MAAM;;;uBAiBnC,MAAM;wBAWL;QAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB;2DACmD;QACnD,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB;;wBAqCmB,MAAM,IAAI;;;EAejC;AAED,MAAM,MAAM,mBAAmB,GAAG,UAAU,CAAC,OAAO,yBAAyB,CAAC,CAAC"}