@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,355 @@
1
+ /**
2
+ * THE PLAY VERBS of the session wire (`@volter/editor-sdk/commands`, a
3
+ * `workspace.command` contribution): `play`, `stop`, `pause`, `resume`,
4
+ * `step`, and the paused run's `bridge-recording-export`.
5
+ *
6
+ * These left `command-listener.ts`'s switch when Play left the host (WORK.md
7
+ * §The workbench, P3b). What did NOT leave is the host's dispatch PROLOGUE,
8
+ * which still runs before any contributed handler is looked up and therefore
9
+ * still owns the two gates that are not Play's: the ingest play-control latch
10
+ * (a captured ingest answers `play`/`stop`/`pause`/`resume`/`step` itself —
11
+ * P4's move) and the mount-failure refusal. The prologue also refuses every
12
+ * OTHER command while a video export owns a paused run, reading
13
+ * `gameplay-export-state.ts`, which stays host-visible for exactly that (the
14
+ * PlayBar reads it too). The AbortController behind that flag is this file's:
15
+ * `stop` falls THROUGH the prologue so the handler below can cancel the export
16
+ * before tearing the run down.
17
+ *
18
+ * Host internals Play still holds are reached through the `@editor/*` alias
19
+ * the editor's Vite serves to every contribution (precedent:
20
+ * `instances.command.ts`); the doors a lane is meant to use are
21
+ * `@volter/editor-sdk/host`.
22
+ *
23
+ * `play-recording.ts` is Play's own (`../src/play/`): its idle watchdog reads
24
+ * every relayed command through `host.session.onCommandDispatched`, the door
25
+ * P4b added, so the stamp is no longer a host call. `gameplay-recording.ts`
26
+ * stays in the host — `components/GameplaySessionTimeline.tsx`, which
27
+ * `ToolHost.tsx` mounts directly for the `workspace.analytics` point, imports
28
+ * it, and no contribution point injects a panel's header component.
29
+ */
30
+
31
+ import { getActiveSystems } from '@volter/editor-core/authoring/active-systems';
32
+ import { setGameplayExportActive } from '@volter/editor-core/gameplay-export-state';
33
+ import { stopGameplayRecording } from '../src/host/gameplay-recording';
34
+ import { liveInstanceContainer } from '@volter/editor-core/live-session-registry';
35
+ import type { CommandContribution } from '@volter/editor-sdk/commands';
36
+ import { editorHost } from '@volter/editor-sdk/host';
37
+ import type { OfflineAudioRenderer } from '@volter/editor-project/adapter';
38
+ import { flushSync } from 'react-dom';
39
+ import { notPlayingResult, structuredErrorResult } from '../src/command-results';
40
+ import {
41
+ enterPlayMode,
42
+ exitPlayMode,
43
+ getPlayRuntimeAccess,
44
+ pausePlayMode,
45
+ resumePlayMode,
46
+ stepPlayMode,
47
+ } from '../src/play/play-mode';
48
+ import { beginPlayRecording, endPlayRecording, notePlayActivity } from '../src/play/play-recording';
49
+
50
+ export const point = 'workspace.command';
51
+
52
+ /** Pixel recovery is a command-time concern. Keeping this indirection here
53
+ * avoids loading the Pixi extraction stack merely because the control relay
54
+ * is listening for a future screenshot/recording command. */
55
+ async function captureLiveCanvasFrame(
56
+ canvas: HTMLCanvasElement,
57
+ ): Promise<CanvasImageSource | null> {
58
+ const { liveCanvasFrame } = await import('@volter/editor-core/live-canvas-frame');
59
+ return liveCanvasFrame(canvas);
60
+ }
61
+
62
+ /** Both recording entry points keep a hidden native game's pixels current. */
63
+ function refreshRecordingFrame(): void {
64
+ const runtime = getPlayRuntimeAccess();
65
+ if (runtime?.loop.liveness === 'loop-starved') runtime.runTicks?.(1, { render: 'last' });
66
+ }
67
+
68
+ /**
69
+ * The fixed-step export's audio, or `undefined` when this world has none.
70
+ *
71
+ * Two facts have to line up, and BOTH come from the live run, never from a
72
+ * default. The world's `AudioAdapter` must implement `renderOffline` (the
73
+ * offline, deterministic sibling of the recorder's real-time
74
+ * `acquireRecordingStream` — a paused run being stepped makes no real-time
75
+ * sound at all), and the run must be able to say WHERE IT STANDS on its own
76
+ * canonical clock, because the render is asked for an ABSOLUTE sim window.
77
+ * `time` is the engine's built-in debug state provider
78
+ * (`engine/src/runtime/debug-registry.ts`), the same one the play log stamps
79
+ * entries from.
80
+ *
81
+ * A world that cannot answer the clock question — an ingest root with no tick
82
+ * runner — gets no audio rather than a fabricated `start: 0`, which would
83
+ * silently render the wrong slice of the score against the right frames.
84
+ */
85
+ function exportAudioSource(): { startSeconds: number; render: OfflineAudioRenderer } | undefined {
86
+ const audio = getActiveSystems().audio;
87
+ const render = audio?.renderOffline;
88
+ if (!audio || !render) return undefined;
89
+ let startSeconds: number | null = null;
90
+ try {
91
+ const time = getActiveSystems().debug?.state('time') as { simSeconds?: number } | undefined;
92
+ if (typeof time?.simSeconds === 'number' && Number.isFinite(time.simSeconds)) {
93
+ startSeconds = time.simSeconds;
94
+ }
95
+ } catch {
96
+ startSeconds = null;
97
+ }
98
+ if (startSeconds === null) return undefined;
99
+ return { startSeconds, render: render.bind(audio) };
100
+ }
101
+
102
+ /** The in-flight video export's cancel handle. Module state, like the flag it
103
+ * raises in `gameplay-export-state.ts`: one paused run, one export. */
104
+ let gameplayExportAbort: AbortController | null = null;
105
+
106
+ export const commands: CommandContribution['commands'] = {
107
+ play: {
108
+ // `play` boots the game, including its full async `setup()` (asset loads,
109
+ // procedural worldgen); under the generic budget any substantial game
110
+ // reported "did not respond" while play in fact succeeded.
111
+ timeoutMs: 120_000,
112
+ derivedRefresh: 'always',
113
+ handle: async (cmd) => {
114
+ // Fix for the ghost-runtime bug — deliberately no "already running"
115
+ // guard here: `play` on an already-playing (or still-booting) session
116
+ // is a RESTART, not a no-op, matching the SDK's own documented contract
117
+ // (`play.start`'s summary: "Start (or restart) play mode" —
118
+ // `packages/vgai-sdk/src/play/lifecycle-operations.ts`). What used to
119
+ // make a restart unsafe was `enterPlayMode()` having no reentrancy
120
+ // guard for the async-boot window before it assigns `_session` — two
121
+ // overlapping calls ran their entire prologues concurrently and
122
+ // corrupted shared module state (proven by a real console-patching
123
+ // stack overflow — see `play-mode-reentry-ghost.test.ts`). That is
124
+ // fixed at the source in `enterPlayMode()` itself (a FIFO queue
125
+ // serializes every invocation), so every caller of THIS handler — CLI,
126
+ // SDK, a UI Play-button click racing a relayed command — gets a safe,
127
+ // clean restart for free without needing a guard here too.
128
+ try {
129
+ // D15/T-D15.6 (objection-4 fix) — `vgai play --seed <n>` relays as
130
+ // `cmd['seed']`; `enterPlayMode`'s explicit-config seed leg (beats
131
+ // manifest.determinism.defaultSeed/?vgai-seed=).
132
+ const seed = cmd['seed'];
133
+ // `vgai play --name <text>` rides the same wire as `--seed`: a plain
134
+ // field on the relayed command, slugified server-side into this run's
135
+ // log filename and journal line. Absent, everything is unchanged.
136
+ const runName = cmd['name'];
137
+ await enterPlayMode(
138
+ typeof seed === 'number' ? seed : undefined,
139
+ undefined,
140
+ typeof runName === 'string' ? runName : null,
141
+ );
142
+ } catch (err) {
143
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
144
+ }
145
+ // PD-1 — never ack a play that did not start. `enterPlayMode()` resolving
146
+ // does NOT mean play is running: it also resolves for every path that
147
+ // abandoned this run (a Stop or a newer play landed during the async
148
+ // boot, invalidating this generation). Those used to return `{ok:true}`,
149
+ // and the CLI — seeing `playState: 'stopped'` a moment later — fell back
150
+ // to "✗ Play mode failed — check logs/play-*.jsonl", a message naming no
151
+ // cause and pointing at a log that says nothing about it. Ack the
152
+ // measured truth (the live registry, the same predicate `collectState`
153
+ // derives `playState` from) instead of the call returning.
154
+ // The other honest answers are adapter-owned: a root with design-time
155
+ // pieces is a deferred ingest Play session, a boot-mounted ingest runs
156
+ // through its explicit play-control latch, and a standalone `{ module }`
157
+ // route owns its module session. None allocates `play-mode.ts`'s
158
+ // first-party session object, and answering "play did not start" over a
159
+ // visibly running game is the same fabrication in the other direction.
160
+ if (!editorHost().live.playing()) {
161
+ return {
162
+ ok: false,
163
+ error:
164
+ 'Play did not start: the run was superseded before it finished booting (a stop, or a ' +
165
+ 'newer play, landed during startup). Nothing is running.',
166
+ };
167
+ }
168
+ // EVERY PLAY RECORDS. There is no flag and no opt-in — see
169
+ // `play-recording.ts` for why the capability had to stop being one an
170
+ // agent must remember to switch on. `cmd['record']` only NAMES the file
171
+ // (`vgai play --record <name>`); its absence names the clip after the
172
+ // durable Gameplay Session.
173
+ //
174
+ // Started here rather than inside `enterPlayMode` on purpose: this
175
+ // handler is the RELAYED play (`vgai play`, the SDK's `play.start`),
176
+ // which is exactly the population the recording exists for. A human
177
+ // clicking the editor's own Play button is their own witness and gets
178
+ // play unchanged.
179
+ const record = cmd['record'];
180
+ const recording = await beginPlayRecording(
181
+ liveInstanceContainer() ?? editorHost().workspace.liveDocument.container(),
182
+ {
183
+ name: typeof record === 'string' ? record : null,
184
+ audio: getActiveSystems().audio?.acquireRecordingStream?.() ?? null,
185
+ canvasFrame: captureLiveCanvasFrame,
186
+ // A hidden tab's game loop is STOPPED (`engine/src/core/game-loop.ts`
187
+ // never arms rAF while `document.hidden`), so its canvas holds one
188
+ // frozen frame. This is the still-capture path's own recovery — one
189
+ // deterministic tick, rendered — applied per recorded frame.
190
+ refreshFrame: refreshRecordingFrame,
191
+ },
192
+ );
193
+ return { ok: true, ...(recording ? { data: { recording } } : {}) };
194
+ },
195
+ },
196
+ stop: {
197
+ // `stop` waits on teardown (dispose chain, Rapier world free, renderer
198
+ // teardown) — routinely past the generic budget under software rendering.
199
+ timeoutMs: 30_000,
200
+ derivedRefresh: 'always',
201
+ handle: async () => {
202
+ // A video export OWNS the paused run it is stepping, and Stop is the one
203
+ // command the host prologue lets past its refusal so it can cancel it.
204
+ gameplayExportAbort?.abort(new Error('Video export cancelled by Stop.'));
205
+ // Finalize the clip BEFORE the surface it is photographing is torn down:
206
+ // a WebM is unreadable until its recorder closes it, and compositing
207
+ // through a teardown truncates the last seconds — which are the ones
208
+ // someone stopping the run wanted to look at.
209
+ const capture = await endPlayRecording('stop');
210
+ exitPlayMode();
211
+ return { ok: true, ...(capture ? { data: { recording: capture } } : {}) };
212
+ },
213
+ },
214
+ pause: {
215
+ derivedRefresh: 'always',
216
+ handle: () => {
217
+ pausePlayMode();
218
+ return { ok: true };
219
+ },
220
+ },
221
+ resume: {
222
+ derivedRefresh: 'always',
223
+ handle: () => {
224
+ resumePlayMode();
225
+ return { ok: true };
226
+ },
227
+ },
228
+ step: {
229
+ derivedRefresh: 'always',
230
+ handle: () => {
231
+ stepPlayMode();
232
+ return { ok: true };
233
+ },
234
+ },
235
+ // The video export hands control to the GAME (it steps a paused run frame
236
+ // by frame), so its budget is the game's, not the editor's.
237
+ 'bridge-recording-export': {
238
+ timeoutMs: 600_000,
239
+ derivedRefresh: 'none',
240
+ handle: async (cmd) => {
241
+ const host = editorHost();
242
+ if (host.session.playState() !== 'paused')
243
+ return { ok: false, error: 'Pause the game before exporting video.' };
244
+ const container = liveInstanceContainer();
245
+ if (!container) return notPlayingResult();
246
+ if (gameplayExportAbort) return { ok: false, error: 'A video export is already active.' };
247
+ const { exportGameplayVideo, validateGameplayExport, assertGameplayVideoSupport } =
248
+ await import('../src/host/gameplay-export');
249
+ const exportOptions = {
250
+ frames: Number(cmd['frames']),
251
+ fps: cmd['fps'] === undefined ? 30 : Number(cmd['fps']),
252
+ };
253
+ try {
254
+ validateGameplayExport(exportOptions);
255
+ } catch (error) {
256
+ return structuredErrorResult(error);
257
+ }
258
+ if (gameplayExportAbort) return { ok: false, error: 'A video export is already active.' };
259
+ const controller = new AbortController();
260
+ gameplayExportAbort = controller;
261
+ setGameplayExportActive(true);
262
+ const deadline = setTimeout(
263
+ () => controller.abort(new Error('Video export exceeded its ten-minute deadline.')),
264
+ 590_000,
265
+ );
266
+ const playStartedAt = host.live.runWindow()?.startedAt ?? null;
267
+ const unsubscribe = host.session.subscribe(() => {
268
+ if (
269
+ host.session.playState() !== 'paused' ||
270
+ (host.live.runWindow()?.startedAt ?? null) !== playStartedAt
271
+ ) {
272
+ controller.abort(new Error('Video export cancelled: the Play session changed.'));
273
+ }
274
+ });
275
+ // The world's own audio for the sim window the frame walk will cover.
276
+ // `renderOffline` is the fixed-step sibling of the recorder's real-time
277
+ // `acquireRecordingStream`; a world that does not implement it exports a
278
+ // genuinely video-only file, said so in the result and in the clip note.
279
+ const offlineAudio = exportAudioSource();
280
+ try {
281
+ await assertGameplayVideoSupport(container);
282
+ controller.signal.throwIfAborted();
283
+ const { gameplayRecordingActive } = await import('../src/host/gameplay-recording');
284
+ if (gameplayRecordingActive()) await stopGameplayRecording();
285
+ const { blob, ...result } = await exportGameplayVideo(container, exportOptions, {
286
+ step: () => flushSync(() => stepPlayMode()),
287
+ assertActive: () => {
288
+ notePlayActivity();
289
+ if (
290
+ host.session.playState() !== 'paused' ||
291
+ liveInstanceContainer() !== container ||
292
+ (host.live.runWindow()?.startedAt ?? null) !== playStartedAt
293
+ ) {
294
+ throw new Error('Video export interrupted: its game must remain paused and mounted.');
295
+ }
296
+ },
297
+ canvasFrame: captureLiveCanvasFrame,
298
+ signal: controller.signal,
299
+ ...(offlineAudio ? { audio: offlineAudio } : {}),
300
+ });
301
+ const {
302
+ beginGameplayRecordingSink,
303
+ appendGameplayRecordingChunk,
304
+ finishGameplayRecordingSink,
305
+ abortGameplayRecordingSink,
306
+ } = await import('@volter/editor-core/editor-api');
307
+ controller.signal.throwIfAborted();
308
+ const sink = await beginGameplayRecordingSink(
309
+ {
310
+ startedAt: new Date().toISOString(),
311
+ mimeType: 'video/webm',
312
+ purpose: 'export',
313
+ name: typeof cmd['name'] === 'string' ? cmd['name'] : `export-${Date.now()}`,
314
+ },
315
+ controller.signal,
316
+ );
317
+ try {
318
+ for (
319
+ let offset = 0, sequence = 0;
320
+ offset < blob.size;
321
+ offset += 4 * 1024 * 1024, sequence++
322
+ ) {
323
+ controller.signal.throwIfAborted();
324
+ await appendGameplayRecordingChunk(
325
+ sink,
326
+ sequence,
327
+ blob.slice(offset, offset + 4 * 1024 * 1024),
328
+ controller.signal,
329
+ );
330
+ }
331
+ controller.signal.throwIfAborted();
332
+ await finishGameplayRecordingSink(
333
+ sink,
334
+ result.audio
335
+ ? `fixed-step export with audio (${result.audio.codec}, ${result.audio.channels}ch @ ${result.audio.sampleRate} Hz)`
336
+ : 'fixed-step export, video only: this world has no AudioAdapter.renderOffline',
337
+ controller.signal,
338
+ );
339
+ controller.signal.throwIfAborted();
340
+ } catch (error) {
341
+ await abortGameplayRecordingSink(sink);
342
+ throw error;
343
+ }
344
+ return { ok: true, data: { ...result, path: sink.path } };
345
+ } catch (error) {
346
+ return structuredErrorResult(error);
347
+ } finally {
348
+ unsubscribe();
349
+ clearTimeout(deadline);
350
+ gameplayExportAbort = null;
351
+ setGameplayExportActive(false);
352
+ }
353
+ },
354
+ },
355
+ };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * `Open Profiler` in the command palette — the same reveal as Debug ▸
3
+ * Profiler, through the action point.
4
+ */
5
+ import { profilerView } from '../src/host/components/utility-view-state';
6
+ import type { ActionContribution } from '@volter/editor-sdk/chrome';
7
+
8
+ export const point = 'workspace.action';
9
+
10
+ export const actions: ActionContribution['actions'] = [
11
+ {
12
+ id: 'profiler',
13
+ label: 'Open Profiler',
14
+ execute: () => profilerView.open('profiler'),
15
+ },
16
+ ];
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Debug ▸ Profiler — the menu item that selects the bench's Profiler view and
3
+ * reveals the drawer tab this package contributes. The host namespaces every
4
+ * contributed id under `tool:`, so the utility revealed is
5
+ * `tool:profiler.utility` (`profilerView.open` names it).
6
+ */
7
+ import { profilerView } from '../src/host/components/utility-view-state';
8
+ import type { MenuContribution } from '@volter/editor-sdk/chrome';
9
+
10
+ export const point = 'workspace.menu';
11
+
12
+ export const menu: MenuContribution = {
13
+ menu: 'debug',
14
+ items: [
15
+ {
16
+ id: 'profiler',
17
+ label: 'Profiler',
18
+ testId: 'menu-profiler',
19
+ execute: () => profilerView.open('profiler'),
20
+ },
21
+ ],
22
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The Profiler as a drawer tab (W4/W4b): the engine's instrument bench —
3
+ * frame cost over time (Profiler) and the single-frame draw-call capture
4
+ * (Frame) over the inspected session's `RenderDebugAdapter` and the active
5
+ * document's registered render loop. Always on offer: the editor's own
6
+ * documents register profiler sources in edit mode, and Play supplies the
7
+ * running game's.
8
+ */
9
+ import { ProfilerPanel } from '../src/profiler/ProfilerPanel';
10
+
11
+ export const point = 'workspace.utility';
12
+ export const title = 'Profiler';
13
+ export const order = 20;
14
+
15
+ export default function ProfilerUtility() {
16
+ return <ProfilerPanel />;
17
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * THE DOM MEDIUM'S COMPONENT BOARD (`@volter/editor-sdk/services`, a
3
+ * `workspace.service` contribution): `@volter/editor-game` tells the host's component
4
+ * board registry that this project's `dom` stories have a `UI` board, when it
5
+ * exists, and what to say when it does not.
6
+ *
7
+ * TRANSCRIBED from `design-time-mount.service.ts` beside it — the same
8
+ * package, the same point, a sibling registry. The board module is loaded
9
+ * eagerly rather than behind a dynamic `import()` (which that mount uses)
10
+ * because `presence()` is asked the moment the host reconciles, not when a
11
+ * candidate appears: a promise cannot answer a verdict thunk.
12
+ */
13
+
14
+ import { registerComponentBoard } from '@volter/editor-core/component-board-registry';
15
+ import { uiComponentBoard } from '../../src/react/ui-component-board';
16
+
17
+ export const point = 'workspace.service';
18
+
19
+ export function start(): () => void {
20
+ return registerComponentBoard(uiComponentBoard);
21
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * THE DOM MEDIUM'S DESIGN-TIME MOUNT (`@volter/editor-sdk/services`, a
3
+ * `workspace.service` contribution): `@volter/editor-game` tells the host's design-time
4
+ * mount registry how a `dom` world mounts at design time, and when its
5
+ * mounted layers go stale.
6
+ *
7
+ * It is a `workspace.service` for the same reason the React/DOM Inspector
8
+ * beside it is: a mount is not a document, so it never opens and never
9
+ * persists, and what a contribution pass owns is when its registration
10
+ * exists. Unlike that one the stop is REAL — the registration is the only
11
+ * thing standing between a `dom` root and the "no package registered a mount
12
+ * for this medium" disclosure, so a pass that ends must take it back.
13
+ *
14
+ * THE MOUNT IS LAZY, and that is transcribed rather than invented: the canvas
15
+ * medium's mount has been behind a dynamic `import()` since it was written
16
+ * (so `pixi.js`/`@pixi/react` enter a bundle only for a project that declares
17
+ * a canvas root), and the same argument holds here — the board's module
18
+ * reaches the story registry, Storybook's preview API, the React mount
19
+ * runtime and the React authoring adapter. A project with no `dom` root never
20
+ * loads any of it, and this contribution module stays a handful of lines in
21
+ * the build's eager graph.
22
+ */
23
+
24
+ import { registerDesignTimeMount } from '@volter/editor-core/authoring/design-time-mount-registry';
25
+
26
+ export const point = 'workspace.service';
27
+
28
+ export function start(): () => void {
29
+ return registerDesignTimeMount({
30
+ kind: 'dom',
31
+ owner: '@volter/editor-game/react/design-time-mount',
32
+ // The `ResolvedAdapter['identity']` a mount failure is reported under —
33
+ // the literal the host's own catch block used to spell.
34
+ identity: 'default-react',
35
+ // Deliberately absent: a dom layer's design-time hover/press testing of
36
+ // real UI competes with authoring gestures, so it stays behind the `eye`
37
+ // row's `interactive` toggle. The canvas medium declares the opposite.
38
+ mount: async (candidate, layer, context) => {
39
+ const { mountReactDesignLayer } = await import('../../src/react/design-time-react-mount');
40
+ return mountReactDesignLayer(candidate, layer, context);
41
+ },
42
+ reprojectWhen: (invalidate) => {
43
+ let stopped = false;
44
+ let stop: (() => void) | null = null;
45
+ // The hook's own module is the mount's, so binding it eagerly would
46
+ // load the board's whole graph for a project that has no dom root. The
47
+ // host only calls this when a dom candidate exists, so loading here is
48
+ // exactly the right moment.
49
+ void import('../../src/react/design-time-react-mount').then((module) => {
50
+ if (stopped) return;
51
+ stop = module.reprojectWhenStoriesRepublish(invalidate);
52
+ });
53
+ return () => {
54
+ stopped = true;
55
+ stop?.();
56
+ stop = null;
57
+ };
58
+ },
59
+ });
60
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * "MATERIALIZE PASTEBOARD FROM UI BOARD" in the command palette
3
+ * (`@volter/editor-sdk/chrome`, a `workspace.action` contribution) — the ONE
4
+ * action that turns the UI board's derived auto-grid into an authored
5
+ * pasteboard `.tsx`.
6
+ *
7
+ * It was a row in the host's own `action-registry.ts:404-423`, lazily
8
+ * importing `pasteboard-materialize.ts` — a host action about THIS package's
9
+ * design-time surface, which is the only thing that kept both files in the
10
+ * host (WORK.md §The open-source launch item 17). Shape transcribed from
11
+ * `@volter/editor-game/contributions/profiler.action.ts`: a static `actions` array, no
12
+ * subscribe, because this action's availability never changes — it either
13
+ * materializes or it refuses by name.
14
+ *
15
+ * THE OUTCOME LANDS IN THE EDITOR CONSOLE EITHER WAY, unchanged from the row
16
+ * it left: the success sentence names the file (which also opens), a refusal
17
+ * names its remedy.
18
+ */
19
+
20
+ import { editorConsole } from '@volter/editor-core/editor-console';
21
+ import type { ActionContribution } from '@volter/editor-sdk/chrome';
22
+
23
+ export const point = 'workspace.action';
24
+
25
+ export const actions: ActionContribution['actions'] = [
26
+ {
27
+ id: 'pasteboard.materialize',
28
+ label: 'Materialize Pasteboard from UI Board',
29
+ execute: async () => {
30
+ const { materializePasteboard } = await import('../../src/react/pasteboard-materialize');
31
+ try {
32
+ editorConsole.log(`[pasteboard] ${await materializePasteboard()}`, 'authoring');
33
+ } catch (error) {
34
+ editorConsole.error(
35
+ `[pasteboard] materialize refused: ${error instanceof Error ? error.message : String(error)}`,
36
+ 'authoring',
37
+ );
38
+ }
39
+ },
40
+ },
41
+ ];
@@ -0,0 +1,85 @@
1
+ /**
2
+ * THE REACT/DOM INSPECTOR (`@volter/editor-sdk/services`, a `workspace.service`
3
+ * contribution): the rich CSS/layout section that stands in for the shell's
4
+ * generic property grid on a node owned by a react or DOM adapter — borders,
5
+ * radii, shadows, gradients, filters, fonts, alignment, the breakpoint strip
6
+ * and the eyedropper, each descriptor mapped to its widget instead of a text
7
+ * field.
8
+ *
9
+ * ## Why this is a package and not the host's
10
+ *
11
+ * It was `packages/editor/src/authoring/react-inspector-section.tsx`,
12
+ * registered at module scope and reached by ONE line in the host's entry:
13
+ * `main.tsx:33`, `import './authoring/react-inspector-section';`. A bare
14
+ * side-effect import has no `from`, so the closure gate's walk never followed
15
+ * it — and behind it came the whole React/DOM design-time surface, 28 files
16
+ * and 14,274 lines, into every editor boot, a `models` build that authors no
17
+ * DOM included. The gate was corrected first (PR #7225) so this cut could be
18
+ * measured rather than asserted.
19
+ *
20
+ * ## Why its own package rather than `@volter/editor-game`
21
+ *
22
+ * Because `@volter/editor-game` already imports the surface this section drives from
23
+ * two unrelated lanes — `src/play/react-play-live-authoring.ts` and the
24
+ * ingest lane's DOM mount both reach `ReactRootAuthoringAdapter` — and the
25
+ * planned design skew (a Figma-shaped canvas) wants the same surface and is
26
+ * not a game. A surface a game editor and a design editor both author lives
27
+ * in neither.
28
+ *
29
+ * ## The seam, which is not a new one
30
+ *
31
+ * `@editor/inspector-section-registry` is the host's own registry and has been
32
+ * "the PERMANENT, sanctioned adapter-module seam" since it was written; the
33
+ * package REGISTERS, and the host names no lane. The service point is
34
+ * transcribed from `@volter/editor-game`'s `story-documents.service.ts`, which installs
35
+ * a story-scoped Inspector section the same way and for the same reason: a
36
+ * section is not a document, so it never opens and never persists, and what a
37
+ * contribution pass owns is when its registration exists.
38
+ *
39
+ * The `tool.inspector` point was CONSIDERED AND REJECTED with the reason here:
40
+ * it pins a contributed section to the wrench glyph at
41
+ * `CONTRIBUTED_SECTION_ORDER + declared`, while this section CLAIMS
42
+ * `PROPERTIES_SECTION_ID` and composes the node's own property GROUPS in
43
+ * `GROUP_SECTION_ORDER`. Routing it through that point would have moved the
44
+ * blocks below every contributed section and left the thin generic grid
45
+ * showing beside them.
46
+ *
47
+ * ## What has NOT moved, and why
48
+ *
49
+ * THE ADAPTERS THEMSELVES MOVED HERE on 2026-09-18 (phase 1 unit 12):
50
+ * `react-world-authoring-adapter.ts` and `dom-authoring-adapter.ts` are
51
+ * `@volter/editor-game/src/react/` files now. The blocker this docblock used to name —
52
+ * "`authoring/design-time-layers.ts` CONSTRUCTS the React adapter" — is gone,
53
+ * because the design-time mount is a REGISTRATION now
54
+ * (`@editor/authoring/design-time-mount-registry`), so both constructions
55
+ * moved into this package with `mountReactLayer` and the pair had no host
56
+ * importer left. `@volter/editor-game`'s play-live authoring and four modules of its
57
+ * ingest lane import them as `@volter/editor-game/react/…`; that package declares the
58
+ * dependency.
59
+ *
60
+ * Still host files, reached through `@editor/*` — the shape units 4 and 5
61
+ * landed — and each with the host importer that keeps it there:
62
+ * the DOM projection and the CSS inspection estate
63
+ * (`components/RootSelectionOverlay.tsx`); the surgical JSX writer and the
64
+ * structural write pipe (`authoring/r3f-source-authoring-adapter.ts`,
65
+ * `@vgai/canvas/pixi-source-write-target.ts`); the breakpoint state
66
+ * (`components/BoardRulers.tsx`); `authoring/react-story-board.ts`, the
67
+ * board's geometry, which four host surfaces read
68
+ * (`components/RootSelectionOverlay.tsx:98`,
69
+ * `components/ReactCanvasControls.tsx:15`, `pasteboard-materialize.ts:20`,
70
+ * `canvas-board/CanvasBoardDocument.tsx:57`) because they draw chrome OVER
71
+ * the board. Each is its own unit with its own seam; none of them is in the
72
+ * host's EAGER closure, which is what these cuts buy.
73
+ */
74
+
75
+ import { ensureReactInspectorSectionRegistered } from '../../src/react/react-inspector-section';
76
+
77
+ export const point = 'workspace.service';
78
+
79
+ export function start(): () => void {
80
+ ensureReactInspectorSectionRegistered();
81
+ // The stop is a no-op, and that is the same lifetime this had as a host
82
+ // module: its registration group is HMR-owned, and the section outlives any
83
+ // one contribution pass. Nothing here holds a resource a pass may end.
84
+ return () => {};
85
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * THE ISOLATED STORY DOCUMENT (`@volter/editor-sdk/services`, a
3
+ * `workspace.service` contribution): the center document a portable CSF story
4
+ * opens as on the DOM/canvas surface, with its `Story Args` Inspector section
5
+ * and its `Actions`/`Interactions`/`Accessibility` utilities, plus the
6
+ * Storybook Docs companion.
7
+ *
8
+ * The three-medium turntable is `@volter/editor-game`'s own contribution of the same
9
+ * name. They were one service while both documents lived in `@volter/editor-game`;
10
+ * splitting them is what lets a product compose one medium without the other,
11
+ * and neither registers anything the other needs.
12
+ *
13
+ * A SERVICE and not a `workspace.document`: this is not ONE tab the host opens
14
+ * by contribution id — it is a kind with an unbounded set of documents, one
15
+ * per (module, story), each carrying its own arg overrides and action log.
16
+ * What the contribution pass owns is when its registrations exist; the
17
+ * documents themselves open through their addresses
18
+ * (`@editor/document-open-registry` for the address,
19
+ * `@editor/workspace-document-restore` for the persisted state), so the kit
20
+ * names no module here.
21
+ *
22
+ * What stays the KIT's, deliberately: the story REGISTRY and discovery, the
23
+ * declared medium and the project's story regions, the arg descriptors and the
24
+ * Content grid's component thumbnail — portable CSF is the kit's design-time
25
+ * state (ARCHITECTURE-CORE §The target shape), read by the project adapter,
26
+ * the inspection projection, the coverage families and the Edit tab row.
27
+ */
28
+
29
+ import { ensureStoryContributionsRegistered } from '../../src/react/story-documents/story-documents';
30
+
31
+ export const point = 'workspace.service';
32
+
33
+ export function start(): () => void {
34
+ // The module registers its opener and restorer at module scope; this call
35
+ // installs the story-scoped Inspector section and utilities, which used to
36
+ // happen at editor boot. It is idempotent and returns nothing; the
37
+ // registration group it owns is HMR-safe by construction.
38
+ ensureStoryContributionsRegistered();
39
+ // The stop is a no-op, and that is the same lifetime this had as a host
40
+ // module: its registration group is HMR-owned, and an open story document
41
+ // outlives any one contribution pass. Nothing here holds a resource a pass
42
+ // may end.
43
+ return () => {};
44
+ }