@vgai/engine 0.5.15 → 0.5.16

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 (174) hide show
  1. package/dist/adapter/adapter-module.d.ts +228 -17
  2. package/dist/adapter/adapter-module.d.ts.map +1 -1
  3. package/dist/adapter/adapter-module.js +224 -42
  4. package/dist/adapter/authoring-seam-contract.d.ts +530 -0
  5. package/dist/adapter/authoring-seam-contract.d.ts.map +1 -0
  6. package/dist/adapter/authoring-seam-contract.js +162 -0
  7. package/dist/adapter/authoring.d.ts +237 -50
  8. package/dist/adapter/authoring.d.ts.map +1 -1
  9. package/dist/adapter/authoring.js +2 -1
  10. package/dist/adapter/entrypoint-selection-source.d.ts +35 -0
  11. package/dist/adapter/entrypoint-selection-source.d.ts.map +1 -0
  12. package/dist/adapter/entrypoint-selection-source.js +151 -0
  13. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts +7 -1
  14. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
  15. package/dist/adapter/finders/scenes-from-entrypoint-selection.js +142 -21
  16. package/dist/adapter/first-party-systems.d.ts +13 -3
  17. package/dist/adapter/first-party-systems.d.ts.map +1 -1
  18. package/dist/adapter/first-party-systems.js +10 -3
  19. package/dist/adapter/index.d.ts +7 -2
  20. package/dist/adapter/index.d.ts.map +1 -1
  21. package/dist/adapter/index.js +4 -0
  22. package/dist/adapter/ingest/contract-system-adapters.d.ts +1 -1
  23. package/dist/adapter/ingest/contract-system-adapters.d.ts.map +1 -1
  24. package/dist/adapter/ingest/contract-system-adapters.js +13 -25
  25. package/dist/adapter/ingest/game-contract-seams.d.ts +214 -0
  26. package/dist/adapter/ingest/game-contract-seams.d.ts.map +1 -0
  27. package/dist/adapter/ingest/game-contract-seams.js +62 -0
  28. package/dist/adapter/ingest/game-contract.d.ts +5 -3
  29. package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
  30. package/dist/adapter/ingest/game-contract.js +4 -1
  31. package/dist/adapter/ingest/merge-debug-adapters.d.ts +52 -0
  32. package/dist/adapter/ingest/merge-debug-adapters.d.ts.map +1 -0
  33. package/dist/adapter/ingest/merge-debug-adapters.js +141 -0
  34. package/dist/adapter/ingest/observation-debug-adapter.d.ts +56 -0
  35. package/dist/adapter/ingest/observation-debug-adapter.d.ts.map +1 -0
  36. package/dist/adapter/ingest/observation-debug-adapter.js +131 -0
  37. package/dist/adapter/ingest/scene-capture.d.ts +38 -13
  38. package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
  39. package/dist/adapter/ingest/scene-capture.js +101 -24
  40. package/dist/adapter/ingest/visible-capture-window.d.ts +22 -5
  41. package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -1
  42. package/dist/adapter/ingest/visible-capture-window.js +46 -7
  43. package/dist/adapter/native-debug-module.d.ts +43 -0
  44. package/dist/adapter/native-debug-module.d.ts.map +1 -0
  45. package/dist/adapter/native-debug-module.js +230 -0
  46. package/dist/adapter/object3d-authoring-subject.d.ts +32 -0
  47. package/dist/adapter/object3d-authoring-subject.d.ts.map +1 -0
  48. package/dist/adapter/object3d-authoring-subject.js +25 -0
  49. package/dist/adapter/root-adapter.d.ts +21 -56
  50. package/dist/adapter/root-adapter.d.ts.map +1 -1
  51. package/dist/adapter/root-adapter.js +4 -20
  52. package/dist/adapter/root-seam-contract.d.ts +124 -0
  53. package/dist/adapter/root-seam-contract.d.ts.map +1 -0
  54. package/dist/adapter/root-seam-contract.js +36 -0
  55. package/dist/adapter/seam-evidence.d.ts +118 -0
  56. package/dist/adapter/seam-evidence.d.ts.map +1 -0
  57. package/dist/adapter/seam-evidence.js +231 -0
  58. package/dist/adapter/setup-three-root-adapter.d.ts +2 -3
  59. package/dist/adapter/setup-three-root-adapter.d.ts.map +1 -1
  60. package/dist/adapter/setup-three-root-adapter.js +2 -3
  61. package/dist/adapter/system-adapter.d.ts +6 -1
  62. package/dist/adapter/system-adapter.d.ts.map +1 -1
  63. package/dist/adapter/system-seam-contract.d.ts +354 -0
  64. package/dist/adapter/system-seam-contract.d.ts.map +1 -0
  65. package/dist/adapter/system-seam-contract.js +95 -0
  66. package/dist/animation/xstate-animation-binding.d.ts +6 -0
  67. package/dist/animation/xstate-animation-binding.d.ts.map +1 -1
  68. package/dist/animation/xstate-animation-binding.js +142 -4
  69. package/dist/asset-formats/collider.d.ts +1 -1
  70. package/dist/asset-formats/mesh.d.ts +2 -2
  71. package/dist/asset-formats/particles.d.ts +1 -1
  72. package/dist/canvas-react/engine-bridge.d.ts +6 -6
  73. package/dist/canvas-react/engine-bridge.d.ts.map +1 -1
  74. package/dist/canvas-react/engine-bridge.js +2 -2
  75. package/dist/canvas-react/index.d.ts +2 -2
  76. package/dist/canvas-react/index.d.ts.map +1 -1
  77. package/dist/canvas-react/index.js +1 -1
  78. package/dist/canvas-react/pixi-react-adapter.d.ts +15 -9
  79. package/dist/canvas-react/pixi-react-adapter.d.ts.map +1 -1
  80. package/dist/canvas-react/pixi-react-adapter.js +43 -29
  81. package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -1
  82. package/dist/canvas-react/pixi-react-root-factory.js +4 -0
  83. package/dist/canvas-react/world-context.d.ts +22 -12
  84. package/dist/canvas-react/world-context.d.ts.map +1 -1
  85. package/dist/canvas-react/world-context.js +12 -51
  86. package/dist/ecs/user-data.d.ts +5 -0
  87. package/dist/ecs/user-data.d.ts.map +1 -1
  88. package/dist/ecs/user-data.js +3 -0
  89. package/dist/input/input-manager.d.ts +49 -0
  90. package/dist/input/input-manager.d.ts.map +1 -1
  91. package/dist/input/input-manager.js +127 -0
  92. package/dist/manifest/schema.d.ts +9 -9
  93. package/dist/pixi/authoring.d.ts.map +1 -1
  94. package/dist/pixi/authoring.js +220 -1
  95. package/dist/pixi/ingest.d.ts +2 -1
  96. package/dist/pixi/ingest.d.ts.map +1 -1
  97. package/dist/pixi/ingest.js +4 -2
  98. package/dist/pixi/scene-capture.d.ts +9 -1
  99. package/dist/pixi/scene-capture.d.ts.map +1 -1
  100. package/dist/pixi/scene-capture.js +9 -6
  101. package/dist/render/particles-factory.d.ts +13 -6
  102. package/dist/render/particles-factory.d.ts.map +1 -1
  103. package/dist/render/particles-factory.js +6 -3
  104. package/dist/runtime/create-runtime.d.ts +23 -46
  105. package/dist/runtime/create-runtime.d.ts.map +1 -1
  106. package/dist/runtime/create-runtime.js +22 -67
  107. package/dist/runtime/game.d.ts +29 -71
  108. package/dist/runtime/game.d.ts.map +1 -1
  109. package/dist/runtime/game.js +15 -54
  110. package/dist/runtime/mount-game.d.ts.map +1 -1
  111. package/dist/runtime/mount-game.js +12 -1
  112. package/dist/runtime/mount-manifest.d.ts.map +1 -1
  113. package/dist/runtime/mount-manifest.js +4 -5
  114. package/dist/runtime/types.d.ts +18 -0
  115. package/dist/runtime/types.d.ts.map +1 -1
  116. package/dist/setup/setup-renderer.d.ts +3 -4
  117. package/dist/setup/setup-renderer.d.ts.map +1 -1
  118. package/dist/setup/setup-renderer.js +3 -4
  119. package/dist/world3d-react/engine-bridge.d.ts +1 -1
  120. package/dist/world3d-react/engine-bridge.js +1 -1
  121. package/dist/world3d-react/r3f-adapter.d.ts +7 -0
  122. package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
  123. package/dist/world3d-react/r3f-adapter.js +50 -21
  124. package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
  125. package/dist/world3d-react/r3f-root-factory.js +4 -0
  126. package/dist/world3d-react/world-context.d.ts +9 -3
  127. package/dist/world3d-react/world-context.d.ts.map +1 -1
  128. package/dist/world3d-react/world-context.js +5 -18
  129. package/package.json +1 -1
  130. package/schemas/engine-capabilities.json +7 -7
  131. package/src/adapter/adapter-module.ts +407 -50
  132. package/src/adapter/authoring-seam-contract.ts +211 -0
  133. package/src/adapter/authoring.ts +242 -50
  134. package/src/adapter/entrypoint-selection-source.ts +159 -0
  135. package/src/adapter/finders/scenes-from-entrypoint-selection.ts +159 -28
  136. package/src/adapter/first-party-systems.ts +17 -3
  137. package/src/adapter/index.ts +51 -1
  138. package/src/adapter/ingest/contract-system-adapters.ts +26 -30
  139. package/src/adapter/ingest/game-contract-seams.ts +85 -0
  140. package/src/adapter/ingest/game-contract.ts +9 -4
  141. package/src/adapter/ingest/merge-debug-adapters.ts +193 -0
  142. package/src/adapter/ingest/observation-debug-adapter.ts +158 -0
  143. package/src/adapter/ingest/scene-capture.ts +126 -23
  144. package/src/adapter/ingest/visible-capture-window.ts +63 -9
  145. package/src/adapter/native-debug-module.ts +307 -0
  146. package/src/adapter/object3d-authoring-subject.ts +50 -0
  147. package/src/adapter/root-adapter.ts +21 -56
  148. package/src/adapter/root-seam-contract.ts +56 -0
  149. package/src/adapter/seam-evidence.ts +336 -0
  150. package/src/adapter/setup-three-root-adapter.ts +4 -6
  151. package/src/adapter/system-adapter.ts +6 -1
  152. package/src/adapter/system-seam-contract.ts +121 -0
  153. package/src/animation/xstate-animation-binding.ts +151 -4
  154. package/src/canvas-react/engine-bridge.ts +6 -6
  155. package/src/canvas-react/index.ts +2 -1
  156. package/src/canvas-react/pixi-react-adapter.tsx +55 -34
  157. package/src/canvas-react/pixi-react-root-factory.tsx +4 -0
  158. package/src/canvas-react/world-context.ts +26 -101
  159. package/src/ecs/user-data.ts +5 -0
  160. package/src/input/input-manager.ts +122 -0
  161. package/src/pixi/authoring.ts +236 -1
  162. package/src/pixi/ingest.ts +9 -2
  163. package/src/pixi/scene-capture.ts +20 -5
  164. package/src/render/particles-factory.ts +16 -6
  165. package/src/runtime/create-runtime.ts +41 -106
  166. package/src/runtime/game.ts +41 -111
  167. package/src/runtime/mount-game.ts +19 -1
  168. package/src/runtime/mount-manifest.ts +4 -5
  169. package/src/runtime/types.ts +22 -0
  170. package/src/setup/setup-renderer.ts +3 -4
  171. package/src/world3d-react/engine-bridge.ts +1 -1
  172. package/src/world3d-react/r3f-adapter.tsx +64 -24
  173. package/src/world3d-react/r3f-root-factory.tsx +4 -0
  174. package/src/world3d-react/world-context.ts +11 -36
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Projects an ADAPTER's declared observation slots
3
+ * (`AdapterDefinition.observation`, `adapter/adapter-module.ts`) onto the host's
4
+ * existing {@link DebugAdapter} — the one seam `game.providers()` /
5
+ * `game.state()` / `game.commands()` already read through
6
+ * (`command-listener.ts`'s `dispatchBridgeMethod` → `getActiveSystems().debug`).
7
+ *
8
+ * This is the SIBLING of `contract-debug-adapter.ts` and deliberately the same
9
+ * shape: a projection rather than a second door. The two differ only in WHO
10
+ * declared the surface — the game itself, on `window.vgaiGame.systems`, or the
11
+ * game's adapter module beside it — and a consumer downstream must not be able
12
+ * to tell them apart. `merge-debug-adapters.ts` is what folds both onto the one
13
+ * slot.
14
+ *
15
+ * ## What `game` is
16
+ *
17
+ * The host's to hand over, and for an INGEST mount it is the REALM the game's
18
+ * own modules ran in. That is not a convenience: an ingest mount runs the game
19
+ * in the editor's own realm (`game-contract.ts`'s `readGameContract` says so —
20
+ * "the realm every ingest mount runs the game's modules in, where the game and
21
+ * the host share one global"), a vendored bundle's adapter is HOST-realm by the
22
+ * placement rule, and the only handles such a game publishes are the ones on
23
+ * that realm — its runtime's own public registry, or a host-build seam beside
24
+ * it. Handing the realm is therefore handing the game everything it actually
25
+ * exposes; handing anything narrower would make the adapter reach for `window`
26
+ * itself, which is the same read with the provenance hidden.
27
+ *
28
+ * ## The two-state rule, kept
29
+ *
30
+ * Declaring a slot and answering it are separate facts. A declaration with NO
31
+ * `answer` is still listed and refuses BY NAME when read — never omitted,
32
+ * because omitting it would report "the game has no such state" when the truth
33
+ * is "the table declared it and nothing binds it yet". Same for a throwing
34
+ * `answer`: it propagates (exactly as a first-party provider's does), and only
35
+ * `stateAll()` isolates it per key.
36
+ *
37
+ * ## What this deliberately does NOT do
38
+ *
39
+ * - **Validate arguments.** A declared command's `answer` receives the invoked
40
+ * argument list after the game handle; there is no Zod tuple beside a foreign
41
+ * game, so a verb validates itself and throws its own error, surfaced wrapped
42
+ * as `DEBUG_COMMAND_FAILED`. Identical to the contract projection's stance.
43
+ * - **Fabricate an event ring.** Nothing emits behind a declaration, so
44
+ * `events()` is empty — accurate, not a stub.
45
+ */
46
+ import { DebugError } from '../../runtime/debug-registry';
47
+ /**
48
+ * An adapter-declared read is DERIVED by the host-side table from the game's own
49
+ * handles — which is precisely what `assisted` means in the tier vocabulary
50
+ * (`game-contract.ts`: "`observable` is a value the game already computes,
51
+ * `assisted` is one the shim derives"). Reporting `observable` would claim the
52
+ * game itself publishes the value.
53
+ */
54
+ const DECLARED_TIER = 'assisted';
55
+ function unanswered(id, kind) {
56
+ return new DebugError('OBSERVATION_UNANSWERED', `Observation "${id}" (${kind}) is declared by this game's adapter with no \`answer\` — ` +
57
+ 'the slot exists and nothing binds it yet, which is not the same as the game having none.', { id, kind });
58
+ }
59
+ /**
60
+ * Build a {@link DebugAdapter} over an adapter's observation declarations, or
61
+ * `null` when it declares none. `null` is the honest floor — the same one
62
+ * `createContractDebugAdapter` returns — so a game that declared nothing keeps
63
+ * reporting `DEBUG_ADAPTER_UNAVAILABLE` rather than serving an empty adapter
64
+ * that reads as "this game has no state".
65
+ */
66
+ export function createObservationDebugAdapter(declarations, game) {
67
+ const all = declarations ?? [];
68
+ if (all.length === 0)
69
+ return null;
70
+ // Snapshot into id-keyed maps once, exactly as the contract projection does:
71
+ // a later duplicate shadows an earlier one and the listing de-duplicates with
72
+ // it, so a listed id can never resolve differently than it reads.
73
+ const byProvider = new Map(all.filter((d) => d.kind === 'state').map((d) => [d.id, d]));
74
+ const byCommand = new Map(all.filter((d) => d.kind === 'command').map((d) => [d.id, d]));
75
+ return {
76
+ providers: () => [...byProvider.keys()].map((name) => ({ name, tier: DECLARED_TIER })),
77
+ state: (name) => {
78
+ const declaration = byProvider.get(name);
79
+ if (!declaration) {
80
+ throw new DebugError('STATE_PROVIDER_NOT_FOUND', `No state provider named "${name}" is declared by this game's adapter.`, { registered: [...byProvider.keys()] });
81
+ }
82
+ if (!declaration.answer)
83
+ throw unanswered(name, 'state');
84
+ return declaration.answer(game);
85
+ },
86
+ stateAll: () => {
87
+ const out = {};
88
+ for (const [name, declaration] of byProvider) {
89
+ // One throwing declaration must not cost the caller every other
90
+ // reading — the same per-key isolation the first-party registry gives.
91
+ try {
92
+ out[name] = declaration.answer
93
+ ? declaration.answer(game)
94
+ : (() => {
95
+ throw unanswered(name, 'state');
96
+ })();
97
+ }
98
+ catch (err) {
99
+ out[name] = { __error: String(err) };
100
+ }
101
+ }
102
+ return out;
103
+ },
104
+ commands: () => [...byCommand.keys()].map((name) => ({
105
+ name,
106
+ // An ingested game runs entirely in the browser realm the host mounted
107
+ // it in; there is no server leg for its verbs to route to.
108
+ locus: byCommand.get(name)?.locus ?? 'client',
109
+ })),
110
+ invoke: async (name, args) => {
111
+ const declaration = byCommand.get(name);
112
+ if (!declaration) {
113
+ throw new DebugError('DEBUG_COMMAND_NOT_REGISTERED', `No command named "${name}" is declared by this game's adapter.`, { registered: [...byCommand.keys()] });
114
+ }
115
+ if (!declaration.answer)
116
+ throw unanswered(name, 'command');
117
+ try {
118
+ // The game handle first, then the invoked argument list — a declared
119
+ // verb is a function OF the mounted game, which is the whole reason
120
+ // `answer` takes one.
121
+ return await declaration.answer(game, ...args);
122
+ }
123
+ catch (err) {
124
+ throw new DebugError('DEBUG_COMMAND_FAILED', `Command "${name}" threw: ${String(err)}`, {
125
+ name,
126
+ });
127
+ }
128
+ },
129
+ events: () => [],
130
+ };
131
+ }
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Scene capture — the core primitive of unmodified-game ingestion.
3
3
  *
4
- * An external three.js game owns its own `WebGLRenderer`, `Scene`, camera, and
5
- * render loop. To let the editor inspect/edit that live scene WITHOUT touching
6
- * the game's code, we need a handle to the game's `Scene`+camera the moment it
7
- * first renders. The robust way to obtain it is an **accessor trap** on
8
- * `WebGLRenderer.prototype.render`:
4
+ * An external three.js game owns its own renderer, `Scene`, camera, and render
5
+ * loop. To let the editor inspect/edit that live scene WITHOUT touching the
6
+ * game's code, we need a handle to the game's `Scene`+camera the moment it
7
+ * first renders. For WebGL, the robust way to obtain it is an **accessor trap**
8
+ * on `WebGLRenderer.prototype.render`:
9
9
  *
10
10
  * - `WebGLRenderer` assigns `this.render` as an OWN instance property inside
11
11
  * its constructor (not on the prototype), and `THREE.WebGLRenderer` is a
@@ -20,12 +20,16 @@
20
20
  * (`docs/ingest-study-spike/vgai-ingest-adapter.js`), proven against an
21
21
  * unmodified `three.js/examples/games_fps` game.
22
22
  *
23
- * CRITICAL: the trap must be installed on the SAME `three` module instance the
24
- * game uses. In a bundler/dev-server that dedupes `three` (one `node_modules/
25
- * three`), an external ESM game's `import 'three'` resolves to that one instance,
26
- * so passing the host's `THREE` here traps the game's renderer too. A game that
27
- * bundles its own copy of `three` cannot be captured this way (the module-identity
28
- * gatekeeper).
23
+ * DOM-backed addon renderers use ordinary prototype methods instead. The host
24
+ * supplies those exact shared classes through `additionalRendererCtors`, and
25
+ * the same observer captures their `(scene, camera)` pair without inventing a
26
+ * second world model. CSS3DRenderer is the first implementer.
27
+ *
28
+ * CRITICAL: every trap must be installed on the SAME `three` module/addon
29
+ * instance the game uses. In a bundler/dev-server that dedupes `three` (one
30
+ * `node_modules/three`), an external ESM game's imports resolve to those shared
31
+ * classes. A game that bundles its own copy cannot be captured this way (the
32
+ * module-identity gatekeeper).
29
33
  *
30
34
  * Wave 17 adds an OPTIONAL, ADDITIVE composer capture: a game rendering through
31
35
  * its own three.js addon `EffectComposer`
@@ -50,14 +54,35 @@
50
54
  */
51
55
  import type * as THREE from 'three';
52
56
  import { type VisibilityClock, type VisibleCaptureWindow } from './visible-capture-window';
57
+ /** The renderer operations the ingest host may lawfully use after capture.
58
+ * WebGLRenderer supplies every member; DOM-backed Three addon renderers such as
59
+ * CSS3DRenderer deliberately omit the WebGL-only lifecycle operations. */
60
+ export interface CapturedThreeRenderer {
61
+ readonly domElement: HTMLElement;
62
+ readonly info?: THREE.WebGLInfo | undefined;
63
+ render(scene: THREE.Scene, camera: THREE.Camera): unknown;
64
+ setSize(width: number, height: number, updateStyle?: boolean): void;
65
+ setAnimationLoop?(callback: ((time: number) => void) | null): void;
66
+ dispose?(): void;
67
+ getPixelRatio?(): number;
68
+ getContext?(): unknown;
69
+ getRenderTarget?(): unknown;
70
+ }
53
71
  /** A live runtime captured from an external game on its first rendered frame. */
54
72
  export interface CapturedRuntime {
55
73
  scene: THREE.Scene;
56
74
  camera: THREE.Camera;
57
- renderer: THREE.WebGLRenderer;
75
+ renderer: CapturedThreeRenderer;
58
76
  }
59
77
  /** Options for {@link installSceneCapture}. */
60
78
  export interface SceneCaptureOptions {
79
+ /**
80
+ * Additional shared Three addon renderer classes whose prototype `render`
81
+ * method carries the same `(scene, camera)` pair as WebGLRenderer. CSS3DRenderer
82
+ * is the first implementer. The caller must pass the exact class its game
83
+ * imports; a bundled private copy remains deliberately uncapturable.
84
+ */
85
+ additionalRendererCtors?: readonly unknown[];
61
86
  /**
62
87
  * True when `renderer` is one the HOST constructed for its own drawing.
63
88
  *
@@ -206,7 +231,7 @@ export interface SceneCaptureHandle {
206
231
  getDrawCount(): number;
207
232
  /** The game's last (non-null) `setAnimationLoop` callback for a renderer, so the
208
233
  * host can pause (set null) and resume (re-set it) the game's own loop. */
209
- getAnimationLoop(renderer: THREE.WebGLRenderer): ((time: number) => void) | null;
234
+ getAnimationLoop(renderer: CapturedThreeRenderer): ((time: number) => void) | null;
210
235
  /**
211
236
  * Wave 17 (D-C3): resize every captured `EffectComposer` that renders
212
237
  * through the captured renderer to `w`×`h`, matching its pixel ratio to
@@ -1 +1 @@
1
- {"version":3,"file":"scene-capture.d.ts","sourceRoot":"","sources":["../../../src/adapter/ingest/scene-capture.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scene-capture.d.ts","sourceRoot":"","sources":["../../../src/adapter/ingest/scene-capture.ts"],"names":[],"mappings":"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"}
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Scene capture — the core primitive of unmodified-game ingestion.
3
3
  *
4
- * An external three.js game owns its own `WebGLRenderer`, `Scene`, camera, and
5
- * render loop. To let the editor inspect/edit that live scene WITHOUT touching
6
- * the game's code, we need a handle to the game's `Scene`+camera the moment it
7
- * first renders. The robust way to obtain it is an **accessor trap** on
8
- * `WebGLRenderer.prototype.render`:
4
+ * An external three.js game owns its own renderer, `Scene`, camera, and render
5
+ * loop. To let the editor inspect/edit that live scene WITHOUT touching the
6
+ * game's code, we need a handle to the game's `Scene`+camera the moment it
7
+ * first renders. For WebGL, the robust way to obtain it is an **accessor trap**
8
+ * on `WebGLRenderer.prototype.render`:
9
9
  *
10
10
  * - `WebGLRenderer` assigns `this.render` as an OWN instance property inside
11
11
  * its constructor (not on the prototype), and `THREE.WebGLRenderer` is a
@@ -20,12 +20,16 @@
20
20
  * (`docs/ingest-study-spike/vgai-ingest-adapter.js`), proven against an
21
21
  * unmodified `three.js/examples/games_fps` game.
22
22
  *
23
- * CRITICAL: the trap must be installed on the SAME `three` module instance the
24
- * game uses. In a bundler/dev-server that dedupes `three` (one `node_modules/
25
- * three`), an external ESM game's `import 'three'` resolves to that one instance,
26
- * so passing the host's `THREE` here traps the game's renderer too. A game that
27
- * bundles its own copy of `three` cannot be captured this way (the module-identity
28
- * gatekeeper).
23
+ * DOM-backed addon renderers use ordinary prototype methods instead. The host
24
+ * supplies those exact shared classes through `additionalRendererCtors`, and
25
+ * the same observer captures their `(scene, camera)` pair without inventing a
26
+ * second world model. CSS3DRenderer is the first implementer.
27
+ *
28
+ * CRITICAL: every trap must be installed on the SAME `three` module/addon
29
+ * instance the game uses. In a bundler/dev-server that dedupes `three` (one
30
+ * `node_modules/three`), an external ESM game's imports resolve to those shared
31
+ * classes. A game that bundles its own copy cannot be captured this way (the
32
+ * module-identity gatekeeper).
29
33
  *
30
34
  * Wave 17 adds an OPTIONAL, ADDITIVE composer capture: a game rendering through
31
35
  * its own three.js addon `EffectComposer`
@@ -48,7 +52,7 @@
48
52
  * against the `bloom-composer` fixture
49
53
  * (`docs/f13-bloom-composer-proof/record-fixed.mjs`).
50
54
  */
51
- import { startVisibleCaptureWindow, } from './visible-capture-window';
55
+ import { documentVisibilityClock, startVisibleCaptureWindow, } from './visible-capture-window';
52
56
  /**
53
57
  * How many distinct alternates are recorded before the trap stops looking.
54
58
  *
@@ -112,7 +116,27 @@ export function installSceneCapture(threeNamespace, effectComposerCtor, opts) {
112
116
  // the GETTER returns a wrapper that records each non-null callback before forwarding.
113
117
  const REAL_SAL = Symbol('vgai.realSetAnimationLoop');
114
118
  const loopCallbacks = new WeakMap();
119
+ const loopedRenderers = new Set();
120
+ /** Visibility clock of the in-flight `waitForCapture`, if any. */
121
+ let waitingVisibility = null;
115
122
  const priorSAL = Object.getOwnPropertyDescriptor(proto, 'setAnimationLoop');
123
+ function pumpHiddenLoops() {
124
+ if (captured ||
125
+ !waitingVisibility ||
126
+ !(waitingVisibility.suspended?.() ?? waitingVisibility.hidden())) {
127
+ return;
128
+ }
129
+ for (const renderer of loopedRenderers) {
130
+ try {
131
+ loopCallbacks.get(renderer)?.(0);
132
+ }
133
+ catch {
134
+ /* a throwing game frame must not kill the waiter */
135
+ }
136
+ if (captured)
137
+ return;
138
+ }
139
+ }
116
140
  Object.defineProperty(proto, 'setAnimationLoop', {
117
141
  configurable: true,
118
142
  set(fn) {
@@ -121,8 +145,14 @@ export function installSceneCapture(threeNamespace, effectComposerCtor, opts) {
121
145
  get() {
122
146
  const self = this;
123
147
  return function setAnimationLoop(cb) {
124
- if (cb)
148
+ if (cb) {
125
149
  loopCallbacks.set(self, cb);
150
+ loopedRenderers.add(self);
151
+ // Hidden tabs park rAF. Play/eval still need a first frame, so when
152
+ // a waiter is parked we drive the game's own loop once — the same
153
+ // class of tick `waitSimTime` already uses for a hidden document.
154
+ queueMicrotask(pumpHiddenLoops);
155
+ }
126
156
  const real = self[REAL_SAL];
127
157
  return typeof real === 'function'
128
158
  ? real.call(self, cb)
@@ -203,6 +233,21 @@ export function installSceneCapture(threeNamespace, effectComposerCtor, opts) {
203
233
  drawCount,
204
234
  });
205
235
  }
236
+ /** Observe the common `(scene, camera)` render contract once, regardless of
237
+ * whether it came from WebGLRenderer's constructor-assigned method or an
238
+ * addon's ordinary prototype method. */
239
+ function observeRendererRender(self, scene, camera) {
240
+ if (!captured || self === captured.renderer)
241
+ drawCount++;
242
+ if (opts?.isHostRenderer?.(self) === true)
243
+ return;
244
+ if (!scene?.isScene)
245
+ return;
246
+ if (captured)
247
+ recordAlternateWorld(self, scene, camera);
248
+ else
249
+ adoptWorld(self, scene, camera);
250
+ }
206
251
  Object.defineProperty(proto, 'render', {
207
252
  configurable: true,
208
253
  set(fn) {
@@ -221,8 +266,6 @@ export function installSceneCapture(threeNamespace, effectComposerCtor, opts) {
221
266
  // `setAnimationLoop`, so they ARE gateable) and the instrument was
222
267
  // reporting someone else's frames. Before capture every render still
223
268
  // counts: that is how the first game frame is detected at all.
224
- if (!captured || self === captured.renderer)
225
- drawCount++;
226
269
  // Never capture a scene the HOST owns. The trap sits on the shared
227
270
  // `WebGLRenderer.prototype`, so the editor's own viewport renders
228
271
  // arrive here too — and when a game bundles its own MISMATCHED three,
@@ -233,16 +276,39 @@ export function installSceneCapture(threeNamespace, effectComposerCtor, opts) {
233
276
  // Whether that happened at all came down to whether the editor
234
277
  // rendered a frame inside the game's capture window, so the same
235
278
  // session could pass or fail on timing alone.
236
- if (opts?.isHostRenderer?.(self) !== true && scene?.isScene) {
237
- if (captured)
238
- recordAlternateWorld(self, scene, camera);
239
- else
240
- adoptWorld(self, scene, camera);
241
- }
279
+ observeRendererRender(self, scene, camera);
242
280
  return forwardRender(self, args);
243
281
  };
244
282
  },
245
283
  });
284
+ // DOM-backed Three renderers (CSS3DRenderer is the first) use a normal
285
+ // prototype method rather than WebGLRenderer's constructor assignment. Trap
286
+ // only classes explicitly supplied by the host, and restore each byte-for-byte.
287
+ const additionalRendererRestores = [];
288
+ for (const candidate of opts?.additionalRendererCtors ?? []) {
289
+ const ctor = candidate;
290
+ const rendererProto = ctor?.prototype;
291
+ const priorRender = rendererProto?.render;
292
+ if (!rendererProto || typeof priorRender !== 'function' || rendererProto === proto)
293
+ continue;
294
+ rendererProto.render = function (...args) {
295
+ observeRendererRender(this, args[0], args[1]);
296
+ const hooks = renderPassHooks;
297
+ if (hooks === null || !captured || this !== captured.renderer) {
298
+ return priorRender.apply(this, args);
299
+ }
300
+ hooks.before();
301
+ try {
302
+ return priorRender.apply(this, args);
303
+ }
304
+ finally {
305
+ hooks.after();
306
+ }
307
+ };
308
+ additionalRendererRestores.push(() => {
309
+ rendererProto.render = priorRender;
310
+ });
311
+ }
246
312
  // ---- Wave 17 (D-C2): additive composer capture, only when a composer ctor
247
313
  // was passed. `EffectComposer` methods (including `render`) live on the
248
314
  // PROTOTYPE (a plain ES class — the constructor never does `this.render =
@@ -296,7 +362,9 @@ export function installSceneCapture(threeNamespace, effectComposerCtor, opts) {
296
362
  // renderer need not even expose that method for this to stay a no-op.
297
363
  if (!captured || composers.size === 0)
298
364
  return;
299
- const pixelRatio = captured.renderer.getPixelRatio();
365
+ const pixelRatio = captured.renderer.getPixelRatio?.();
366
+ if (pixelRatio === undefined)
367
+ return;
300
368
  for (const composer of composers) {
301
369
  composer.setSize(w, h);
302
370
  composer.setPixelRatio(pixelRatio);
@@ -320,20 +388,27 @@ export function installSceneCapture(threeNamespace, effectComposerCtor, opts) {
320
388
  const i = waiters.indexOf(wrapped);
321
389
  if (i >= 0)
322
390
  waiters.splice(i, 1);
391
+ waitingVisibility = null;
323
392
  opts.onWait?.(null);
324
393
  reject(new Error(`Scene capture timed out after ${timeoutMs}ms of VISIBLE time ` +
325
- `(${Math.round(captureWindow.elapsedHiddenMs())}ms hidden, which is not counted — ` +
326
- 'a hidden tab cannot render) — the game never rendered, ' +
394
+ `(${Math.round(captureWindow.elapsedHiddenMs())}ms browser-suspended, which is ` +
395
+ 'not counted because no frame can be presented) — the game never rendered, ' +
327
396
  'or it bundles its own (un-shared) copy of three.'));
328
397
  },
329
398
  });
330
399
  const wrapped = (rt) => {
331
400
  captureWindow.cancel();
401
+ waitingVisibility = null;
332
402
  opts.onWait?.(null);
333
403
  resolve(rt);
334
404
  };
335
405
  waiters.push(wrapped);
406
+ waitingVisibility = opts.visibility ?? documentVisibilityClock();
336
407
  opts.onWait?.(captureWindow);
408
+ // A waiter that starts already-hidden (the normal `vgai play` path
409
+ // against a backgrounded tab) must not wait for a human to foreground
410
+ // it. Pump any loop the game has already registered.
411
+ queueMicrotask(pumpHiddenLoops);
337
412
  });
338
413
  },
339
414
  uninstall() {
@@ -379,6 +454,8 @@ export function installSceneCapture(threeNamespace, effectComposerCtor, opts) {
379
454
  // prototype method — no per-instance own-property to restore, unlike
380
455
  // the renderer/loop traps above).
381
456
  restoreComposerTrap();
457
+ for (const restore of additionalRendererRestores)
458
+ restore();
382
459
  },
383
460
  };
384
461
  }
@@ -45,6 +45,16 @@ export interface VisibilityClock {
45
45
  now(): number;
46
46
  /** True while the document is hidden (no rAF, so no frame can be captured). */
47
47
  hidden(): boolean;
48
+ /**
49
+ * True while the browser is not presenting frames. This is wider than
50
+ * `hidden()`: WebKit also stops rAF for an unfocused window (and can suspend
51
+ * the page entirely) while `document.hidden` still reads false.
52
+ *
53
+ * Optional for compatibility with injected clocks; absent means `hidden()`.
54
+ */
55
+ suspended?(): boolean;
56
+ /** Why {@link suspended} is true, when the clock can say. */
57
+ suspensionReason?(): 'hidden' | 'unfocused' | 'page-suspended' | null;
48
58
  /** Subscribe to visibility transitions; returns the unsubscribe. */
49
59
  subscribe(onChange: () => void): () => void;
50
60
  }
@@ -91,18 +101,25 @@ export interface VisibleCaptureWindow {
91
101
  readonly budgetMs: number;
92
102
  /** Visible milliseconds spent so far. */
93
103
  elapsedVisibleMs(): number;
94
- /** Milliseconds this window has spent parked while hidden (not counted). */
104
+ /** Milliseconds this window has spent browser-suspended (not counted). */
95
105
  elapsedHiddenMs(): number;
96
- /** Whether the wait is parked right now because the document is hidden. */
106
+ /**
107
+ * Whether the document itself is hidden. Kept distinct from browser
108
+ * suspension so status never calls an unfocused, still-visible window hidden.
109
+ */
97
110
  isHidden(): boolean;
111
+ /** Whether the expiry budget is parked because the browser is not presenting frames. */
112
+ isSuspended(): boolean;
113
+ /** The browser condition parking the budget, when observable. */
114
+ suspensionReason(): 'hidden' | 'unfocused' | 'page-suspended' | null;
98
115
  /** Stop the timer and drop the visibility listener. Idempotent. */
99
116
  cancel(): void;
100
117
  }
101
118
  /**
102
119
  * Start a window that calls `onExpire` after `budgetMs` of VISIBLE time.
103
- * While the document is hidden the timer is disarmed entirely (so a throttled
104
- * background timer cannot fire it late either) and re-armed with the remaining
105
- * budget when the document comes back.
120
+ * While browser frame presentation is suspended the timer is disarmed entirely
121
+ * (so a throttled background timer cannot fire it late either) and re-armed
122
+ * with the remaining budget when frame presentation resumes.
106
123
  */
107
124
  export declare function startVisibleCaptureWindow(opts: {
108
125
  budgetMs: number;
@@ -1 +1 @@
1
- {"version":3,"file":"visible-capture-window.d.ts","sourceRoot":"","sources":["../../../src/adapter/ingest/visible-capture-window.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,GAAG,IAAI,MAAM,CAAC;IACd,+EAA+E;IAC/E,MAAM,IAAI,OAAO,CAAC;IAClB,oEAAoE;IACpE,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;CAC7C;AAED;;yEAEyE;AACzE,wBAAgB,uBAAuB,IAAI,eAAe,CAWzD;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,2CAA2C;IAC3C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,mDAAmD;IACnD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,wCAAwC;IACxC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,mDAAmD;IACnD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,OAAO,GACd,kBAAkB,CAEpB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,kBAAkB,EACzB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,OAAO,GACd,kBAAkB,CAUpB;AAED,kEAAkE;AAClE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/E;AAED;2EAC2E;AAC3E,wBAAgB,eAAe,CAAC,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED,6DAA6D;AAC7D,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjF;AAED,sFAAsF;AACtF,MAAM,WAAW,oBAAoB;IACnC,kDAAkD;IAClD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yCAAyC;IACzC,gBAAgB,IAAI,MAAM,CAAC;IAC3B,4EAA4E;IAC5E,eAAe,IAAI,MAAM,CAAC;IAC1B,2EAA2E;IAC3E,QAAQ,IAAI,OAAO,CAAC;IACpB,mEAAmE;IACnE,MAAM,IAAI,IAAI,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,iDAAiD;IACjD,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;IACnD,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CACxC,GAAG,oBAAoB,CAmDvB"}
1
+ {"version":3,"file":"visible-capture-window.d.ts","sourceRoot":"","sources":["../../../src/adapter/ingest/visible-capture-window.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,GAAG,IAAI,MAAM,CAAC;IACd,+EAA+E;IAC/E,MAAM,IAAI,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,SAAS,CAAC,IAAI,OAAO,CAAC;IACtB,6DAA6D;IAC7D,gBAAgB,CAAC,IAAI,QAAQ,GAAG,WAAW,GAAG,gBAAgB,GAAG,IAAI,CAAC;IACtE,oEAAoE;IACpE,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;CAC7C;AAED;;yEAEyE;AACzE,wBAAgB,uBAAuB,IAAI,eAAe,CA4CzD;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,2CAA2C;IAC3C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,mDAAmD;IACnD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,wCAAwC;IACxC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,mDAAmD;IACnD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,OAAO,GACd,kBAAkB,CAEpB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,kBAAkB,EACzB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,OAAO,GACd,kBAAkB,CAUpB;AAED,kEAAkE;AAClE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/E;AAED;2EAC2E;AAC3E,wBAAgB,eAAe,CAAC,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED,6DAA6D;AAC7D,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjF;AAED,sFAAsF;AACtF,MAAM,WAAW,oBAAoB;IACnC,kDAAkD;IAClD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yCAAyC;IACzC,gBAAgB,IAAI,MAAM,CAAC;IAC3B,0EAA0E;IAC1E,eAAe,IAAI,MAAM,CAAC;IAC1B;;;OAGG;IACH,QAAQ,IAAI,OAAO,CAAC;IACpB,wFAAwF;IACxF,WAAW,IAAI,OAAO,CAAC;IACvB,iEAAiE;IACjE,gBAAgB,IAAI,QAAQ,GAAG,WAAW,GAAG,gBAAgB,GAAG,IAAI,CAAC;IACrE,mEAAmE;IACnE,MAAM,IAAI,IAAI,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,iDAAiD;IACjD,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;IACnD,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CACxC,GAAG,oBAAoB,CAuDvB"}
@@ -39,14 +39,50 @@
39
39
  * honest answer for a headless caller — it has no tab to background. */
40
40
  export function documentVisibilityClock() {
41
41
  const doc = typeof document === 'undefined' ? null : document;
42
+ const win = doc?.defaultView ?? null;
43
+ let pageSuspended = false;
44
+ const reason = () => {
45
+ if (pageSuspended)
46
+ return 'page-suspended';
47
+ if (doc?.hidden === true)
48
+ return 'hidden';
49
+ // WebKit stops rAF when the browser window becomes inactive, including
50
+ // when another app is in front. Page Visibility can still say `visible`
51
+ // in that state. Focus is a platform fact, not a Safari/UA sniff, and in
52
+ // browsers that keep rendering while unfocused this merely parks the
53
+ // expiry clock until either a frame arrives or focus returns.
54
+ if (doc && typeof doc.hasFocus === 'function' && !doc.hasFocus())
55
+ return 'unfocused';
56
+ return null;
57
+ };
42
58
  return {
43
59
  now: () => (typeof performance === 'undefined' ? Date.now() : performance.now()),
44
60
  hidden: () => doc?.hidden === true,
61
+ suspended: () => reason() !== null,
62
+ suspensionReason: reason,
45
63
  subscribe(onChange) {
46
64
  if (!doc)
47
65
  return () => { };
66
+ const onPageHide = () => {
67
+ pageSuspended = true;
68
+ onChange();
69
+ };
70
+ const onPageShow = () => {
71
+ pageSuspended = false;
72
+ onChange();
73
+ };
48
74
  doc.addEventListener('visibilitychange', onChange);
49
- return () => doc.removeEventListener('visibilitychange', onChange);
75
+ win?.addEventListener('blur', onChange);
76
+ win?.addEventListener('focus', onChange);
77
+ win?.addEventListener('pagehide', onPageHide);
78
+ win?.addEventListener('pageshow', onPageShow);
79
+ return () => {
80
+ doc.removeEventListener('visibilitychange', onChange);
81
+ win?.removeEventListener('blur', onChange);
82
+ win?.removeEventListener('focus', onChange);
83
+ win?.removeEventListener('pagehide', onPageHide);
84
+ win?.removeEventListener('pageshow', onPageShow);
85
+ };
50
86
  },
51
87
  };
52
88
  }
@@ -88,15 +124,16 @@ export function visibleRemainingMs(state, now) {
88
124
  }
89
125
  /**
90
126
  * Start a window that calls `onExpire` after `budgetMs` of VISIBLE time.
91
- * While the document is hidden the timer is disarmed entirely (so a throttled
92
- * background timer cannot fire it late either) and re-armed with the remaining
93
- * budget when the document comes back.
127
+ * While browser frame presentation is suspended the timer is disarmed entirely
128
+ * (so a throttled background timer cannot fire it late either) and re-armed
129
+ * with the remaining budget when frame presentation resumes.
94
130
  */
95
131
  export function startVisibleCaptureWindow(opts) {
96
132
  const clock = opts.clock ?? documentVisibilityClock();
97
133
  const setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
98
134
  const clearTimer = opts.clearTimer ?? ((h) => clearTimeout(h));
99
- let state = beginVisibleBudget(opts.budgetMs, clock.now(), clock.hidden());
135
+ const suspended = () => clock.suspended?.() ?? clock.hidden();
136
+ let state = beginVisibleBudget(opts.budgetMs, clock.now(), suspended());
100
137
  let timer = null;
101
138
  let done = false;
102
139
  function disarm() {
@@ -121,7 +158,7 @@ export function startVisibleCaptureWindow(opts) {
121
158
  const unsubscribe = clock.subscribe(() => {
122
159
  if (done)
123
160
  return;
124
- state = applyVisibility(state, clock.now(), clock.hidden());
161
+ state = applyVisibility(state, clock.now(), suspended());
125
162
  arm();
126
163
  });
127
164
  arm();
@@ -129,7 +166,9 @@ export function startVisibleCaptureWindow(opts) {
129
166
  budgetMs: opts.budgetMs,
130
167
  elapsedVisibleMs: () => visibleElapsedMs(state, clock.now()),
131
168
  elapsedHiddenMs: () => hiddenElapsedMs(state, clock.now()),
132
- isHidden: () => state.hidden,
169
+ isHidden: () => clock.hidden(),
170
+ isSuspended: () => state.hidden,
171
+ suspensionReason: () => state.hidden ? (clock.suspensionReason?.() ?? (clock.hidden() ? 'hidden' : null)) : null,
133
172
  cancel() {
134
173
  if (done)
135
174
  return;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The ordinary module surface a native game may expose to its own debugger.
3
+ *
4
+ * A root entry re-exports one `debug` object from the project's command module:
5
+ *
6
+ * export { debug } from './commands';
7
+ *
8
+ * The command module imports no vgai API. Its functions are the application's
9
+ * own functions over its own stores; the native adapter merely projects that
10
+ * existing registry onto the session debug/input doors after the root mounts.
11
+ * Loading through the root entry is load-bearing: the functions close over the
12
+ * exact module graph the mounted application uses, never a separately evaluated
13
+ * copy of its stores.
14
+ */
15
+ import type { Game } from '../runtime/game';
16
+ export declare const NATIVE_INPUT_VALUE_TYPES: readonly ["digital", "scalar", "vector2", "pointerDelta", "pointerPosition"];
17
+ export type NativeInputValueType = (typeof NATIVE_INPUT_VALUE_TYPES)[number];
18
+ export type NativeInputValue = boolean | number | {
19
+ readonly x: number;
20
+ readonly y: number;
21
+ };
22
+ export interface NativeDebugModule {
23
+ readonly commands?: Readonly<Record<string, (...args: unknown[]) => unknown>>;
24
+ readonly state?: Readonly<Record<string, () => unknown>>;
25
+ readonly input?: {
26
+ readonly actions: Readonly<Record<string, NativeInputValueType>>;
27
+ readonly set: (action: string, value: NativeInputValue) => void;
28
+ readonly clear: () => void;
29
+ /** Optional native-scheduler operation. The adapter never synthesizes one. */
30
+ readonly tap?: (action: string) => void;
31
+ /** Optional native-scheduler operation. The adapter never adds a host phase. */
32
+ readonly scheduleAtTick?: (tick: number, action: string, value: NativeInputValue) => void;
33
+ };
34
+ }
35
+ export interface NativeDebugBinding {
36
+ readonly rootId: string;
37
+ readonly debug: NativeDebugModule;
38
+ }
39
+ /** Read the single native `debug` export from an already-loaded root module. */
40
+ export declare function nativeDebugBindingFromEntryModule(rootId: string, entryModule: unknown): NativeDebugBinding | null;
41
+ /** Project native registries onto the existing session debugger after mount. */
42
+ export declare function installNativeDebugBindings(game: Game, bindings: readonly NativeDebugBinding[]): void;
43
+ //# sourceMappingURL=native-debug-module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-debug-module.d.ts","sourceRoot":"","sources":["../../src/adapter/native-debug-module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAE5C,eAAO,MAAM,wBAAwB,8EAM3B,CAAC;AACX,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC7E,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7F,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC;IAC9E,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC;IACzD,QAAQ,CAAC,KAAK,CAAC,EAAE;QACf,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;QACjE,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;QAChE,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC;QAC3B,8EAA8E;QAC9E,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;QACxC,gFAAgF;QAChF,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;KAC3F,CAAC;CACH;AAID,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;CACnC;AA0ED,gFAAgF;AAChF,wBAAgB,iCAAiC,CAC/C,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,OAAO,GACnB,kBAAkB,GAAG,IAAI,CAwB3B;AAkID,gFAAgF;AAChF,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,SAAS,kBAAkB,EAAE,GACtC,IAAI,CAkBN"}