@vgai/engine 0.5.13 → 0.5.14

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 (192) hide show
  1. package/dist/adapter/adapter-module.d.ts +447 -0
  2. package/dist/adapter/adapter-module.d.ts.map +1 -0
  3. package/dist/adapter/adapter-module.js +308 -0
  4. package/dist/adapter/authoring.d.ts +357 -10
  5. package/dist/adapter/authoring.d.ts.map +1 -1
  6. package/dist/adapter/authoring.js +87 -1
  7. package/dist/adapter/constraint.d.ts +9 -3
  8. package/dist/adapter/constraint.d.ts.map +1 -1
  9. package/dist/adapter/finders/finder-result.d.ts +28 -0
  10. package/dist/adapter/finders/finder-result.d.ts.map +1 -0
  11. package/dist/adapter/finders/finder-result.js +10 -0
  12. package/dist/adapter/finders/index.d.ts +38 -0
  13. package/dist/adapter/finders/index.d.ts.map +1 -0
  14. package/dist/adapter/finders/index.js +41 -0
  15. package/dist/adapter/finders/prefabs-from-stories.d.ts +64 -0
  16. package/dist/adapter/finders/prefabs-from-stories.d.ts.map +1 -0
  17. package/dist/adapter/finders/prefabs-from-stories.js +92 -0
  18. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts +67 -0
  19. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -0
  20. package/dist/adapter/finders/scenes-from-entrypoint-selection.js +226 -0
  21. package/dist/adapter/first-party-systems.d.ts.map +1 -1
  22. package/dist/adapter/first-party-systems.js +29 -0
  23. package/dist/adapter/index.d.ts +7 -3
  24. package/dist/adapter/index.d.ts.map +1 -1
  25. package/dist/adapter/index.js +5 -2
  26. package/dist/adapter/ingest/contract-system-adapters.d.ts +24 -1
  27. package/dist/adapter/ingest/contract-system-adapters.d.ts.map +1 -1
  28. package/dist/adapter/ingest/contract-system-adapters.js +100 -34
  29. package/dist/adapter/ingest/game-contract.d.ts +188 -2
  30. package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
  31. package/dist/adapter/ingest/game-contract.js +107 -0
  32. package/dist/adapter/ingest/mount-readiness.d.ts +84 -0
  33. package/dist/adapter/ingest/mount-readiness.d.ts.map +1 -0
  34. package/dist/adapter/ingest/mount-readiness.js +81 -0
  35. package/dist/adapter/ingest/scene-capture.d.ts +91 -2
  36. package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
  37. package/dist/adapter/ingest/scene-capture.js +110 -18
  38. package/dist/adapter/ingest/structural-ids.d.ts +11 -5
  39. package/dist/adapter/ingest/structural-ids.d.ts.map +1 -1
  40. package/dist/adapter/ingest/structural-ids.js +18 -9
  41. package/dist/adapter/ingest/upstream-pin.d.ts +4 -2
  42. package/dist/adapter/ingest/upstream-pin.d.ts.map +1 -1
  43. package/dist/adapter/ingest/upstream-pin.js +4 -2
  44. package/dist/adapter/ingest/visible-capture-window.d.ts +116 -0
  45. package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -0
  46. package/dist/adapter/ingest/visible-capture-window.js +141 -0
  47. package/dist/adapter/loop-gate-report.d.ts +3 -4
  48. package/dist/adapter/loop-gate-report.d.ts.map +1 -1
  49. package/dist/adapter/loop-gate-report.js +3 -4
  50. package/dist/adapter/root-adapter.d.ts +22 -2
  51. package/dist/adapter/root-adapter.d.ts.map +1 -1
  52. package/dist/adapter/setup-three-root-adapter.d.ts.map +1 -1
  53. package/dist/adapter/setup-three-root-adapter.js +15 -0
  54. package/dist/adapter/system-adapter.d.ts +161 -2
  55. package/dist/adapter/system-adapter.d.ts.map +1 -1
  56. package/dist/adapter/system-adapter.js +18 -1
  57. package/dist/animation/camera-ownership.d.ts +4 -2
  58. package/dist/animation/camera-ownership.d.ts.map +1 -1
  59. package/dist/animation/camera-ownership.js +58 -1
  60. package/dist/animation/runtime-inspection.d.ts +70 -0
  61. package/dist/animation/runtime-inspection.d.ts.map +1 -0
  62. package/dist/animation/runtime-inspection.js +25 -0
  63. package/dist/animation/xstate-animation-binding.d.ts +9 -1
  64. package/dist/animation/xstate-animation-binding.d.ts.map +1 -1
  65. package/dist/animation/xstate-animation-binding.js +48 -1
  66. package/dist/asset-formats/material.d.ts +1 -1
  67. package/dist/asset-formats/mesh.d.ts +1 -1
  68. package/dist/asset-formats/particles.d.ts +13 -0
  69. package/dist/asset-formats/particles.d.ts.map +1 -1
  70. package/dist/asset-formats/particles.js +28 -0
  71. package/dist/behavior/xstate-inspection.d.ts +24 -0
  72. package/dist/behavior/xstate-inspection.d.ts.map +1 -0
  73. package/dist/behavior/xstate-inspection.js +25 -0
  74. package/dist/core/game-loop.d.ts +7 -5
  75. package/dist/core/game-loop.d.ts.map +1 -1
  76. package/dist/core/game-loop.js +39 -18
  77. package/dist/core/types.d.ts +11 -12
  78. package/dist/core/types.d.ts.map +1 -1
  79. package/dist/data/data-asset.js +1 -1
  80. package/dist/dev/instruments.d.ts.map +1 -1
  81. package/dist/dev/instruments.js +5 -1
  82. package/dist/dev/render-debug-adapter.d.ts +19 -2
  83. package/dist/dev/render-debug-adapter.d.ts.map +1 -1
  84. package/dist/dev/render-debug-adapter.js +10 -0
  85. package/dist/ecs/user-data.d.ts +10 -2
  86. package/dist/ecs/user-data.d.ts.map +1 -1
  87. package/dist/ecs/user-data.js +6 -2
  88. package/dist/input/binding-identity.d.ts +10 -0
  89. package/dist/input/binding-identity.d.ts.map +1 -0
  90. package/dist/input/binding-identity.js +36 -0
  91. package/dist/input/input-manager.d.ts +18 -185
  92. package/dist/input/input-manager.d.ts.map +1 -1
  93. package/dist/input/input-manager.js +78 -68
  94. package/dist/manifest/load.d.ts +14 -0
  95. package/dist/manifest/load.d.ts.map +1 -1
  96. package/dist/manifest/load.js +6 -1
  97. package/dist/manifest/schema.d.ts +18 -0
  98. package/dist/manifest/schema.d.ts.map +1 -1
  99. package/dist/manifest/schema.js +64 -7
  100. package/dist/pixi/authoring.d.ts +27 -0
  101. package/dist/pixi/authoring.d.ts.map +1 -1
  102. package/dist/pixi/authoring.js +10 -3
  103. package/dist/pixi/index.d.ts +1 -1
  104. package/dist/pixi/index.d.ts.map +1 -1
  105. package/dist/pixi/index.js +1 -1
  106. package/dist/pixi/ingest.d.ts +8 -0
  107. package/dist/pixi/ingest.d.ts.map +1 -1
  108. package/dist/pixi/ingest.js +10 -1
  109. package/dist/pixi/render-pass-bracket.d.ts +56 -0
  110. package/dist/pixi/render-pass-bracket.d.ts.map +1 -0
  111. package/dist/pixi/render-pass-bracket.js +98 -0
  112. package/dist/pixi/scene-capture.d.ts +32 -1
  113. package/dist/pixi/scene-capture.d.ts.map +1 -1
  114. package/dist/pixi/scene-capture.js +21 -9
  115. package/dist/pixi/system-adapters.d.ts +29 -0
  116. package/dist/pixi/system-adapters.d.ts.map +1 -1
  117. package/dist/pixi/system-adapters.js +27 -0
  118. package/dist/render/particles-factory.d.ts +16 -2
  119. package/dist/render/particles-factory.d.ts.map +1 -1
  120. package/dist/render/particles-factory.js +88 -5
  121. package/dist/render/soft-particle-depth.d.ts +149 -0
  122. package/dist/render/soft-particle-depth.d.ts.map +1 -0
  123. package/dist/render/soft-particle-depth.js +305 -0
  124. package/dist/render/voxel-surface.d.ts +117 -0
  125. package/dist/render/voxel-surface.d.ts.map +1 -0
  126. package/dist/render/voxel-surface.js +263 -0
  127. package/dist/runtime/create-runtime.js +7 -0
  128. package/dist/runtime/debug-bridge.d.ts +15 -15
  129. package/dist/runtime/debug-bridge.d.ts.map +1 -1
  130. package/dist/runtime/debug-bridge.js +27 -26
  131. package/dist/runtime/game.d.ts.map +1 -1
  132. package/dist/runtime/game.js +1 -2
  133. package/dist/setup/setup-particles.d.ts.map +1 -1
  134. package/dist/setup/setup-particles.js +9 -0
  135. package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
  136. package/dist/world3d-react/r3f-adapter.js +42 -3
  137. package/dist/world3d-react/rapier-physics-bridge.d.ts.map +1 -1
  138. package/dist/world3d-react/rapier-physics-bridge.js +201 -11
  139. package/dist-config/config.js.map +1 -1
  140. package/package.json +4 -2
  141. package/schemas/engine-capabilities.json +12 -12
  142. package/schemas/vgai-project.schema.json +41 -2
  143. package/src/adapter/adapter-module.ts +607 -0
  144. package/src/adapter/authoring.ts +440 -10
  145. package/src/adapter/constraint.ts +10 -3
  146. package/src/adapter/finders/finder-result.ts +29 -0
  147. package/src/adapter/finders/index.ts +62 -0
  148. package/src/adapter/finders/prefabs-from-stories.ts +142 -0
  149. package/src/adapter/finders/scenes-from-entrypoint-selection.ts +304 -0
  150. package/src/adapter/first-party-systems.ts +30 -0
  151. package/src/adapter/index.ts +32 -1
  152. package/src/adapter/ingest/contract-system-adapters.ts +116 -32
  153. package/src/adapter/ingest/game-contract.ts +265 -2
  154. package/src/adapter/ingest/mount-readiness.ts +140 -0
  155. package/src/adapter/ingest/scene-capture.ts +199 -22
  156. package/src/adapter/ingest/structural-ids.ts +21 -9
  157. package/src/adapter/ingest/upstream-pin.ts +4 -2
  158. package/src/adapter/ingest/visible-capture-window.ts +213 -0
  159. package/src/adapter/loop-gate-report.ts +3 -4
  160. package/src/adapter/root-adapter.ts +22 -2
  161. package/src/adapter/setup-three-root-adapter.ts +16 -0
  162. package/src/adapter/system-adapter.ts +188 -2
  163. package/src/animation/camera-ownership.ts +68 -3
  164. package/src/animation/runtime-inspection.ts +104 -0
  165. package/src/animation/xstate-animation-binding.ts +53 -1
  166. package/src/asset-formats/particles.ts +31 -0
  167. package/src/behavior/xstate-inspection.ts +51 -0
  168. package/src/core/game-loop.ts +39 -17
  169. package/src/core/types.ts +11 -12
  170. package/src/data/data-asset.ts +1 -1
  171. package/src/dev/instruments.ts +5 -1
  172. package/src/dev/render-debug-adapter.ts +22 -2
  173. package/src/ecs/user-data.ts +10 -2
  174. package/src/input/binding-identity.ts +37 -0
  175. package/src/input/input-manager.ts +93 -68
  176. package/src/manifest/load.ts +23 -2
  177. package/src/manifest/schema.ts +76 -7
  178. package/src/pixi/authoring.ts +37 -5
  179. package/src/pixi/index.ts +2 -0
  180. package/src/pixi/ingest.ts +15 -2
  181. package/src/pixi/render-pass-bracket.ts +117 -0
  182. package/src/pixi/scene-capture.ts +63 -13
  183. package/src/pixi/system-adapters.ts +41 -0
  184. package/src/render/particles-factory.ts +89 -6
  185. package/src/render/soft-particle-depth.ts +336 -0
  186. package/src/render/voxel-surface.ts +358 -0
  187. package/src/runtime/create-runtime.ts +7 -0
  188. package/src/runtime/debug-bridge.ts +32 -31
  189. package/src/runtime/game.ts +1 -2
  190. package/src/setup/setup-particles.ts +9 -0
  191. package/src/world3d-react/r3f-adapter.tsx +48 -2
  192. package/src/world3d-react/rapier-physics-bridge.tsx +249 -11
