@volter/editor-game 0.5.65

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 (302) hide show
  1. package/LICENSE +661 -0
  2. package/NOTICE +23 -0
  3. package/contributions/asset-budget-asset.menu.ts +26 -0
  4. package/contributions/asset-budget.action.ts +18 -0
  5. package/contributions/asset-budget.document.tsx +21 -0
  6. package/contributions/asset-budget.menu.ts +22 -0
  7. package/contributions/audio-unlock.service.ts +17 -0
  8. package/contributions/audio.utility.tsx +15 -0
  9. package/contributions/autoplay.service.ts +48 -0
  10. package/contributions/bridge.command.ts +172 -0
  11. package/contributions/build-profiles.document.tsx +21 -0
  12. package/contributions/build-progress.status.tsx +45 -0
  13. package/contributions/build.action.ts +26 -0
  14. package/contributions/build.command.ts +27 -0
  15. package/contributions/build.header.tsx +37 -0
  16. package/contributions/build.menu.ts +31 -0
  17. package/contributions/build.service.ts +31 -0
  18. package/contributions/connection.status.tsx +43 -0
  19. package/contributions/coverage.service.ts +118 -0
  20. package/contributions/edit-mode-audio.service.ts +23 -0
  21. package/contributions/edit-mode-networking.service.ts +30 -0
  22. package/contributions/game-document.service.ts +25 -0
  23. package/contributions/game-eval.command.ts +210 -0
  24. package/contributions/game.layout.ts +19 -0
  25. package/contributions/gameplay.command.ts +255 -0
  26. package/contributions/generation.service.ts +96 -0
  27. package/contributions/generations.status.tsx +53 -0
  28. package/contributions/ingest.service.ts +80 -0
  29. package/contributions/instances.command.ts +75 -0
  30. package/contributions/navmesh.menu.ts +39 -0
  31. package/contributions/navmesh.service.ts +19 -0
  32. package/contributions/network.utility.tsx +17 -0
  33. package/contributions/play.command.ts +355 -0
  34. package/contributions/profiler.action.ts +16 -0
  35. package/contributions/profiler.menu.ts +22 -0
  36. package/contributions/profiler.utility.tsx +17 -0
  37. package/contributions/react/component-board.service.ts +21 -0
  38. package/contributions/react/design-time-mount.service.ts +60 -0
  39. package/contributions/react/pasteboard.action.ts +41 -0
  40. package/contributions/react/react-inspector.service.ts +85 -0
  41. package/contributions/react/story-documents.service.ts +44 -0
  42. package/contributions/scene-document.service.ts +32 -0
  43. package/contributions/state-watch.action.ts +17 -0
  44. package/contributions/state-watch.menu.ts +23 -0
  45. package/contributions/state-watch.utility.tsx +20 -0
  46. package/contributions/team-playtest.service.ts +124 -0
  47. package/contributions/three/camera-runtime.inspector.tsx +34 -0
  48. package/contributions/three/component-board.service.ts +24 -0
  49. package/contributions/three/component-verbs.command.ts +110 -0
  50. package/contributions/three/component-verbs.service.ts +92 -0
  51. package/contributions/three/constraints.inspector.tsx +34 -0
  52. package/contributions/three/model-asset-sections.service.ts +26 -0
  53. package/contributions/three/reflection-probe-capture.inspector.tsx +32 -0
  54. package/contributions/three/story-documents.service.ts +31 -0
  55. package/contributions/three/three-authoring.service.ts +66 -0
  56. package/contributions/transport.header.tsx +19 -0
  57. package/contributions/xstate-behavior.action.ts +42 -0
  58. package/contributions/xstate-behavior.document.tsx +73 -0
  59. package/contributions/xstate-behavior.inspector.tsx +28 -0
  60. package/contributions/xstate-behavior.menu.ts +23 -0
  61. package/package.json +144 -0
  62. package/src/asset-budget/AssetBudgetPanel.tsx +1172 -0
  63. package/src/asset-budget/asset-budget-model.ts +799 -0
  64. package/src/asset-budget/basis-encoder.ts +165 -0
  65. package/src/asset-budget/gltf-io.ts +154 -0
  66. package/src/asset-budget/gltf-optimize.ts +384 -0
  67. package/src/asset-budget/image-dims.ts +78 -0
  68. package/src/asset-budget/optimize-apply.ts +221 -0
  69. package/src/audio/AudioDebuggerPanel.tsx +457 -0
  70. package/src/audio/audio-debugger-model.ts +87 -0
  71. package/src/bridge/call.ts +60 -0
  72. package/src/bridge/dispatch.ts +459 -0
  73. package/src/bridge/live-frames.ts +25 -0
  74. package/src/bridge/screenshot.ts +316 -0
  75. package/src/build/BuildProfilesPanel.tsx +476 -0
  76. package/src/build/build-session.ts +247 -0
  77. package/src/build/format-bytes.ts +14 -0
  78. package/src/command-results.ts +36 -0
  79. package/src/coverage/live-authoring-surface.ts +30 -0
  80. package/src/coverage/live-project-verbs.ts +162 -0
  81. package/src/coverage/native-system-coverage.ts +143 -0
  82. package/src/coverage/root-coverage.ts +79 -0
  83. package/src/coverage/session-coverage.ts +193 -0
  84. package/src/design-system-stories/ApplicationChrome.stories.tsx +100 -0
  85. package/src/design-system-stories/InspectorNarrowBodies.stories.tsx +406 -0
  86. package/src/edit-mode/edit-mode-audio.ts +56 -0
  87. package/src/edit-mode/edit-mode-networking.ts +112 -0
  88. package/src/game-document/DevicePresetPicker.tsx +84 -0
  89. package/src/game-document/GameCaptureFrameButton.tsx +45 -0
  90. package/src/game-document/GameDocument.tsx +215 -0
  91. package/src/game-document/GamePanel.tsx +662 -0
  92. package/src/game-document/InstanceInspectorPicker.tsx +183 -0
  93. package/src/game-document/crowd-debug.ts +183 -0
  94. package/src/game-document/device-preview.ts +336 -0
  95. package/src/game-document/game-view-store.ts +107 -0
  96. package/src/game-document/physics-debug.ts +187 -0
  97. package/src/generation/GenerationActivity.tsx +421 -0
  98. package/src/generation/GenerationGallery.css +231 -0
  99. package/src/generation/generation-documents.tsx +338 -0
  100. package/src/generation/generation-jobs.ts +128 -0
  101. package/src/generation/generation-presentation.ts +257 -0
  102. package/src/host/adapter-reach.ts +445 -0
  103. package/src/host/adapter-runtime-bindings.ts +303 -0
  104. package/src/host/after-paint.ts +112 -0
  105. package/src/host/api/configurations.ts +66 -0
  106. package/src/host/authoring/babylon-authoring-adapter.ts +703 -0
  107. package/src/host/authoring/canvas-runtime-recognition.ts +50 -0
  108. package/src/host/authoring/contract-hierarchy-authoring.ts +144 -0
  109. package/src/host/authoring/contract-scenes-stories.ts +204 -0
  110. package/src/host/authoring/creation-site-related.ts +55 -0
  111. package/src/host/authoring/ephemeral-persistence.ts +29 -0
  112. package/src/host/authoring/gesture-persist.ts +84 -0
  113. package/src/host/authoring/ingest-data-writer.ts +232 -0
  114. package/src/host/authoring/ingest-source-persistence.ts +826 -0
  115. package/src/host/authoring/mount-isolated-pixi-screen.ts +250 -0
  116. package/src/host/authoring/mounted-authoring.ts +41 -0
  117. package/src/host/authoring/owned-pixi-ticker-listeners.ts +96 -0
  118. package/src/host/authoring/phaser-live-authoring-adapter.ts +265 -0
  119. package/src/host/authoring/pixi-authoring-adapter.ts +1554 -0
  120. package/src/host/authoring/pixi-creation-site-write-target.ts +60 -0
  121. package/src/host/authoring/pixi-isolation-assets.ts +25 -0
  122. package/src/host/authoring/pixi-live-write-target.ts +979 -0
  123. package/src/host/authoring/pixi-source-identity.ts +141 -0
  124. package/src/host/authoring/pixi-still-presentation.ts +71 -0
  125. package/src/host/authoring/pixi-structure-history.ts +237 -0
  126. package/src/host/authoring/pixi-transform-channels.ts +205 -0
  127. package/src/host/authoring/selection-remount-handoff.ts +23 -0
  128. package/src/host/authoring/source-persistence-backend.ts +373 -0
  129. package/src/host/authoring/source-refresh-revisions.ts +81 -0
  130. package/src/host/authoring/struct-write-pipe.ts +143 -0
  131. package/src/host/auto-frame-window.ts +89 -0
  132. package/src/host/binding-resolver.ts +393 -0
  133. package/src/host/browser-transpile.ts +631 -0
  134. package/src/host/canvas-entry-runtime.ts +95 -0
  135. package/src/host/components/CameraAuthoringOverlay.tsx +216 -0
  136. package/src/host/components/HeaderTelemetry.tsx +341 -0
  137. package/src/host/components/PixiIsolationSceneContent.tsx +280 -0
  138. package/src/host/components/ResolutionPicker.tsx +89 -0
  139. package/src/host/components/ThreeIsolationSceneContent.tsx +180 -0
  140. package/src/host/components/frame-debugger-model.ts +579 -0
  141. package/src/host/components/header-telemetry-model.ts +74 -0
  142. package/src/host/components/scene-document.tsx +447 -0
  143. package/src/host/components/utility-view-state.ts +87 -0
  144. package/src/host/components/world-root-stage-binding.tsx +133 -0
  145. package/src/host/components/world-root-stage.ts +1046 -0
  146. package/src/host/coverage/authoring-read-probe.ts +583 -0
  147. package/src/host/coverage/capability-coverage.ts +1587 -0
  148. package/src/host/coverage/coverage-accounting.ts +261 -0
  149. package/src/host/coverage/game-contract-seam-evidence.ts +82 -0
  150. package/src/host/coverage/project-verb-coverage.ts +148 -0
  151. package/src/host/coverage/system-adapter-coverage.ts +373 -0
  152. package/src/host/design-system-stories/StoryLayout.tsx +104 -0
  153. package/src/host/design-system-stories/fixtures/authoring.ts +247 -0
  154. package/src/host/design-system-stories/fixtures/editor-runtime.tsx +115 -0
  155. package/src/host/document-preview-three.ts +110 -0
  156. package/src/host/entry-adjudication.ts +89 -0
  157. package/src/host/game-location-guard.ts +150 -0
  158. package/src/host/game-module-access.ts +196 -0
  159. package/src/host/game-realm-page.ts +358 -0
  160. package/src/host/game-realm-reclaim.ts +55 -0
  161. package/src/host/game-realm-storage.ts +104 -0
  162. package/src/host/gameplay-export.ts +288 -0
  163. package/src/host/gameplay-recording.ts +717 -0
  164. package/src/host/gated-globals.ts +1511 -0
  165. package/src/host/history/json-history-resource.ts +254 -0
  166. package/src/host/ingest/registry.ts +261 -0
  167. package/src/host/instance-extract-actions.ts +120 -0
  168. package/src/host/instance-fork-actions.ts +120 -0
  169. package/src/host/play-control-hook.ts +26 -0
  170. package/src/host/playwright-shim.ts +479 -0
  171. package/src/host/projection/dom.ts +295 -0
  172. package/src/host/projection/pixi.ts +288 -0
  173. package/src/host/r3f-entry-runtime.ts +77 -0
  174. package/src/host/react-mount-runtime.ts +162 -0
  175. package/src/host/realm-services.ts +148 -0
  176. package/src/host/recording-preview.ts +106 -0
  177. package/src/host/roots/module-root.ts +203 -0
  178. package/src/host/roots/react-root.ts +181 -0
  179. package/src/host/same-realm-loop-gate.ts +544 -0
  180. package/src/host/scene-view-drawability.ts +69 -0
  181. package/src/host/sdk/tools.ts +31 -0
  182. package/src/host/served-bundle-runtime-modules.ts +331 -0
  183. package/src/host/server-log-bridge.ts +60 -0
  184. package/src/host/staged-projects.ts +24 -0
  185. package/src/host/stories/mounted-story-viewport-source.ts +64 -0
  186. package/src/host/stories/story-arg-descriptors.ts +50 -0
  187. package/src/host/stories/story-media-presence.ts +152 -0
  188. package/src/host/surface-content.ts +87 -0
  189. package/src/host/take-named-export.ts +23 -0
  190. package/src/host/three-ingest-runtime.ts +76 -0
  191. package/src/host/types-fastnoise-lite.d.ts +7 -0
  192. package/src/host/types-mikktspace.d.ts +20 -0
  193. package/src/host/types-troika-three-text.d.ts +7 -0
  194. package/src/host/use-active-performance-source.ts +53 -0
  195. package/src/host/viewport-pose-memory.ts +48 -0
  196. package/src/host/viewport-root-presentation.ts +40 -0
  197. package/src/ingest/active-ingest.ts +251 -0
  198. package/src/ingest/active-scene-navigation.ts +43 -0
  199. package/src/ingest/authoring/ingest-dom-surface-authoring.ts +191 -0
  200. package/src/ingest/authoring/ingest-root-adapter.ts +897 -0
  201. package/src/ingest/capture-wait-report.ts +122 -0
  202. package/src/ingest/deferred-ingest-play.ts +216 -0
  203. package/src/ingest/deferred-ingest-session.ts +23 -0
  204. package/src/ingest/discovery-public-ingest.ts +242 -0
  205. package/src/ingest/dom-stub-mark.ts +7 -0
  206. package/src/ingest/entry-load.ts +56 -0
  207. package/src/ingest/game-contract-realm.ts +34 -0
  208. package/src/ingest/game-pointer-lock.ts +89 -0
  209. package/src/ingest/held-scene-repaint.ts +73 -0
  210. package/src/ingest/host-surface-box.ts +174 -0
  211. package/src/ingest/ingest-boot-viewport.ts +54 -0
  212. package/src/ingest/ingest-canvas-scene-document.tsx +177 -0
  213. package/src/ingest/ingest-canvas-scene.ts +50 -0
  214. package/src/ingest/ingest-evidence-hook.ts +131 -0
  215. package/src/ingest/ingest-frame-snapshot.ts +183 -0
  216. package/src/ingest/ingest-play-commands.ts +134 -0
  217. package/src/ingest/ingest-play-control.ts +299 -0
  218. package/src/ingest/ingest-render-debug.ts +320 -0
  219. package/src/ingest/ingest-siblings.ts +487 -0
  220. package/src/ingest/ingest-status.ts +62 -0
  221. package/src/ingest/live-ingest-facet.ts +46 -0
  222. package/src/ingest/module-mode.ts +229 -0
  223. package/src/ingest/mount-canvas-ingest-root.ts +899 -0
  224. package/src/ingest/mount-coverage.ts +296 -0
  225. package/src/ingest/mount-dom-ingest-root.ts +282 -0
  226. package/src/ingest/mount-ingest-root.ts +744 -0
  227. package/src/ingest/mount-three-ingest-root.ts +366 -0
  228. package/src/ingest/resolve-canvas.ts +47 -0
  229. package/src/ingest/resolve-three.ts +123 -0
  230. package/src/ingest/served-bundle.ts +109 -0
  231. package/src/ingest/served-html-boot.ts +292 -0
  232. package/src/ingest/surface-canvas.ts +84 -0
  233. package/src/ingest/surface-dom.ts +84 -0
  234. package/src/ingest/surface-three.ts +128 -0
  235. package/src/ingest/types.ts +76 -0
  236. package/src/ingest/unmount-ingest-root.ts +224 -0
  237. package/src/navmesh/navmesh-actions.ts +27 -0
  238. package/src/navmesh/navmesh-handler.ts +237 -0
  239. package/src/navmesh/navmesh-workflow-store.ts +78 -0
  240. package/src/network/NetworkInspectorPanel.tsx +644 -0
  241. package/src/network/network-inspector-model.ts +225 -0
  242. package/src/play/play-boot-stall.ts +118 -0
  243. package/src/play/play-log-events.ts +26 -0
  244. package/src/play/play-mode.ts +2553 -0
  245. package/src/play/play-recording.ts +335 -0
  246. package/src/play/react-play-live-authoring.ts +165 -0
  247. package/src/play-bar/PlayBar.tsx +488 -0
  248. package/src/play-bar/PlayerCountPicker.tsx +100 -0
  249. package/src/profiler/FrameDebuggerPanel.tsx +458 -0
  250. package/src/profiler/PerformancePanel.tsx +1068 -0
  251. package/src/profiler/ProfilerPanel.tsx +53 -0
  252. package/src/profiler/frame-debugger-store.ts +97 -0
  253. package/src/profiler/main-thread-busy.ts +90 -0
  254. package/src/react/design-time-react-mount.ts +782 -0
  255. package/src/react/dom-authoring-adapter.ts +764 -0
  256. package/src/react/pasteboard-materialize.ts +154 -0
  257. package/src/react/react-inspector-section.tsx +2496 -0
  258. package/src/react/react-world-authoring-adapter.ts +3795 -0
  259. package/src/react/story-documents/story-args-section.ts +19 -0
  260. package/src/react/story-documents/story-documents.tsx +1380 -0
  261. package/src/react/story-paint-bounds.ts +94 -0
  262. package/src/react/ui-board-document.tsx +101 -0
  263. package/src/react/ui-board-title.ts +9 -0
  264. package/src/react/ui-component-board.ts +72 -0
  265. package/src/services/audio-pose-guard.ts +81 -0
  266. package/src/services/game-audio-unlock.ts +48 -0
  267. package/src/state-watch/StateWatchPanel.tsx +535 -0
  268. package/src/three/authoring/camera-runtime-inspector-section.tsx +135 -0
  269. package/src/three/authoring/constraint-inspector-section.tsx +189 -0
  270. package/src/three/authoring/design-time-renderer.ts +189 -0
  271. package/src/three/authoring/model-asset-inspector-section.css +41 -0
  272. package/src/three/authoring/model-asset-inspector-section.tsx +869 -0
  273. package/src/three/authoring/oid-source-persistence.ts +557 -0
  274. package/src/three/authoring/r3f-design-session.ts +1174 -0
  275. package/src/three/authoring/r3f-source-authoring-adapter.ts +5949 -0
  276. package/src/three/authoring/reflection-probe-inspector-section.tsx +76 -0
  277. package/src/three/authoring/spatial-audio-handles.ts +215 -0
  278. package/src/three/authoring/spatial-collider-handles.ts +229 -0
  279. package/src/three/authoring/spatial-joint-handles.ts +114 -0
  280. package/src/three/authoring/spatial-light-handles.ts +178 -0
  281. package/src/three/authoring/spatial-lod-handles.ts +93 -0
  282. package/src/three/authoring/spatial-particle-handles.ts +368 -0
  283. package/src/three/authoring/three-authoring-adapter.ts +1953 -0
  284. package/src/three/authoring/three-scene-identity.ts +19 -0
  285. package/src/three/authoring/three-spatial-handles.ts +161 -0
  286. package/src/three/authoring/typed-three-inspector.ts +528 -0
  287. package/src/three/component-verbs/extract-menu.ts +71 -0
  288. package/src/three/component-verbs/fork-menu.ts +78 -0
  289. package/src/three/component-verbs/internals-menu.ts +91 -0
  290. package/src/three/story-documents/three-story-documents.tsx +607 -0
  291. package/src/three/three-board/ThreeBoardDocument.tsx +888 -0
  292. package/src/three/three-board/board-framing.ts +412 -0
  293. package/src/three/three-board/board-layout.ts +401 -0
  294. package/src/three/three-board/board-scene.ts +901 -0
  295. package/src/three/three-board/three-component-board.ts +62 -0
  296. package/src/xstate/XStateBehaviorSection.tsx +130 -0
  297. package/src/xstate/XStateMachineInspector.tsx +566 -0
  298. package/src/xstate/character-animation-machine.fixture.ts +74 -0
  299. package/src/xstate/live-behaviors.ts +106 -0
  300. package/src/xstate/use-live-actor-state.ts +44 -0
  301. package/src/xstate/xstate-graph.ts +235 -0
  302. package/src/xstate/xstate-layout.ts +76 -0
@@ -0,0 +1,459 @@
1
+ /**
2
+ * THE SESSION-WIRE DISPATCH — `bridge-call`'s method table, moved out of the
3
+ * editor's `command-listener.ts` with the verb it serves (WORK.md §The
4
+ * workbench).
5
+ *
6
+ * The game skew's own host modules — play mode, the active-systems registry,
7
+ * the live-seam evidence recorders, the ingest contract — are reached through
8
+ * the `@editor/*` alias the editor's Vite serves to every contribution, and
9
+ * become this package's own imports when they move. Nothing here is host API.
10
+ */
11
+
12
+ import {
13
+ getActiveSystems,
14
+ InstanceResolutionError,
15
+ systemsForInstance,
16
+ } from '@volter/editor-core/authoring/active-systems';
17
+ import { collectPlayRunPageErrors } from '@volter/editor-core/command-listener';
18
+ import { gameContractEpoch } from '../host/coverage/game-contract-seam-evidence';
19
+ import { recordLiveSeamEvidence } from '@volter/editor-core/coverage/live-seam-evidence';
20
+ import { systemAdapterEpoch } from '@volter/editor-core/coverage/system-seam-evidence';
21
+ import {
22
+ HOLD_STARVED_NO_DRIVER_REASON,
23
+ waitForHoldBudget,
24
+ } from '@volter/game-runtime/runtime/debug-bridge';
25
+ import type { SystemAdapters } from '@volter/editor-project/adapter';
26
+ import { activeIngestContract } from '../ingest/ingest-play-control';
27
+ import { getPlayRuntimeAccess } from '../play/play-mode';
28
+
29
+ /** Is there ANY live debug plane to answer from?
30
+ *
31
+ * Not the same question as `isPlayModeActive()`: the edit-time R3F design
32
+ * session publishes the edit-mounted world's own registry through
33
+ * `setActiveSystems` while play is stopped (ARCHITECTURE-CORE §Editor chrome
34
+ * — "the edit-time design session PUBLISHES its debug plane"), so a stopped
35
+ * session with a mounted world has a real, honest plane to read: the game's
36
+ * declared providers/commands, values as the still world reports them.
37
+ * An INGESTED game reaches the same answer by a different route and is the
38
+ * second reason this predicate is provenance-free: its mount IS the running
39
+ * game, so it never creates a play session at all, and what it publishes
40
+ * through `setActiveSystems` is the debug adapter projected from its own
41
+ * declared contract (`ingest-root-adapter.ts` →
42
+ * `@volter/game-runtime/adapter/ingest/contract-debug-adapter`). The question asked here is
43
+ * only ever "has a live mount published a plane", never who mounted it.
44
+ * `notPlayingResult()` stays the answer for the genuinely-no-world case. */
45
+ export const hasLiveDebugPlane = (): boolean => getActiveSystems().debug != null;
46
+
47
+ /** Duck-typed structured error (see `structuredErrorResult` above) — a plain
48
+ * `Error` carrying a machine-readable `code` (and optional `data`), the same
49
+ * shape `DebugError`/`InputActionError` already throw. Used by
50
+ * `dispatchBridgeMethod` below for its OWN precondition failures (no debug
51
+ * adapter installed, no InputManager wired) so they survive
52
+ * `structuredErrorResult` exactly like an engine-thrown error would. */
53
+ export function bridgeError(code: string, message: string, data?: Record<string, unknown>): Error {
54
+ const err = new Error(message) as Error & { code: string; data?: unknown };
55
+ err.code = code;
56
+ if (data) err.data = data;
57
+ return err;
58
+ }
59
+
60
+ function returnedNames(value: unknown): ReadonlySet<string> {
61
+ if (!Array.isArray(value)) return new Set();
62
+ return new Set(
63
+ value
64
+ .filter((entry): entry is { name: string } =>
65
+ Boolean(entry && typeof entry === 'object' && typeof entry.name === 'string'),
66
+ )
67
+ .map((entry) => entry.name),
68
+ );
69
+ }
70
+
71
+ function debugUseReachesContract(
72
+ member: string,
73
+ value: unknown,
74
+ name: string | undefined,
75
+ commands: ReadonlySet<string>,
76
+ state: ReadonlySet<string>,
77
+ ): boolean {
78
+ if (member === 'commands') {
79
+ const returned = returnedNames(value);
80
+ return Array.isArray(value) && [...commands].every((declared) => returned.has(declared));
81
+ }
82
+ if (member === 'providers') {
83
+ const returned = returnedNames(value);
84
+ return Array.isArray(value) && [...state].every((declared) => returned.has(declared));
85
+ }
86
+ if (!name) return false;
87
+ if (member === 'state') return state.has(name);
88
+ if (member === 'invoke') return commands.has(name);
89
+ return false;
90
+ }
91
+
92
+ function recordContractSystemUse(member: string, value: unknown, name?: string): void {
93
+ const contract = activeIngestContract();
94
+ const systems = contract?.systems;
95
+ if (!contract || !systems) return;
96
+ const commands = new Set((systems.commands ?? []).map((entry) => entry.name));
97
+ const state = new Set((systems.state ?? []).map((entry) => entry.name));
98
+ if (!debugUseReachesContract(member, value, name, commands, state)) return;
99
+ recordLiveSeamEvidence({
100
+ seam: 'contract.systems',
101
+ subject: 'game',
102
+ epoch: gameContractEpoch(contract),
103
+ stage: 'operation',
104
+ outcome: 'pass',
105
+ source: 'consumer',
106
+ detail: `the session wire consumed contract systems through debug.${member}`,
107
+ });
108
+ }
109
+
110
+ /**
111
+ * #140 — the generic session-wire dispatch table: the SAME method-name
112
+ * surface `window.__vgai` exposes (`@volter/game-runtime/runtime/debug-bridge`'s
113
+ * `VgaiDebugHandle` — `state`/`stateAll`/`providers`/`commands`/`events`/
114
+ * `snapshot`/`invoke`/`runTicks`/`input.*`), built from the SAME
115
+ * registry-backed accessors the sibling
116
+ * `gameplay.command.ts` verbs (`list-gameplay-state`/`inject-input`/`run-ticks`)
117
+ * already use. This is what makes the
118
+ * `bridge-call` relay op (below) a session-generic primitive rather than a
119
+ * test-specific one — any client of the live session (the `@volter/editor-live`
120
+ * `RelayTransport`, a future one-shot `vgai game state/call/hold` REPL) can
121
+ * issue `{method, callArgs}` against it with no dependency on a proof run's
122
+ * own lifecycle/env, and get byte-identical dispatch to the page-transport's
123
+ * `page.evaluate(bridgeCallInPage)` (D17) — without requiring the editor tab
124
+ * to be loaded with `?vgai-debug=1` (that gate exists for the bridge's own
125
+ * production exposure; this relay is already local-loopback-only — see
126
+ * `editor-server.ts`'s CSRF/origin guard).
127
+ */
128
+ export async function dispatchBridgeMethod(
129
+ method: string,
130
+ callArgs: unknown[],
131
+ instance?: string,
132
+ ): Promise<unknown> {
133
+ // ADDRESSED resolution, not editor focus. `getActiveSystems()` answers "the
134
+ // game the user is looking at", which is the right question for the panels
135
+ // and the wrong one here: a relayed command names an instance, and resolving
136
+ // it through focus would let a bot drive whichever game was last clicked.
137
+ // With one instance the two coincide, which is why this was invisible.
138
+ let debug: SystemAdapters['debug'];
139
+ try {
140
+ debug = systemsForInstance(instance).debug;
141
+ } catch (err) {
142
+ if (err instanceof InstanceResolutionError) {
143
+ throw bridgeError('INSTANCE_UNRESOLVED', `session wire: "${method}" — ${err.message}`, {
144
+ instances: err.instances,
145
+ });
146
+ }
147
+ throw err;
148
+ }
149
+ const requireDebug = () => {
150
+ if (!debug) {
151
+ throw bridgeError(
152
+ 'DEBUG_ADAPTER_UNAVAILABLE',
153
+ `session wire: "${method}" — this game exposes no debug adapter`,
154
+ );
155
+ }
156
+ return debug;
157
+ };
158
+ // Named `throughDebug`, not `useDebug`: a `use`-prefixed local reads as a
159
+ // React hook to the lint rules a package is checked under.
160
+ const throughDebug = <T>(member: string, stage: 'operation' | 'effect', run: () => T): T => {
161
+ const adapter = requireDebug();
162
+ const epoch = systemAdapterEpoch(adapter);
163
+ try {
164
+ const value = run();
165
+ recordLiveSeamEvidence({
166
+ seam: `system.debug.${member}`,
167
+ subject: 'game',
168
+ epoch,
169
+ stage,
170
+ outcome: 'pass',
171
+ source: 'consumer',
172
+ detail: `the session wire called debug.${member} and received its result`,
173
+ });
174
+ recordLiveSeamEvidence({
175
+ seam: 'system.debug',
176
+ subject: 'game',
177
+ epoch,
178
+ stage: 'operation',
179
+ outcome: 'pass',
180
+ source: 'consumer',
181
+ detail: 'the session wire consumed the mounted debug adapter',
182
+ });
183
+ recordContractSystemUse(member, value, callArgs[0] as string | undefined);
184
+ return value;
185
+ } catch (error) {
186
+ recordLiveSeamEvidence({
187
+ seam: `system.debug.${member}`,
188
+ subject: 'game',
189
+ epoch,
190
+ stage,
191
+ outcome: 'fail',
192
+ source: 'consumer',
193
+ detail: `the session wire called debug.${member} and it failed: ${error instanceof Error ? error.message : String(error)}`,
194
+ });
195
+ throw error;
196
+ }
197
+ };
198
+ switch (method) {
199
+ case 'state':
200
+ return throughDebug('state', 'operation', () => requireDebug().state(callArgs[0] as string));
201
+ case 'stateAll':
202
+ return throughDebug('stateAll', 'operation', () => requireDebug().stateAll());
203
+ case 'providers':
204
+ return throughDebug('providers', 'operation', () => requireDebug().providers());
205
+ case 'commands':
206
+ return throughDebug('commands', 'operation', () => requireDebug().commands());
207
+ case 'events':
208
+ return throughDebug('events', 'operation', () =>
209
+ requireDebug().events(callArgs[0] as number | undefined),
210
+ );
211
+ case 'snapshot': {
212
+ const adapter = requireDebug();
213
+ return {
214
+ time: throughDebug('state', 'operation', () => adapter.state('time')) as {
215
+ simSeconds: number;
216
+ tick: number;
217
+ },
218
+ state: throughDebug('stateAll', 'operation', () => adapter.stateAll()),
219
+ // Run-4 friction #5 — same `sinceSeq` contract as the in-page
220
+ // bridge's `snapshot()` (`runtime/debug-bridge.ts`): both doors must
221
+ // fence identically, or a script driving the live session (this door)
222
+ // vs. standalone (that door) could disagree about which events are
223
+ // "new".
224
+ events: throughDebug('events', 'operation', () =>
225
+ adapter.events(callArgs[0] as number | undefined),
226
+ ),
227
+ // #146 (closes the #140 honest-empty gap): the editor page already
228
+ // funnels window error/unhandledrejection into the editor console as
229
+ // level 'error' / source 'runtime' (`installEditorConsoleCapture`'s
230
+ // boot-time listeners) — the SAME events and message format
231
+ // `window.__vgai`'s own handle buffers. Fenced to the current play run
232
+ // via `inPlayRun` so a probe never reads a previous run's stale
233
+ // failures, capped at 100 for parity with the bridge's ring buffer.
234
+ pageErrors: collectPlayRunPageErrors(),
235
+ };
236
+ }
237
+ case 'invoke': {
238
+ const name = callArgs[0] as string;
239
+ const invokeArgs = (callArgs[1] as unknown[]) ?? [];
240
+ try {
241
+ const result = await requireDebug().invoke(name, invokeArgs);
242
+ const adapter = requireDebug();
243
+ recordLiveSeamEvidence({
244
+ seam: 'system.debug.invoke',
245
+ subject: 'game',
246
+ epoch: systemAdapterEpoch(adapter),
247
+ stage: 'effect',
248
+ outcome: 'pass',
249
+ source: 'consumer',
250
+ detail: `the session wire invoked debug command "${name}" and received its result`,
251
+ });
252
+ recordLiveSeamEvidence({
253
+ seam: 'system.debug',
254
+ subject: 'game',
255
+ epoch: systemAdapterEpoch(adapter),
256
+ stage: 'operation',
257
+ outcome: 'pass',
258
+ source: 'consumer',
259
+ detail: 'the session wire consumed the mounted debug adapter',
260
+ });
261
+ recordContractSystemUse('invoke', result, name);
262
+ return result;
263
+ } catch (error) {
264
+ const adapter = requireDebug();
265
+ recordLiveSeamEvidence({
266
+ seam: 'system.debug.invoke',
267
+ subject: 'game',
268
+ epoch: systemAdapterEpoch(adapter),
269
+ stage: 'effect',
270
+ outcome: 'fail',
271
+ source: 'consumer',
272
+ detail: `the session wire invoked debug command "${name}" and it failed: ${error instanceof Error ? error.message : String(error)}`,
273
+ });
274
+ throw error;
275
+ }
276
+ }
277
+ case 'runTicks': {
278
+ const runtime = getPlayRuntimeAccess();
279
+ if (!runtime?.runTicks) {
280
+ throw bridgeError(
281
+ 'RUN_TICKS_UNAVAILABLE',
282
+ 'this play session has no first-party Game to run ticks on',
283
+ );
284
+ }
285
+ const n = callArgs[0] as number;
286
+ const opts = callArgs[1] as { render?: 'last' | 'all' | 'none' } | undefined;
287
+ runtime.runTicks(n, opts);
288
+ return undefined;
289
+ }
290
+ case 'runTicksSettled': {
291
+ // The settled-aware driver (`engine/runtime/run-ticks-settled.ts`) — a tick never races
292
+ // a scene remount's async commit; byte-identical with the relay's `run-ticks` case and
293
+ // the page bridge's own `runTicksSettled` (D17).
294
+ const runtime = getPlayRuntimeAccess();
295
+ if (!runtime?.runTicksSettled) {
296
+ throw bridgeError(
297
+ 'RUN_TICKS_UNAVAILABLE',
298
+ 'this play session has no first-party Game to run ticks on',
299
+ );
300
+ }
301
+ const n = callArgs[0] as number;
302
+ const opts = callArgs[1] as { render?: 'last' | 'all' | 'none' } | undefined;
303
+ await runtime.runTicksSettled(n, opts);
304
+ return undefined;
305
+ }
306
+ case 'input.setVirtualAction':
307
+ case 'input.tapVirtualAction':
308
+ case 'input.clearVirtualActions':
309
+ // bridge↔wire coverage-parity gate (`bridge-wire-parity.test.ts`)
310
+ // closed this trio's gap: `startRecording`/`stopRecording`/`isRecording`
311
+ // exist on the page bridge's `VgaiDebugInputHandle` (`runtime/
312
+ // debug-bridge.ts`) but had no relay case at all — an agent driving the
313
+ // input-trace recorder over the session wire (RelayTransport) instead of
314
+ // `?vgai-debug=1` simply had no door to it. Same single optional
315
+ // trailing `worldId` shape as `clearVirtualActions`.
316
+ case 'input.startRecording':
317
+ case 'input.stopRecording':
318
+ case 'input.isRecording': {
319
+ const runtime = getPlayRuntimeAccess();
320
+ // D15/T-D15.5 (rebase composition with #140): this wire is a THIRD
321
+ // door to the virtual-input target — it must resolve through the SAME
322
+ // `DebugRegistry.getVirtualInputTarget(worldId?)` the page bridge
323
+ // (`window.__vgai.input.*`) and the `inject-input` relay case use, so
324
+ // no two doors can ever disagree about which world an unqualified
325
+ // actuation targets. Each method takes the page bridge's own optional
326
+ // trailing `worldId` (byte-parity with `VgaiDebugInputHandle`); an
327
+ // explicit, unregistered id throws the registry's structured
328
+ // `DEBUG_INPUT_WORLD_NOT_FOUND`, shipped back over the wire as-is.
329
+ const worldId = (
330
+ method === 'input.setVirtualAction'
331
+ ? callArgs[2]
332
+ : method === 'input.tapVirtualAction'
333
+ ? callArgs[1]
334
+ : callArgs[0]
335
+ ) as string | undefined;
336
+ const inputTarget = runtime ? runtime.getInputTarget(worldId) : null;
337
+ if (!inputTarget) {
338
+ throw bridgeError(
339
+ 'DEBUG_INPUT_UNAVAILABLE',
340
+ `session wire: ${method}() has no virtual-input target wired — no default three ` +
341
+ "world has mounted yet, or this project's mount path never wired one",
342
+ );
343
+ }
344
+ if (method === 'input.setVirtualAction') {
345
+ return inputTarget.setVirtualAction(
346
+ callArgs[0] as string,
347
+ callArgs[1] as boolean | number | { x: number; y: number },
348
+ );
349
+ }
350
+ if (method === 'input.tapVirtualAction') {
351
+ return inputTarget.tapVirtualAction(callArgs[0] as string);
352
+ }
353
+ if (method === 'input.clearVirtualActions') {
354
+ inputTarget.clearVirtualActions();
355
+ return undefined;
356
+ }
357
+ if (method === 'input.startRecording') {
358
+ inputTarget.startInputRecording();
359
+ return undefined;
360
+ }
361
+ if (method === 'input.stopRecording') {
362
+ inputTarget.stopInputRecording();
363
+ return undefined;
364
+ }
365
+ return inputTarget.isInputRecording();
366
+ }
367
+ case 'input.scheduleActionAtTick': {
368
+ // bridge↔wire coverage-parity gate: `window.__vgai.input.
369
+ // scheduleActionAtTick` (`runtime/debug-bridge.ts`, D15/T-D15.5) had NO
370
+ // bridge-call case — it was reachable over the relay ONLY via the
371
+ // separate `inject-input` command's `atTick` field (see that case,
372
+ // above), never by this method name. Same `getInputTarget(worldId?)`
373
+ // resolution as the other `input.*` cases; forwards `tick`/`action`/
374
+ // `value` straight through and returns whatever the target returns
375
+ // (`void`, same as the page bridge's own `scheduleActionAtTick`).
376
+ const runtime = getPlayRuntimeAccess();
377
+ const tick = callArgs[0] as number;
378
+ const action = callArgs[1] as string;
379
+ const value = callArgs[2] as boolean | number | { x: number; y: number };
380
+ const worldId = callArgs[3] as string | undefined;
381
+ const inputTarget = runtime ? runtime.getInputTarget(worldId) : null;
382
+ if (!inputTarget) {
383
+ throw bridgeError(
384
+ 'DEBUG_INPUT_UNAVAILABLE',
385
+ 'session wire: input.scheduleActionAtTick() has no virtual-input target wired — no ' +
386
+ "default three world has mounted yet, or this project's mount path never wired one",
387
+ );
388
+ }
389
+ return inputTarget.scheduleActionAtTick(tick, action, value);
390
+ }
391
+ case 'input.injectPointerDelta':
392
+ case 'input.injectPointerPosition': {
393
+ // pointer-dispatch op: `window.__vgai.input.injectPointerDelta`/
394
+ // `injectPointerPosition` (`runtime/debug-bridge.ts`) had no relay case
395
+ // — this is the HONEST gameplay-seam reading of "pointer dispatch": the
396
+ // engine's existing virtual pointer-injection, never a synthesized DOM
397
+ // mouse event (that boundary belongs to the future playwright shim).
398
+ // Same `getInputTarget(worldId?)` resolution as every other `input.*`
399
+ // case.
400
+ const runtime = getPlayRuntimeAccess();
401
+ const sourceId = callArgs[0] as string;
402
+ const value = callArgs[1] as { x: number; y: number };
403
+ const worldId = callArgs[2] as string | undefined;
404
+ const inputTarget = runtime ? runtime.getInputTarget(worldId) : null;
405
+ if (!inputTarget) {
406
+ throw bridgeError(
407
+ 'DEBUG_INPUT_UNAVAILABLE',
408
+ `session wire: ${method}() has no virtual-input target wired — no default three ` +
409
+ "world has mounted yet, or this project's mount path never wired one",
410
+ );
411
+ }
412
+ if (method === 'input.injectPointerDelta') {
413
+ inputTarget.injectPointerDelta(sourceId, value);
414
+ } else {
415
+ inputTarget.injectPointerPosition(sourceId, value);
416
+ }
417
+ return undefined;
418
+ }
419
+ case 'holdFor': {
420
+ // #140/D17: byte-identical semantics to `window.__vgai.holdFor` (door
421
+ // a, `runtime/debug-bridge.ts`) — same `getInputTarget(worldId?)`
422
+ // resolution as the `input.*` cases above, same `waitForHoldBudget`
423
+ // poll/stall loop (imported, not hand-copied, so the two doors can
424
+ // never silently drift apart), reached through `requireDebug().state
425
+ // ('time')` instead of a `DebugRegistry` (this relay only has
426
+ // `getActiveSystems().debug`, not the registry itself).
427
+ const runtime = getPlayRuntimeAccess();
428
+ const worldId = callArgs[2] as string | undefined;
429
+ const inputTarget = runtime ? runtime.getInputTarget(worldId) : null;
430
+ if (!inputTarget) {
431
+ throw bridgeError(
432
+ 'DEBUG_INPUT_UNAVAILABLE',
433
+ 'session wire: holdFor() has no virtual-input target wired — no default three ' +
434
+ "world has mounted yet, or this project's mount path never wired one",
435
+ );
436
+ }
437
+ const action = callArgs[0] as string;
438
+ const simSeconds = callArgs[1] as number;
439
+ const setResult = inputTarget.setVirtualAction(action, true);
440
+ if (!setResult.delivered) {
441
+ inputTarget.clearVirtualActions();
442
+ return { delivered: false, reason: setResult.reason };
443
+ }
444
+ const { stalled, starvedWithoutDriver } = await waitForHoldBudget(
445
+ requireDebug(),
446
+ simSeconds,
447
+ runtime?.runTicks ? (n) => runtime.runTicks?.(n, { render: 'last' }) : undefined,
448
+ );
449
+ inputTarget.clearVirtualActions();
450
+ if (!stalled) return { delivered: true };
451
+ return {
452
+ delivered: false,
453
+ reason: starvedWithoutDriver ? HOLD_STARVED_NO_DRIVER_REASON : 'play stopped during hold',
454
+ };
455
+ }
456
+ default:
457
+ throw bridgeError('UNKNOWN_BRIDGE_METHOD', `session wire: unknown bridge method "${method}"`);
458
+ }
459
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The two frame indirections `bridge-screenshot` and `bridge-recording-start`
3
+ * need, copied out of the editor's `command-listener.ts` with them (WORK.md
4
+ * §The workbench). Each is a lazy hop, not logic: the host keeps its own
5
+ * copies for the verbs that stay there (the play boot and
6
+ * `bridge-recording-export`, which still reads the shell store).
7
+ */
8
+
9
+ import { getPlayRuntimeAccess } from '../play/play-mode';
10
+
11
+ /** Pixel recovery is a command-time concern. Keeping this indirection here
12
+ * avoids loading the Pixi extraction stack merely because the control relay
13
+ * is listening for a future screenshot/recording command. */
14
+ export async function captureLiveCanvasFrame(
15
+ canvas: HTMLCanvasElement,
16
+ ): Promise<CanvasImageSource | null> {
17
+ const { liveCanvasFrame } = await import('@volter/editor-core/live-canvas-frame');
18
+ return liveCanvasFrame(canvas);
19
+ }
20
+
21
+ /** Both recording entry points keep a hidden native game's pixels current. */
22
+ export function refreshRecordingFrame(): void {
23
+ const runtime = getPlayRuntimeAccess();
24
+ if (runtime?.loop.liveness === 'loop-starved') runtime.runTicks?.(1, { render: 'last' });
25
+ }