@@ -0,0 +1,116 @@
1
+ /**
2
+ * The capture window is a budget of VISIBLE time, not of wall-clock time.
3
+ *
4
+ * WHY THIS MODULE EXISTS. An ingest mount waits for the game to render its
5
+ * first frame and fails by name when that never happens
6
+ * (`scene-capture.ts`'s `waitForCapture`). That wait used to be a plain
7
+ * `setTimeout`, i.e. a wall-clock deadline started AT BOOT — and a game cannot
8
+ * render while its tab is hidden, because the browser parks `requestAnimation
9
+ * Frame` for a backgrounded document. The two facts together make one
10
+ * deterministic failure: a tab that opens in the BACKGROUND (the normal human
11
+ * path — `vgai edit` auto-opens a tab that routinely lands behind the current
12
+ * window) burns its whole capture window unable to draw, the deadline fires,
13
+ * the mount dies terminally, and foregrounding the tab later changes nothing.
14
+ * Agents, whose tabs happen to be visible, never saw it.
15
+ *
16
+ * So the clock only runs while the document is VISIBLE. Hidden time is not
17
+ * spent, and while a tab is hidden the wait PARKS rather than expiring: the
18
+ * capture trap stays installed, the game's modules stay live, and the first
19
+ * frame the tab draws after the human brings it forward is trapped exactly as
20
+ * it would have been at boot. That is the retry — no second mount, no
21
+ * re-running `load()` (which would re-construct module-level state: the
22
+ * ARCHITECTURE-CORE "LOADING CONSTRUCTS, once" contract), and no forcing of a
23
+ * frame: the game's own loop resumes on its own when the browser un-parks it.
24
+ *
25
+ * There is deliberately no wall-clock cap on the parked state. An unspent
26
+ * budget is not a failure — a human returning to the tab is what spends it —
27
+ * and a cap would be exactly the boot-time clock this module exists to
28
+ * remove. The parked wait is reported instead of being silent: see
29
+ * `CaptureWaitObserver` in `scene-capture.ts` and `vgai status`'s
30
+ * `ingestCaptureWait`.
31
+ *
32
+ * The state machine is a pure function of (banked segments, now, hidden) so
33
+ * the whole accrual rule is testable with no browser and no timers
34
+ * (`packages/engine/test/visible-capture-window.test.ts`); the runtime half
35
+ * below is only the timer arm/disarm around it.
36
+ */
37
+ /**
38
+ * Everything the budget reads about the outside world. Injected so the pure
39
+ * core stays pure and the runtime half is drivable by a fake in tests — and,
40
+ * as a side effect, so this module never hard-depends on `document` existing
41
+ * (unit runners, SSR).
42
+ */
43
+ export interface VisibilityClock {
44
+ /** Milliseconds, monotonic-ish; `performance.now()`/`Date.now()` both fit. */
45
+ now(): number;
46
+ /** True while the document is hidden (no rAF, so no frame can be captured). */
47
+ hidden(): boolean;
48
+ /** Subscribe to visibility transitions; returns the unsubscribe. */
49
+ subscribe(onChange: () => void): () => void;
50
+ }
51
+ /** The real one: `document.visibilityState` + `visibilitychange`. Falls back
52
+ * to permanently-visible where there is no `document` at all, which is the
53
+ * honest answer for a headless caller — it has no tab to background. */
54
+ export declare function documentVisibilityClock(): VisibilityClock;
55
+ /**
56
+ * The banked halves of the wait plus the segment currently running. Immutable:
57
+ * every transition returns a new value, so a caller can hold one and compare.
58
+ */
59
+ export interface VisibleBudgetState {
60
+ /** The window, in VISIBLE milliseconds. */
61
+ readonly budgetMs: number;
62
+ /** Visible time banked from completed segments. */
63
+ readonly visibleMs: number;
64
+ /** Hidden time banked from completed segments (reported, never spent). */
65
+ readonly hiddenMs: number;
66
+ /** When the current segment started. */
67
+ readonly since: number;
68
+ /** Whether the current segment is a hidden one. */
69
+ readonly hidden: boolean;
70
+ }
71
+ /** Open the window at `now`, in whichever visibility the document is in. */
72
+ export declare function beginVisibleBudget(budgetMs: number, now: number, hidden: boolean): VisibleBudgetState;
73
+ /**
74
+ * Apply the document's current visibility at `now`: bank the segment that just
75
+ * ended into its own bucket and start the next one. A call that does not change
76
+ * visibility is a no-op *by value* (same accrual, same segment start), so a
77
+ * duplicate `visibilitychange` can never bank a zero-length segment twice or
78
+ * restart the clock.
79
+ */
80
+ export declare function applyVisibility(state: VisibleBudgetState, now: number, hidden: boolean): VisibleBudgetState;
81
+ /** Visible time spent so far, including the segment in flight. */
82
+ export declare function visibleElapsedMs(state: VisibleBudgetState, now: number): number;
83
+ /** Hidden time so far, including the segment in flight. Never spent — it is
84
+ * reported so a failure message can say what the window did NOT count. */
85
+ export declare function hiddenElapsedMs(state: VisibleBudgetState, now: number): number;
86
+ /** Visible time left in the window; `0` once it is spent. */
87
+ export declare function visibleRemainingMs(state: VisibleBudgetState, now: number): number;
88
+ /** A live view of one running window, for the status wire and the failure message. */
89
+ export interface VisibleCaptureWindow {
90
+ /** The window's size, in visible milliseconds. */
91
+ readonly budgetMs: number;
92
+ /** Visible milliseconds spent so far. */
93
+ elapsedVisibleMs(): number;
94
+ /** Milliseconds this window has spent parked while hidden (not counted). */
95
+ elapsedHiddenMs(): number;
96
+ /** Whether the wait is parked right now because the document is hidden. */
97
+ isHidden(): boolean;
98
+ /** Stop the timer and drop the visibility listener. Idempotent. */
99
+ cancel(): void;
100
+ }
101
+ /**
102
+ * 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.
106
+ */
107
+ export declare function startVisibleCaptureWindow(opts: {
108
+ budgetMs: number;
109
+ onExpire: () => void;
110
+ /** Defaults to the document's own visibility. */
111
+ clock?: VisibilityClock;
112
+ /** Defaults to `setTimeout`/`clearTimeout`. */
113
+ setTimer?: (fn: () => void, ms: number) => unknown;
114
+ clearTimer?: (handle: unknown) => void;
115
+ }): VisibleCaptureWindow;
116
+ //# sourceMappingURL=visible-capture-window.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * The capture window is a budget of VISIBLE time, not of wall-clock time.
3
+ *
4
+ * WHY THIS MODULE EXISTS. An ingest mount waits for the game to render its
5
+ * first frame and fails by name when that never happens
6
+ * (`scene-capture.ts`'s `waitForCapture`). That wait used to be a plain
7
+ * `setTimeout`, i.e. a wall-clock deadline started AT BOOT — and a game cannot
8
+ * render while its tab is hidden, because the browser parks `requestAnimation
9
+ * Frame` for a backgrounded document. The two facts together make one
10
+ * deterministic failure: a tab that opens in the BACKGROUND (the normal human
11
+ * path — `vgai edit` auto-opens a tab that routinely lands behind the current
12
+ * window) burns its whole capture window unable to draw, the deadline fires,
13
+ * the mount dies terminally, and foregrounding the tab later changes nothing.
14
+ * Agents, whose tabs happen to be visible, never saw it.
15
+ *
16
+ * So the clock only runs while the document is VISIBLE. Hidden time is not
17
+ * spent, and while a tab is hidden the wait PARKS rather than expiring: the
18
+ * capture trap stays installed, the game's modules stay live, and the first
19
+ * frame the tab draws after the human brings it forward is trapped exactly as
20
+ * it would have been at boot. That is the retry — no second mount, no
21
+ * re-running `load()` (which would re-construct module-level state: the
22
+ * ARCHITECTURE-CORE "LOADING CONSTRUCTS, once" contract), and no forcing of a
23
+ * frame: the game's own loop resumes on its own when the browser un-parks it.
24
+ *
25
+ * There is deliberately no wall-clock cap on the parked state. An unspent
26
+ * budget is not a failure — a human returning to the tab is what spends it —
27
+ * and a cap would be exactly the boot-time clock this module exists to
28
+ * remove. The parked wait is reported instead of being silent: see
29
+ * `CaptureWaitObserver` in `scene-capture.ts` and `vgai status`'s
30
+ * `ingestCaptureWait`.
31
+ *
32
+ * The state machine is a pure function of (banked segments, now, hidden) so
33
+ * the whole accrual rule is testable with no browser and no timers
34
+ * (`packages/engine/test/visible-capture-window.test.ts`); the runtime half
35
+ * below is only the timer arm/disarm around it.
36
+ */
37
+ /** The real one: `document.visibilityState` + `visibilitychange`. Falls back
38
+ * to permanently-visible where there is no `document` at all, which is the
39
+ * honest answer for a headless caller — it has no tab to background. */
40
+ export function documentVisibilityClock() {
41
+ const doc = typeof document === 'undefined' ? null : document;
42
+ return {
43
+ now: () => (typeof performance === 'undefined' ? Date.now() : performance.now()),
44
+ hidden: () => doc?.hidden === true,
45
+ subscribe(onChange) {
46
+ if (!doc)
47
+ return () => { };
48
+ doc.addEventListener('visibilitychange', onChange);
49
+ return () => doc.removeEventListener('visibilitychange', onChange);
50
+ },
51
+ };
52
+ }
53
+ /** Open the window at `now`, in whichever visibility the document is in. */
54
+ export function beginVisibleBudget(budgetMs, now, hidden) {
55
+ return { budgetMs, visibleMs: 0, hiddenMs: 0, since: now, hidden };
56
+ }
57
+ /**
58
+ * Apply the document's current visibility at `now`: bank the segment that just
59
+ * ended into its own bucket and start the next one. A call that does not change
60
+ * visibility is a no-op *by value* (same accrual, same segment start), so a
61
+ * duplicate `visibilitychange` can never bank a zero-length segment twice or
62
+ * restart the clock.
63
+ */
64
+ export function applyVisibility(state, now, hidden) {
65
+ if (hidden === state.hidden)
66
+ return state;
67
+ const elapsed = Math.max(0, now - state.since);
68
+ return {
69
+ budgetMs: state.budgetMs,
70
+ visibleMs: state.hidden ? state.visibleMs : state.visibleMs + elapsed,
71
+ hiddenMs: state.hidden ? state.hiddenMs + elapsed : state.hiddenMs,
72
+ since: now,
73
+ hidden,
74
+ };
75
+ }
76
+ /** Visible time spent so far, including the segment in flight. */
77
+ export function visibleElapsedMs(state, now) {
78
+ return state.visibleMs + (state.hidden ? 0 : Math.max(0, now - state.since));
79
+ }
80
+ /** Hidden time so far, including the segment in flight. Never spent — it is
81
+ * reported so a failure message can say what the window did NOT count. */
82
+ export function hiddenElapsedMs(state, now) {
83
+ return state.hiddenMs + (state.hidden ? Math.max(0, now - state.since) : 0);
84
+ }
85
+ /** Visible time left in the window; `0` once it is spent. */
86
+ export function visibleRemainingMs(state, now) {
87
+ return Math.max(0, state.budgetMs - visibleElapsedMs(state, now));
88
+ }
89
+ /**
90
+ * 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.
94
+ */
95
+ export function startVisibleCaptureWindow(opts) {
96
+ const clock = opts.clock ?? documentVisibilityClock();
97
+ const setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
98
+ const clearTimer = opts.clearTimer ?? ((h) => clearTimeout(h));
99
+ let state = beginVisibleBudget(opts.budgetMs, clock.now(), clock.hidden());
100
+ let timer = null;
101
+ let done = false;
102
+ function disarm() {
103
+ if (timer !== null) {
104
+ clearTimer(timer);
105
+ timer = null;
106
+ }
107
+ }
108
+ function arm() {
109
+ disarm();
110
+ if (done || state.hidden)
111
+ return;
112
+ timer = setTimer(() => {
113
+ timer = null;
114
+ if (done)
115
+ return;
116
+ done = true;
117
+ unsubscribe();
118
+ opts.onExpire();
119
+ }, visibleRemainingMs(state, clock.now()));
120
+ }
121
+ const unsubscribe = clock.subscribe(() => {
122
+ if (done)
123
+ return;
124
+ state = applyVisibility(state, clock.now(), clock.hidden());
125
+ arm();
126
+ });
127
+ arm();
128
+ return {
129
+ budgetMs: opts.budgetMs,
130
+ elapsedVisibleMs: () => visibleElapsedMs(state, clock.now()),
131
+ elapsedHiddenMs: () => hiddenElapsedMs(state, clock.now()),
132
+ isHidden: () => state.hidden,
133
+ cancel() {
134
+ if (done)
135
+ return;
136
+ done = true;
137
+ disarm();
138
+ unsubscribe();
139
+ },
140
+ };
141
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Capability-gate shortfall reporting (D10, T7.6) — the honesty half of the
3
- * play-control contract. §(d) draws the `loop` axis (`gated | self-driven`) as
4
- * independent of capability tier: a `self-driven` world (an ingested game
3
+ * play-control contract. §(d) draws the `loop` axis (`gated | self-driven`) on
4
+ * its own: a `self-driven` world (an ingested game
5
5
  * driving its own rAF/ticker) pauses for real ONLY if its adapter implements an
6
6
  * explicit loop-gate capability (`MountedRootBase.setPaused`/`step`) —
7
7
  * otherwise it "honestly reports so" rather than a silent no-op (the
@@ -10,8 +10,7 @@
10
10
  * has the identical shape: absent or unable to silence a world's audio on pause
11
11
  * must report, not pretend.
12
12
  *
13
- * Pure compute + message-formatting, same split as `achieved-tier.ts`
14
- * (`packages/editor/src/achieved-tier.ts`) — callers own the actual
13
+ * Pure compute + message-formatting callers own the actual
15
14
  * `console.warn`/`editorConsole.warn` call; this module never logs.
16
15
  */
17
16
  /** Greppable prefix for a loop-gate shortfall (pause requested, world's loop
@@ -1 +1 @@
1
- {"version":3,"file":"loop-gate-report.d.ts","sourceRoot":"","sources":["../../src/adapter/loop-gate-report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;oCACoC;AACpC,eAAO,MAAM,gBAAgB,cAAc,CAAC;AAE5C;6CAC6C;AAC7C,eAAO,MAAM,iBAAiB,eAAe,CAAC;AAE9C,gFAAgF;AAChF,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAcD;iCACiC;AACjC,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAE1E;AAED;iDACiD;AACjD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAE3E"}
1
+ {"version":3,"file":"loop-gate-report.d.ts","sourceRoot":"","sources":["../../src/adapter/loop-gate-report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;oCACoC;AACpC,eAAO,MAAM,gBAAgB,cAAc,CAAC;AAE5C;6CAC6C;AAC7C,eAAO,MAAM,iBAAiB,eAAe,CAAC;AAE9C,gFAAgF;AAChF,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAcD;iCACiC;AACjC,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAE1E;AAED;iDACiD;AACjD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAE3E"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Capability-gate shortfall reporting (D10, T7.6) — the honesty half of the
3
- * play-control contract. §(d) draws the `loop` axis (`gated | self-driven`) as
4
- * independent of capability tier: a `self-driven` world (an ingested game
3
+ * play-control contract. §(d) draws the `loop` axis (`gated | self-driven`) on
4
+ * its own: a `self-driven` world (an ingested game
5
5
  * driving its own rAF/ticker) pauses for real ONLY if its adapter implements an
6
6
  * explicit loop-gate capability (`MountedRootBase.setPaused`/`step`) —
7
7
  * otherwise it "honestly reports so" rather than a silent no-op (the
@@ -10,8 +10,7 @@
10
10
  * has the identical shape: absent or unable to silence a world's audio on pause
11
11
  * must report, not pretend.
12
12
  *
13
- * Pure compute + message-formatting, same split as `achieved-tier.ts`
14
- * (`packages/editor/src/achieved-tier.ts`) — callers own the actual
13
+ * Pure compute + message-formatting callers own the actual
15
14
  * `console.warn`/`editorConsole.warn` call; this module never logs.
16
15
  */
17
16
  /** Greppable prefix for a loop-gate shortfall (pause requested, world's loop
@@ -24,6 +24,7 @@
24
24
  */
25
25
  import type { Container } from 'pixi.js';
26
26
  import type * as THREE from 'three';
27
+ import type { WorldRendererConfig } from '../world3d-react/renderer-config';
27
28
  import type { AdapterSurface } from './adapter-surface';
28
29
  import type { AuthoringAdapter } from './authoring';
29
30
  import type { HostContextFor } from './host-context';
@@ -75,8 +76,8 @@ export interface MountedRootBase {
75
76
  * not a promise: implement `setPaused`/`step` only where the game exposes
76
77
  * a sanctioned pause/step mechanism. Gating a raw-rAF loop from outside
77
78
  * was demonstrated and REJECTED (D5 — it halts the loop rather than
78
- * pausing it); such games are the "composited, unsynchronized" tier:
79
- * `setPaused` absent, host degrades loudly (T7.6 owns the tier surface).
79
+ * pausing it). Where `setPaused` is absent the host reports the
80
+ * shortfall loudly, naming the mechanism that would close it (T7.6).
80
81
  */
81
82
  readonly drivesOwnLoop: boolean;
82
83
  update?(dt: number): void;
@@ -105,6 +106,25 @@ export interface MountedThreeRoot extends MountedRootBase {
105
106
  /** The live scene + camera the editor inspects/renders for authoring. */
106
107
  readonly scene: THREE.Scene;
107
108
  readonly camera: THREE.Camera;
109
+ /**
110
+ * The colour pipeline this world was authored for, REPORTED rather than
111
+ * applied — the adapter has already applied it to the renderer its own host
112
+ * handed it (`world3d-react/renderer-config.ts`).
113
+ *
114
+ * It is here because the host that MOUNTS a world is not always the host
115
+ * that DRAWS it. The editor's design session mounts against a
116
+ * non-rasterizing renderer on purpose and then draws the mounted scene with
117
+ * the editor viewport's own `WebGLRenderer`, so the world's declaration
118
+ * lands on a surface with no pixels while the surface with pixels never
119
+ * hears it. Reporting the declaration is what lets that host apply the same
120
+ * config, through the same `applyWorldRendererConfig`, to the renderer that
121
+ * actually produces the frame.
122
+ *
123
+ * Absent means the world declared nothing and the drawing host should leave
124
+ * its own configuration alone — the same "absent = don't touch" rule the
125
+ * config's own fields follow.
126
+ */
127
+ readonly rendererConfig?: WorldRendererConfig | undefined;
108
128
  }
109
129
  /** A live, mounted canvas world (T7.3) — the pixi analog of
110
130
  * {@link MountedThreeRoot}. `stage` is the pixi world container the host
@@ -1 +1 @@
1
- {"version":3,"file":"root-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/root-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;;OAMG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAC5C;;;;;;OAMG;IACH,QAAQ,IAAI,OAAO,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAEhC,MAAM,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,IAAI,CAAC,IAAI,IAAI,CAAC;IAEd,MAAM,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,OAAO,IAAI,IAAI,CAAC;IAEhB,gFAAgF;IAChF,QAAQ,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC;IAClC;;;;iBAIa;IACb,QAAQ,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAC;CACtC;AAED;;;;mBAImB;AACnB,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC;CAC/B;AAED;;uEAEuE;AACvE,MAAM,WAAW,eAAgB,SAAQ,eAAe;IACtD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;CAC3B;AAED;;;wEAGwE;AACxE,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;CACjC;AAED;yEACyE;AACzE,MAAM,MAAM,WAAW,GAAG,gBAAgB,GAAG,eAAe,GAAG,gBAAgB,CAAC;AAEhF;;gFAEgF;AAChF,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,cAAc,IAAI,CAAC,SAAS,OAAO,GACpE,gBAAgB,GAChB,CAAC,SAAS,QAAQ,GAChB,eAAe,GACf,CAAC,SAAS,KAAK,GACb,gBAAgB,GAChB,KAAK,CAAC;AAEd;;;;;;;;;;;;;;;;;GAiBG;AACH;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,SAAS,cAAc,GAAG,OAAO;IAC7D,kDAAkD;IAClD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB;kBACc;IACd,KAAK,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;CAC5D;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,cAAc,IAAI;IACxD,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;CAClC,CAAC;AAEF;;;;oEAIoE;AACpE,MAAM,MAAM,cAAc,GACtB,iBAAiB,CAAC,OAAO,CAAC,GAC1B,iBAAiB,CAAC,QAAQ,CAAC,GAC3B,iBAAiB,CAAC,KAAK,CAAC,CAAC"}
1
+ {"version":3,"file":"root-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/root-adapter.ts"],"names":[],"mappings":"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"}
@@ -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;CAu0BpE"}
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;AAoCrD,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;CAs1BpE"}
@@ -40,6 +40,7 @@ import { createCollisionSystem } from '../physics/collision-system';
40
40
  import { createPhysicsRegistry } from '../physics/physics-registry';
41
41
  import { createTransformWriter } from '../physics/transform-writer';
42
42
  import { updateSceneLODs } from '../render/lod';
43
+ import { createSoftParticleDepthPass } from '../render/soft-particle-depth';
43
44
  import { disposeSparkRendererWhenIdle, SPARK_DISCOVERY_INTERVAL_MS, sceneHasGaussianSplat, shouldDiscoverGaussianSplat, } from '../render/spark-renderer-lifecycle';
44
45
  import { ViewportShadingRenderer } from '../render/viewport-shading';
45
46
  import { createDebugRegistry, getDebugRegistry, } from '../runtime/debug-registry';
@@ -125,6 +126,10 @@ export class SetupThreeRootAdapter {
125
126
  const audio = headless ? headlessAudio() : setupAudio(camera);
126
127
  // --- Particles (three.quarks BatchedRenderer) ---
127
128
  const particles = setupParticles(scene);
129
+ // Scene depth for any soft-particle system registered against this world.
130
+ // Inert (not one traversal, not one draw) until `registerParticleSystem`
131
+ // arms it — see `../render/soft-particle-depth.ts`.
132
+ const softParticleDepth = createSoftParticleDepthPass();
128
133
  // --- Debug draw / assets ---
129
134
  const debugDraw = createDebugDraw(scene);
130
135
  const assets = host.assets;
@@ -218,6 +223,13 @@ export class SetupThreeRootAdapter {
218
223
  // the assembled capture to a pending captureFrame() and restores the
219
224
  // patched context — in the SAME finally as gpuTimer.end(). Spark is
220
225
  // added before this so its draws fall inside the captured frame.
226
+ // Soft-particle scene depth goes in FRONT of the frame's own draw:
227
+ // the fade samples the geometry behind the particle, which cannot be
228
+ // the depth attachment the particle is drawn into. Deliberately
229
+ // OUTSIDE the capture and profiler brackets below — it is a prepass,
230
+ // not part of the frame's presentation, and a captured frame should
231
+ // show the draws the user sees.
232
+ softParticleDepth.render(renderer, scene, camera);
221
233
  renderDebugWiring?.beforeRender();
222
234
  // Issue #1504: CPU render submission, bracketed as its OWN profiler
223
235
  // phase nested inside the frame's enclosing `render` phase. This is
@@ -720,6 +732,9 @@ export class SetupThreeRootAdapter {
720
732
  renderVitals?.dispose();
721
733
  renderVitals = null;
722
734
  });
735
+ // This mount created the depth pass, so this mount ends it — the render
736
+ // target it holds is drawing-buffer sized and nothing else can reach it.
737
+ safeStep('softParticleDepth.dispose', () => softParticleDepth.dispose());
723
738
  // Release analyser taps BEFORE the audio teardown below. A consumer
724
739
  // (the editor's meter poll) should dispose its own handle, but Stop must
725
740
  // never leak taps regardless.
@@ -13,7 +13,58 @@
13
13
  import type * as THREE from 'three';
14
14
  import type { RenderMemorySnapshot } from '../dev/render-memory';
15
15
  import type { FrameCapture } from '../dev/webgl-frame-capture';
16
+ import type { PhysicsAdapter2D } from '../pixi/system-adapters';
16
17
  import type { Transform, TransformOwner } from './transform';
18
+ export type PhysicsColliderShape = {
19
+ readonly type: 'cuboid';
20
+ readonly halfExtents: readonly [number, number, number];
21
+ } | {
22
+ readonly type: 'ball';
23
+ readonly radius: number;
24
+ } | {
25
+ readonly type: 'capsule';
26
+ readonly halfHeight: number;
27
+ readonly radius: number;
28
+ };
29
+ /** Plain-data projection of one native 3D physics collider. The owning
30
+ * physics implementation keeps the actual collider; editor authoring only
31
+ * receives the geometry needed to inspect and draw it. */
32
+ export interface PhysicsColliderSnapshot {
33
+ readonly id: string;
34
+ readonly shape: PhysicsColliderShape;
35
+ readonly position: readonly [number, number, number];
36
+ readonly rotation: readonly [number, number, number, number];
37
+ /** Absolute world scale used when the native shape was created. Source
38
+ * dimensions divide by this to recover @react-three/rapier's local args. */
39
+ readonly scale: readonly [number, number, number];
40
+ readonly sensor: boolean;
41
+ }
42
+ export type PhysicsJointType = 'fixed' | 'spherical' | 'revolute' | 'prismatic' | 'rope' | 'spring' | 'generic';
43
+ /** Plain-data projection of one native 3D impulse joint. Anchors are exposed
44
+ * in both native body-local space (for persistence) and world space (for
45
+ * viewport instruments); the actual joint remains owned by the physics lib. */
46
+ export interface PhysicsJointSnapshot {
47
+ readonly id: string;
48
+ readonly type: PhysicsJointType;
49
+ readonly body1: string;
50
+ readonly body2: string;
51
+ readonly anchor1: readonly [number, number, number];
52
+ readonly anchor2: readonly [number, number, number];
53
+ readonly worldAnchor1: readonly [number, number, number];
54
+ readonly worldAnchor2: readonly [number, number, number];
55
+ readonly body1Position: readonly [number, number, number];
56
+ readonly body2Position: readonly [number, number, number];
57
+ readonly body1Rotation: readonly [number, number, number, number];
58
+ readonly body2Rotation: readonly [number, number, number, number];
59
+ /** The free axis in body-1 local/world space for revolute/prismatic joints. */
60
+ readonly axis?: readonly [number, number, number];
61
+ readonly worldAxis?: readonly [number, number, number];
62
+ readonly limits?: {
63
+ readonly min: number;
64
+ readonly max: number;
65
+ };
66
+ readonly contactsEnabled: boolean;
67
+ }
17
68
  /**
18
69
  * Physics coordination so the editor can stably edit an object a simulation
19
70
  * would otherwise overwrite every frame: `freeze → apply → unfreeze`.
@@ -22,11 +73,36 @@ import type { Transform, TransformOwner } from './transform';
22
73
  * Object3D keying made this seam structurally unreachable for any non-three
23
74
  * world: a Pixi or React world could never expose a physics inspector, because
24
75
  * the seam spoke a vocabulary it has no values in — which is why the Pixi
25
- * surface grew a parallel `PhysicsAdapter2D` (`pixi/system-adapters.ts`)
76
+ * surface grew a parallel {@link PhysicsAdapter2D} (`pixi/system-adapters.ts`)
26
77
  * instead of implementing this. Substrate-specific resolution (id → the native
27
78
  * object) is the IMPLEMENTER's job, done once at its own boundary.
79
+ *
80
+ * ## `keyedBy`, and why the physics slot's union is TAGGED
81
+ *
82
+ * `SystemAdapters['physics']` holds EITHER shape ({@link PhysicsCarrier}),
83
+ * because a carrier addresses the thing being edited in its surface's own
84
+ * vocabulary and the canvas lane's is the display object itself. But the two
85
+ * shapes carry the SAME four member names and are otherwise indistinguishable
86
+ * at runtime, while the registry that holds them
87
+ * (`editor/src/authoring/active-systems.ts`) is GAME-scoped and surface-blind —
88
+ * one object every editor panel reads. An untagged union would therefore be one
89
+ * nothing could narrow, and every three-lane consumer
90
+ * (`getActivePhysics()?.freeze(nodeId)`, `play-mode.ts`'s `physics.commit(id,
91
+ * transform)`) would have to reach it through a cast asserting a fact nobody
92
+ * checked.
93
+ *
94
+ * So `keyedBy` is a real discriminant, and it is REQUIRED on
95
+ * {@link PhysicsAdapter2D} while OPTIONAL here on purpose: node-id keying is
96
+ * this seam's original and — until the canvas lane — only vocabulary, so every
97
+ * existing implementer stays valid untouched, and the shape that needs telling
98
+ * apart is the one obliged to say so. {@link nodeKeyedPhysics} /
99
+ * {@link displayKeyedPhysics} are the two narrowings; nothing else should test
100
+ * the tag by hand.
28
101
  */
29
102
  export interface PhysicsAdapter {
103
+ /** Optional tag; see the interface comment. `'node-id'` is the only value,
104
+ * and omitting it means the same thing. */
105
+ readonly keyedBy?: 'node-id';
30
106
  /** Returns `'physics'` when a body drives this node, else another owner. */
31
107
  ownerOf(nodeId: string): TransformOwner;
32
108
  /** Pause the body driving `nodeId` (kinematic / sleep / detach) for editing. */
@@ -51,6 +127,17 @@ export interface PhysicsAdapter {
51
127
  * geometry).
52
128
  */
53
129
  contactPoints?(): Float32Array;
130
+ /** Optional native collider inspection for one authored node. Implementers
131
+ * return only shapes they can describe exactly; absence means the editor
132
+ * offers no collider component UI. */
133
+ colliders?(nodeId: string): readonly PhysicsColliderSnapshot[];
134
+ /** Optional gesture preview against the implementation's real collider.
135
+ * Source persistence remains the AuthoringAdapter's responsibility. */
136
+ previewCollider?(colliderId: string, shape: PhysicsColliderShape): void;
137
+ /** Optional native impulse-joint inspection for joints attached to one node. */
138
+ joints?(nodeId: string): readonly PhysicsJointSnapshot[];
139
+ /** Optional gesture preview against the implementation's real local anchor. */
140
+ previewJointAnchor?(jointId: string, endpoint: 0 | 1, anchor: readonly [number, number, number]): void;
54
141
  }
55
142
  export interface NetPeer {
56
143
  id: string;
@@ -347,10 +434,67 @@ export interface AudioAdapter {
347
434
  * environment cannot meter (headless world with no real AudioContext) —
348
435
  * the UI says so rather than showing frozen zeros. */
349
436
  acquireMeters?(): AudioMeterHandle | null;
437
+ /** Optional: tap the real mixed output for a gameplay recording. The
438
+ * returned stream is the implementation's native browser audio stream; the
439
+ * caller must dispose the tap when recording ends. Absence means video can
440
+ * still be recorded, honestly without an audio track. */
441
+ acquireRecordingStream?(): AudioRecordingHandle | null;
350
442
  /** Optional: the audio event ring (bounded, drops oldest). `sinceSeq`
351
443
  * filters to `seq > sinceSeq` — the consumer's fence. */
352
444
  audioEvents?(sinceSeq?: number): AudioDebugEvent[];
353
445
  }
446
+ /** One temporary tap of an audio adapter's mixed output. */
447
+ export interface AudioRecordingHandle {
448
+ readonly stream: MediaStream;
449
+ dispose(): void;
450
+ }
451
+ /** One native camera known to a runtime camera controller.
452
+ *
453
+ * `id` is the controller's own stable key (a shot name, virtual-camera id,
454
+ * etc.). `nativeId` is OPTIONAL identity from the underlying scene object —
455
+ * Three.js implementations normally use `Object3D.uuid`; another adapter may
456
+ * use its renderer's equivalent. The editor uses it only to correlate an
457
+ * already-selected native camera with this read-only runtime projection. */
458
+ export interface CameraRuntimeCamera {
459
+ id: string;
460
+ label: string;
461
+ nativeId?: string;
462
+ /** Optional because ownership/cut controllers do not necessarily arbitrate
463
+ * by priority. A camera brain that does may expose the real value it uses. */
464
+ priority?: number;
465
+ enabled?: boolean;
466
+ }
467
+ /** A transition the controller is evaluating now. Plain data only: no
468
+ * renderer camera, tween, graph node, or editor-authored state crosses the
469
+ * seam. */
470
+ export interface CameraRuntimeTransition {
471
+ fromCameraId: string | null;
472
+ toCameraId: string;
473
+ durationSeconds: number;
474
+ elapsedSeconds: number;
475
+ progress: number;
476
+ }
477
+ /** Read-only runtime state for a selected native camera's Inspector section.
478
+ * Runtime follow/aim/collision stays project behavior; this snapshot merely
479
+ * reports what that behavior already decided. */
480
+ export interface CameraRuntimeSnapshot {
481
+ cameras: readonly CameraRuntimeCamera[];
482
+ activeCameraId: string | null;
483
+ /** The behavior/cinematic currently allowed to drive the rendered camera. */
484
+ activeControllerId: string | null;
485
+ ownershipDepth: number;
486
+ transition: CameraRuntimeTransition | null;
487
+ }
488
+ /** Camera-controller INTROSPECTION — not a camera graph or controller API.
489
+ *
490
+ * Games continue to author ordinary native cameras and drive them with their
491
+ * own TS/TSX. A controller registers this adapter only when it can truthfully
492
+ * expose its active camera, priority and blend state. Absence hides the
493
+ * runtime UI; the editor never infers a brain from scene structure. */
494
+ export interface CameraAdapter {
495
+ snapshot(): CameraRuntimeSnapshot;
496
+ subscribe(listener: () => void): Unsubscribe;
497
+ }
354
498
  /** One tick-stamped debug event (`ctx.debug.emit`, spec §3.3): `tick`/`simT`
355
499
  * are the engine's own counters AT EMISSION, so events and state reads
356
500
  * correlate frame-exactly across every door (in-page bridge, relay, panels).
@@ -438,15 +582,30 @@ export interface RenderDebugAdapter {
438
582
  captureFrame(): Promise<FrameCapture>;
439
583
  memorySnapshot?(): RenderMemorySnapshot;
440
584
  }
585
+ /**
586
+ * A physics carrier in EITHER surface's vocabulary — node-id keyed for a three
587
+ * world, display-object keyed for a canvas one. Tagged by `keyedBy`; see
588
+ * {@link PhysicsAdapter}'s comment for why, and use the two narrowings below
589
+ * rather than reading the tag directly.
590
+ */
591
+ export type PhysicsCarrier = PhysicsAdapter | PhysicsAdapter2D;
592
+ /** The carrier a NODE-ID-keyed consumer may call, or `null` — including when
593
+ * the slot holds a display-keyed carrier, which such a consumer must not
594
+ * call and cannot correctly address. */
595
+ export declare function nodeKeyedPhysics(carrier: PhysicsCarrier | null | undefined): PhysicsAdapter | null;
596
+ /** The carrier a DISPLAY-keyed consumer may call, or `null`. Mirror of
597
+ * {@link nodeKeyedPhysics}. */
598
+ export declare function displayKeyedPhysics(carrier: PhysicsCarrier | null | undefined): PhysicsAdapter2D | null;
441
599
  /**
442
600
  * The set of optional subsystem providers a mounted game may expose. Absence of
443
601
  * a provider means "capability not supported" — the editor degrades gracefully.
444
602
  */
445
603
  export interface SystemAdapters {
446
- physics?: PhysicsAdapter;
604
+ physics?: PhysicsCarrier;
447
605
  networking?: NetworkingAdapter;
448
606
  navigation?: NavigationAdapter;
449
607
  audio?: AudioAdapter;
608
+ camera?: CameraAdapter;
450
609
  debug?: DebugAdapter;
451
610
  renderDebug?: RenderDebugAdapter;
452
611
  }