@vgai/editor 0.2.0 → 0.4.0-canary.20260715.0

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 (430) hide show
  1. package/README.md +24 -0
  2. package/dist/assets/axe-C7YBtOuM.js +30 -0
  3. package/dist/assets/browserAll-DDtGGE48.js +1 -0
  4. package/dist/assets/create-runtime-DbTlBlKG.js +1 -0
  5. package/dist/assets/{game-BI111o5f.js → game-BE0r7DMI.js} +3 -3
  6. package/dist/assets/game-BdTHR2_X.js +1 -0
  7. package/dist/assets/{game-pt_1lQLv.js → game-C3iQGSqo.js} +1 -1
  8. package/dist/assets/game-CcHfYxjP.js +1 -0
  9. package/dist/assets/game-Cqzj_lpD.js +1 -0
  10. package/dist/assets/game-DSZdeCHu.js +1 -0
  11. package/dist/assets/game-Dch9sAPc.js +1 -0
  12. package/dist/assets/{game-xsOho_wg.js → game-SAFn90pA.js} +3 -3
  13. package/dist/assets/game-loop-DMIgSMV-.js +1 -0
  14. package/dist/assets/{game-source-CvidN7AB.js → game-source-Dk83w26t.js} +1 -1
  15. package/dist/assets/{index-Bp5cecD6.js → index-4idIsHHJ.js} +2 -2
  16. package/dist/assets/{index-DL7RcQ0n.js → index-C-LhOTBN.js} +1 -1
  17. package/dist/assets/{index-Cs8xwv3e.js → index-CDkxceqE.js} +1 -1
  18. package/dist/assets/index-CQEfi217.js +15412 -0
  19. package/dist/assets/index-Dvi-V0NE.css +1 -0
  20. package/dist/assets/ingest-siblings-s0P5I1Ub.js +1 -0
  21. package/dist/assets/mount-manifest-DXMOlF79.js +1 -0
  22. package/dist/assets/react-18-C9eHn8x1.js +1 -0
  23. package/dist/assets/webworkerAll-Fn0e-ouF.js +1 -0
  24. package/dist/assets/world-documents-GM0a9Kv7.js +1 -0
  25. package/dist/index.html +15 -13
  26. package/dist/ingest/racing-game/vgai.game.json +5 -4
  27. package/dist/ingest/simcity/vgai.game.json +5 -4
  28. package/dist/ingest/tanks/vgai.game.json +5 -4
  29. package/dist/project-scripts.bundle.js +3917 -0
  30. package/dist/scenes/untitled.vscn.json +2 -0
  31. package/dist-server/packaged.mjs +7697 -3435
  32. package/package.json +31 -4
  33. package/scripts/probe-reporter.ts +349 -0
  34. package/scripts/run-probe.ts +2260 -0
  35. package/server/asset-catalog-audit.ts +268 -0
  36. package/server/asset-catalog-v2.ts +146 -0
  37. package/server/asset-library-routes.ts +276 -58
  38. package/server/canonical-path.ts +1 -1
  39. package/server/dev.ts +148 -44
  40. package/server/editor-server.ts +888 -61
  41. package/server/editor-sse.ts +115 -4
  42. package/server/harness-chat-service.ts +796 -0
  43. package/server/local-asset-catalog.ts +157 -22
  44. package/server/packaged.ts +51 -21
  45. package/server/process-shutdown.ts +136 -0
  46. package/server/prod.ts +9 -6
  47. package/server/project-hmr-files.ts +41 -0
  48. package/server/project-operations.ts +343 -0
  49. package/server/project-optimize-deps-entries.ts +4 -4
  50. package/server/project-output-writer.ts +156 -0
  51. package/server/project-validation.ts +140 -0
  52. package/server/project-view.ts +8 -50
  53. package/server/server-utils.ts +75 -10
  54. package/server/session-registry.ts +12 -0
  55. package/src/EditorContext.tsx +107 -3
  56. package/src/action-registry.ts +115 -17
  57. package/src/adapter-resolver.ts +213 -478
  58. package/src/asset-events.ts +64 -1
  59. package/src/asset-preview.ts +906 -0
  60. package/src/asset-selection.ts +121 -0
  61. package/src/asset-workflow/asset-capabilities.ts +259 -0
  62. package/src/asset-workflow/asset-editor-errors.ts +46 -0
  63. package/src/asset-workflow/asset-import-pipeline.ts +176 -0
  64. package/src/asset-workflow/asset-library-query.ts +107 -0
  65. package/src/asset-workflow/asset-types.ts +192 -0
  66. package/src/asset-workflow/asset-workflow-quality.ts +65 -0
  67. package/src/asset-workflow/import-contract.ts +113 -0
  68. package/src/asset-workflow/model-conversion.ts +126 -0
  69. package/src/asset-workflow/model-inspection.ts +185 -0
  70. package/src/asset-workflow/project-asset-commands.ts +23 -0
  71. package/src/asset-workflow/project-asset-health.ts +153 -0
  72. package/src/asset-workflow/project-asset-operations.ts +119 -0
  73. package/src/asset-workflow/thumbnail-system.ts +172 -0
  74. package/src/authoring/active-systems.ts +15 -0
  75. package/src/authoring/boundary-authoring-adapter.ts +16 -5
  76. package/src/authoring/composite-authoring-adapter.ts +305 -124
  77. package/src/authoring/design-time-layers.ts +300 -117
  78. package/src/authoring/edit-mode-authoring.ts +278 -293
  79. package/src/authoring/embed-only-adapter.ts +4 -2
  80. package/src/authoring/first-party-hierarchy-menu.ts +7 -2
  81. package/src/authoring/first-party-inspector/AddSectionMenu.tsx +5 -12
  82. package/src/authoring/first-party-inspector/EnvironmentSection.tsx +53 -126
  83. package/src/authoring/first-party-inspector/HeaderSection.tsx +54 -65
  84. package/src/authoring/first-party-inspector/is-first-party-selection.ts +30 -11
  85. package/src/authoring/first-party-inspector/register.tsx +13 -2
  86. package/src/authoring/ingest-authoring-adapter.ts +142 -45
  87. package/src/authoring/mount-failure-report.ts +5 -5
  88. package/src/authoring/provenance.ts +41 -0
  89. package/src/authoring/react-dom-authoring-adapter.ts +92 -92
  90. package/src/authoring/react-inspector-section.tsx +489 -51
  91. package/src/authoring/react-world-authoring-adapter.ts +541 -306
  92. package/src/authoring/story-overlay.ts +1 -1
  93. package/src/authoring/story-types.ts +11 -0
  94. package/src/authoring/vgai-scene-authoring-adapter.ts +54 -25
  95. package/src/authoring/viewport-tool-context.ts +78 -0
  96. package/src/authoring/world-canvas-viewport-state.ts +35 -0
  97. package/src/authoring/world-hidden-viewport.ts +186 -0
  98. package/src/authoring/world-pan-state.ts +33 -17
  99. package/src/authoring/world-session-state.ts +1 -1
  100. package/src/authoring/world2d-authoring-adapter.ts +109 -52
  101. package/src/availability-tick.ts +56 -0
  102. package/src/browser-play-entry.ts +5 -5
  103. package/src/browser-project-scripts.ts +1 -0
  104. package/src/build-identity.ts +15 -0
  105. package/src/command-listener.ts +893 -19
  106. package/src/components/AlignToolbar.tsx +74 -63
  107. package/src/components/AppRoot.tsx +193 -26
  108. package/src/components/ApplicationMenus.tsx +272 -0
  109. package/src/components/AssetBrowser.tsx +1450 -293
  110. package/src/components/AssetEditorShell.tsx +141 -0
  111. package/src/components/AssetImportDetails.tsx +218 -0
  112. package/src/components/AssetLibraryPanel.tsx +14 -0
  113. package/src/components/CameraInfo.tsx +11 -20
  114. package/src/components/CenterDocuments.tsx +585 -0
  115. package/src/components/CommandPalette.tsx +75 -30
  116. package/src/components/ConsolePanel.css +55 -0
  117. package/src/components/ConsolePanel.tsx +57 -112
  118. package/src/components/DebugConsolePanel.tsx +461 -0
  119. package/src/components/DebuggerPanel.tsx +165 -0
  120. package/src/components/DefaultEditorLayout.tsx +167 -231
  121. package/src/components/GameHierarchy.tsx +873 -290
  122. package/src/components/GamePerformanceOverlay.tsx +46 -0
  123. package/src/components/HarnessChatPanel.tsx +2352 -0
  124. package/src/components/HistoryPanel.tsx +226 -0
  125. package/src/components/Inspector.tsx +153 -99
  126. package/src/components/InspectorToolSection.tsx +6 -4
  127. package/src/components/MountFailureBanner.tsx +31 -48
  128. package/src/components/NetworkSection.tsx +25 -14
  129. package/src/components/OnlineAssetBrowser.tsx +794 -343
  130. package/src/components/OverlayReportBanner.tsx +38 -64
  131. package/src/components/PerformancePanel.tsx +206 -0
  132. package/src/components/PlayBar.tsx +93 -263
  133. package/src/components/PrefabSaveDialog.tsx +55 -127
  134. package/src/components/ProjectHeader.css +60 -0
  135. package/src/components/ProjectHeader.tsx +170 -105
  136. package/src/components/ProjectScreen.tsx +246 -184
  137. package/src/components/ReactCanvasControls.tsx +185 -0
  138. package/src/components/ResolutionPicker.tsx +85 -0
  139. package/src/components/RootActivateDialog.tsx +39 -68
  140. package/src/components/SaveDialog.tsx +55 -127
  141. package/src/components/SaveStatus.tsx +11 -29
  142. package/src/components/StartupLoadingScreen.tsx +96 -0
  143. package/src/components/StateWatchPanel.tsx +302 -0
  144. package/src/components/StatsOverlay.tsx +12 -5
  145. package/src/components/StatusBar.tsx +64 -0
  146. package/src/components/ThemeManagerDialog.css +89 -0
  147. package/src/components/ThemeManagerDialog.tsx +371 -0
  148. package/src/components/ToolHost.tsx +26 -15
  149. package/src/components/Toolbar.tsx +117 -214
  150. package/src/components/VgaiVoxelLogo.tsx +89 -0
  151. package/src/components/ViewportOverlay.tsx +97 -172
  152. package/src/components/ViewportPanel.tsx +130 -31
  153. package/src/components/WorkspaceDock.tsx +891 -0
  154. package/src/components/WorldSelectionOverlay.tsx +51 -63
  155. package/src/components/WorldTextEditor.tsx +9 -5
  156. package/src/components/asset-documents.tsx +390 -0
  157. package/src/components/asset-editor-persistence.ts +44 -0
  158. package/src/components/asset-selection-section.tsx +338 -0
  159. package/src/components/asset-viewers/AudioViewer.tsx +73 -21
  160. package/src/components/asset-viewers/GenericJsonViewer.tsx +26 -11
  161. package/src/components/asset-viewers/ImageViewer.tsx +43 -11
  162. package/src/components/asset-viewers/InputMapViewer.tsx +512 -134
  163. package/src/components/asset-viewers/MaterialAssetEditor.tsx +540 -0
  164. package/src/components/asset-viewers/ModelViewer.tsx +805 -121
  165. package/src/components/asset-viewers/OnlineAssetDetail.tsx +177 -60
  166. package/src/components/asset-viewers/SourceAssetViewer.tsx +121 -0
  167. package/src/components/asset-workflow.css +506 -0
  168. package/src/components/build-documents.tsx +696 -0
  169. package/src/components/core-utilities.tsx +75 -0
  170. package/src/components/data-documents.tsx +364 -0
  171. package/src/components/{DataPanel.tsx → data-editor.tsx} +138 -288
  172. package/src/components/debugger-panel-state.ts +57 -0
  173. package/src/components/inspector-widgets/AlignmentGrid.tsx +8 -14
  174. package/src/components/inspector-widgets/BorderEditor.tsx +41 -33
  175. package/src/components/inspector-widgets/ColorPicker.tsx +39 -39
  176. package/src/components/inspector-widgets/FontPicker.tsx +13 -21
  177. package/src/components/inspector-widgets/GradientEditor.tsx +11 -19
  178. package/src/components/inspector-widgets/ScrubbableInput.tsx +6 -3
  179. package/src/components/inspector-widgets/ShadowEditor.tsx +20 -33
  180. package/src/components/inspector-widgets/shared.tsx +90 -49
  181. package/src/components/inspectors/AnimationSection.tsx +39 -40
  182. package/src/components/inspectors/AudioSection.tsx +28 -39
  183. package/src/components/inspectors/BoneAttachmentSection.tsx +19 -24
  184. package/src/components/inspectors/CameraSection.tsx +14 -23
  185. package/src/components/inspectors/ComponentsSection.tsx +39 -43
  186. package/src/components/inspectors/JointsSection.tsx +34 -43
  187. package/src/components/inspectors/LightSection.tsx +90 -35
  188. package/src/components/inspectors/MaterialSection.tsx +137 -174
  189. package/src/components/inspectors/MeshSection.tsx +9 -17
  190. package/src/components/inspectors/NavigationSection.tsx +8 -15
  191. package/src/components/inspectors/ParticleSection.tsx +91 -76
  192. package/src/components/inspectors/PhysicsSection.tsx +42 -46
  193. package/src/components/inspectors/PostProcessingSection.tsx +28 -36
  194. package/src/components/inspectors/RenderingSection.tsx +12 -20
  195. package/src/components/inspectors/ShadowSection.tsx +7 -8
  196. package/src/components/inspectors/SplineSection.tsx +31 -42
  197. package/src/components/inspectors/TransformSection.tsx +14 -22
  198. package/src/components/patterns/Dialog.tsx +95 -0
  199. package/src/components/patterns/Fields.tsx +44 -0
  200. package/src/components/patterns/List.tsx +25 -0
  201. package/src/components/patterns/StateSurface.tsx +40 -0
  202. package/src/components/patterns/Surfaces.tsx +115 -0
  203. package/src/components/patterns/Tabs.tsx +80 -0
  204. package/src/components/patterns/Toolbar.tsx +51 -0
  205. package/src/components/patterns/Tree.tsx +72 -0
  206. package/src/components/primitives/Button.tsx +46 -0
  207. package/src/components/primitives/ColorInput.tsx +11 -47
  208. package/src/components/primitives/EditorIcon.tsx +99 -0
  209. package/src/components/primitives/FormControls.tsx +71 -0
  210. package/src/components/primitives/JsonInput.tsx +15 -2
  211. package/src/components/primitives/Layout.tsx +100 -0
  212. package/src/components/primitives/Menu.tsx +105 -0
  213. package/src/components/primitives/NumberInput.tsx +8 -35
  214. package/src/components/primitives/Panel.tsx +49 -44
  215. package/src/components/primitives/SectionHeader.tsx +10 -28
  216. package/src/components/primitives/Text.tsx +46 -0
  217. package/src/components/primitives/Tooltip.tsx +20 -12
  218. package/src/components/primitives/Vec3Input.tsx +3 -0
  219. package/src/components/primitives/banner-tones.ts +33 -0
  220. package/src/components/primitives/editor-icons.ts +102 -0
  221. package/src/components/primitives/panel-header-styles.ts +30 -0
  222. package/src/components/project-operation-documents.tsx +226 -0
  223. package/src/components/status-contributions.tsx +175 -0
  224. package/src/components/story-documents-register.ts +15 -0
  225. package/src/components/story-documents.tsx +1185 -0
  226. package/src/components/tool-documents.tsx +110 -0
  227. package/src/components/workspace-dock.css +293 -0
  228. package/src/components/workspace-history.ts +33 -0
  229. package/src/components/world-documents.tsx +145 -0
  230. package/src/components/xstate-documents.tsx +209 -0
  231. package/src/composite-screenshot.ts +248 -0
  232. package/src/design-system/index.ts +70 -0
  233. package/src/design-system-stories/Foundation.stories.tsx +183 -0
  234. package/src/design-system-stories/Patterns.stories.tsx +251 -0
  235. package/src/design-system-stories/Workspace.stories.tsx +538 -0
  236. package/src/doctor/report.ts +57 -13
  237. package/src/editor-api.ts +295 -31
  238. package/src/editor-compatibility.ts +200 -0
  239. package/src/editor-console.ts +35 -17
  240. package/src/editor-hotkeys.ts +85 -33
  241. package/src/editor-store.ts +538 -233
  242. package/src/editor-viewport.ts +160 -34
  243. package/src/entity-factory.ts +14 -3
  244. package/src/gizmo-registry.ts +21 -5
  245. package/src/harness-chat-editor-context.ts +37 -0
  246. package/src/harness-chat-indicator.ts +68 -0
  247. package/src/harness-chat-layout.ts +53 -0
  248. package/src/harness-chat-lifecycle.ts +170 -0
  249. package/src/harness-chat-reconnect.ts +85 -0
  250. package/src/harness-chat-types.ts +409 -0
  251. package/src/hierarchy-drop.ts +50 -0
  252. package/src/hierarchy-expansion-state.ts +100 -0
  253. package/src/hierarchy-menu-registry.ts +1 -1
  254. package/src/hierarchy-projection.ts +76 -0
  255. package/src/history/editor-session.ts +21 -0
  256. package/src/history/history-commands.ts +147 -0
  257. package/src/history/history-service.ts +904 -0
  258. package/src/history/json-history-resource.ts +165 -0
  259. package/src/history/persistence-coordinator.ts +35 -0
  260. package/src/history/project-file-history.ts +266 -0
  261. package/src/history/project-root-history-backends.ts +53 -0
  262. package/src/history/resource-registry.ts +209 -0
  263. package/src/history/snapshot-store.ts +108 -0
  264. package/src/history/source-history-backend.ts +374 -0
  265. package/src/history/types.ts +100 -0
  266. package/src/hmr-registration-group.ts +67 -0
  267. package/src/hmr-stable-react-context.ts +23 -0
  268. package/src/hosted-example.ts +12 -2
  269. package/src/hotkeys.ts +89 -18
  270. package/src/ingest/discovery-react.ts +9 -10
  271. package/src/ingest/discovery.ts +4 -4
  272. package/src/ingest/discovery2d.ts +5 -5
  273. package/src/ingest/games/bloom-composer/vgai.game.json +3 -3
  274. package/src/ingest/games/embed-bundle/vgai.game.json +3 -3
  275. package/src/ingest/games/games-fps/vgai.game.json +3 -3
  276. package/src/ingest/games/iframe-reachable/vgai.game.json +3 -3
  277. package/src/ingest/games/interactive-cubes/vgai.game.json +3 -3
  278. package/src/ingest/games/mismatched-three/vgai.game.json +3 -3
  279. package/src/ingest/games/own-three-bundled/vgai.game.json +3 -3
  280. package/src/ingest/games/pointerlock-maze/vgai.game.json +7 -4
  281. package/src/ingest/games-2d/bubbo-bubbo/vgai.game.json +3 -3
  282. package/src/ingest/games-2d/bunnymark/vgai.game.json +3 -3
  283. package/src/ingest/games-2d/flappy/vgai.game.json +3 -3
  284. package/src/ingest/games-2d/flappy-pixi/vgai.game.json +3 -3
  285. package/src/ingest/games-2d/puzzling-potions/vgai.game.json +3 -3
  286. package/src/ingest/games-2d/tilemap-camera/vgai.game.json +3 -3
  287. package/src/ingest/games-react/react-rpg/vgai.game.json +3 -3
  288. package/src/ingest/types.ts +1 -1
  289. package/src/ingest-mode.ts +47 -52
  290. package/src/ingest-siblings.ts +23 -33
  291. package/src/initial-scene.ts +1 -2
  292. package/src/main.tsx +16 -8
  293. package/src/manifest-project.ts +10 -12
  294. package/src/model-thumbnail.ts +261 -26
  295. package/src/module-mode.ts +44 -50
  296. package/src/play-log-events.ts +26 -0
  297. package/src/play-mode.ts +495 -401
  298. package/src/play-scene-adoption.ts +23 -0
  299. package/src/playground/main.ts +68 -24
  300. package/src/playwright-shim.ts +473 -0
  301. package/src/project-manager.ts +90 -10
  302. package/src/project-operations.ts +112 -0
  303. package/src/project-story-discovery.ts +25 -0
  304. package/src/project-tool-discovery.ts +43 -0
  305. package/src/scene-serializer.ts +0 -6
  306. package/src/scene-sync.ts +7 -2
  307. package/src/script-loader.ts +3 -3
  308. package/src/skeleton-helper.ts +57 -0
  309. package/src/storage/seed.ts +68 -8
  310. package/src/stories/StoryPreviewMount.tsx +265 -0
  311. package/src/stories/action-stories.fixture.tsx +44 -0
  312. package/src/stories/button-stories.fixture.tsx +50 -0
  313. package/src/stories/compose-project-stories.ts +182 -0
  314. package/src/stories/loader-stories.fixture.tsx +53 -0
  315. package/src/stories/story-discovery.ts +130 -0
  316. package/src/stories/story-registry.ts +146 -0
  317. package/src/theme-css.ts +1049 -0
  318. package/src/theme-library.ts +238 -0
  319. package/src/theme-preference.ts +130 -0
  320. package/src/theme.ts +527 -0
  321. package/src/tool-loader.ts +80 -24
  322. package/src/ui-source/inspect.ts +27 -4
  323. package/src/ui-source/source-write-backend.ts +83 -22
  324. package/src/widgets/index.ts +9 -18
  325. package/src/workspace-aux-commands.ts +11 -0
  326. package/src/workspace-dock-controller.ts +64 -0
  327. package/src/workspace-document-ids.ts +5 -0
  328. package/src/workspace-document-registry.ts +367 -0
  329. package/src/workspace-state-persistence.ts +455 -0
  330. package/src/workspace-status-registry.ts +110 -0
  331. package/src/workspace-utility-commands.ts +49 -0
  332. package/src/workspace-utility-registry.ts +161 -0
  333. package/src/world-document-routing.ts +16 -0
  334. package/src/xstate-inspect/XStateMachineInspector.tsx +544 -0
  335. package/src/xstate-inspect/character-animation-machine.fixture.ts +74 -0
  336. package/src/xstate-inspect/use-live-actor-state.ts +39 -0
  337. package/src/xstate-inspect/xstate-graph.ts +265 -0
  338. package/src/xstate-inspect/xstate-layout.ts +76 -0
  339. package/src/z-index.ts +26 -0
  340. package/template/.claude/skills/editor/SKILL.md +129 -90
  341. package/template/AGENTS.md +1110 -0
  342. package/template/CLAUDE.md +1 -512
  343. package/template/IDIOMS.md +50 -0
  344. package/template/check-idioms.ts +706 -0
  345. package/template/manifest-entry-modules-plugin.ts +53 -0
  346. package/template/package.json +13 -4
  347. package/template/public/scenes/default.scn2d.json +6 -0
  348. package/template/public/scenes/default.vscn.json +7 -1
  349. package/template/public/scenes/untitled.vscn.json +10 -0
  350. package/template/scripts/playtest.ts +40 -0
  351. package/template/server/loopback.ts +75 -0
  352. package/template/server/rooms/arena-room.ts +69 -1
  353. package/template/server/rooms/game-room.ts +68 -1
  354. package/template/src/data/README.md +9 -4
  355. package/template/src/data/data-ref.ts +35 -0
  356. package/template/src/data/tuning.schema.ts +1 -1
  357. package/template/src/main.ts +41 -62
  358. package/template/src/manifest-entry-modules.d.ts +3 -0
  359. package/template/src/operations/README.md +88 -0
  360. package/template/src/runtime/pixi-adapter.ts +24 -0
  361. package/template/src/scripts/components/data-spinner.ts +43 -2
  362. package/template/src/scripts/components/spin-lap-math.ts +27 -0
  363. package/template/src/tools/README.md +5 -3
  364. package/template/src/tools/example.tool.tsx +13 -7
  365. package/template/src/ui/Button.stories.tsx +66 -0
  366. package/template/src/ui/RebindPanel.tsx +2 -6
  367. package/template/src/ui/game.tsx +17 -0
  368. package/template/tests/acceptance/example.spec.ts +64 -0
  369. package/template/tests/logic/example.test.ts +31 -0
  370. package/template/tests/logic/room.test.ts +100 -0
  371. package/template/tests/tsconfig.json +30 -0
  372. package/template/validate-manifest.ts +5 -5
  373. package/template/validate-scenes.ts +50 -18
  374. package/template/vgai.game.json +20 -13
  375. package/template/vite.config.ts +77 -16
  376. package/vite-plugin-ui-oid.ts +261 -17
  377. package/dist/assets/CCDIKSolver-Dz-ysxZF.js +0 -1
  378. package/dist/assets/SelectionBox-BXsv_6aH.js +0 -1
  379. package/dist/assets/browser-project-scripts-DOru4L1x.js +0 -2
  380. package/dist/assets/browserAll-CEkWYlFk.js +0 -1
  381. package/dist/assets/create-runtime-DYIWr3va.js +0 -1
  382. package/dist/assets/data-asset-Bb9sIM7i.js +0 -5
  383. package/dist/assets/game-8SDZPPgH.js +0 -1
  384. package/dist/assets/game-CevorxyV.js +0 -1
  385. package/dist/assets/game-Di7XlywD.js +0 -1
  386. package/dist/assets/game-DjnCaw8R.js +0 -1
  387. package/dist/assets/game-Q9A0aXSV.js +0 -1
  388. package/dist/assets/index-CPVV6pjw.js +0 -1
  389. package/dist/assets/index-CpOnYyeD.js +0 -213
  390. package/dist/assets/index-Dr8H_6Rl.js +0 -1
  391. package/dist/assets/index-_bCaipUk.js +0 -13650
  392. package/dist/assets/ingest-siblings-Dz8eEqrV.js +0 -1
  393. package/dist/assets/mount-manifest-Bhk0ohsl.js +0 -1
  394. package/dist/assets/scene-query-BivAHhJu.js +0 -1
  395. package/dist/assets/webworkerAll-BLrH0F6F.js +0 -1
  396. package/dist/assets/xstate-animation-binding-1NGLmw6y.js +0 -1
  397. package/dist/assets/xstate.esm-BgUD33nl.js +0 -3
  398. package/src/authoring/scene-ui-layer.ts +0 -485
  399. package/src/authoring/ui-authoring-adapter.ts +0 -325
  400. package/src/authoring/ui-hierarchy-menu.ts +0 -40
  401. package/src/authoring/ui-inspector-sections.tsx +0 -156
  402. package/src/authoring/undo-timeline.ts +0 -145
  403. package/src/components/BottomPanel.tsx +0 -326
  404. package/src/components/BuildPanel.tsx +0 -455
  405. package/src/components/FolderTree.tsx +0 -179
  406. package/src/components/RightPanel.tsx +0 -166
  407. package/src/components/WorldContextMenu.tsx +0 -257
  408. package/src/components/asset-viewers/AssetViewer.tsx +0 -38
  409. package/src/ingest/games-react/react-rpg/src/ui/game-state.tsx +0 -129
  410. package/src/scene-ui-mode/active-scene-ui.ts +0 -58
  411. package/src/scene-ui-mode/scene-ui-palette-panel.tsx +0 -152
  412. package/src/scene-ui-mode/scene-ui-timeline-tab.tsx +0 -29
  413. package/src/scene-ui-mode/ui-prefab-dialog-host.tsx +0 -74
  414. package/src/scene-ui-mode/ui-prefab-hierarchy-menu.ts +0 -49
  415. package/src/scene-ui-mode/ui-prefab-io.ts +0 -76
  416. package/src/ui-editor/inspector.tsx +0 -563
  417. package/src/ui-editor/node-ops.ts +0 -266
  418. package/src/ui-editor/overlay.tsx +0 -380
  419. package/src/ui-editor/palette.tsx +0 -114
  420. package/src/ui-editor/react-store.ts +0 -78
  421. package/src/ui-editor/timeline-panel.tsx +0 -443
  422. package/src/ui-editor/ui-edit-store.ts +0 -552
  423. package/src/ui-editor/ui-prefab.ts +0 -95
  424. package/template/project.json +0 -9
  425. package/template/src/scripts/main.ts +0 -94
  426. package/template/src/ui/DefaultHud.tsx +0 -40
  427. package/template/src/ui/game-state.tsx +0 -37
  428. package/template/src/ui/mount-default-hud.tsx +0 -19
  429. package/template/src/ui/mount-ui.ts +0 -15
  430. package/vite-plugin-react-world-provider.ts +0 -81
@@ -1,11 +1,9 @@
1
1
  /**
2
2
  * Design-time layer stack (B1, docs/unified-world-editor/26-unified-editor-spec.md
3
- * §3 B1 · D3/D9) — mounts a DESIGN-TIME layer for every non-focused react/pixi
4
- * world so edit mode composites the viewport exactly like the runtime does
5
- * (`@engine/runtime/create-runtime.ts`'s `createWorldsGameRuntime`, mirrored
6
- * below): a z-ordered DOM stack, the threejs canvas keeping its existing,
7
- * UNTOUCHED slot, each react/pixi world getting an absolutely-positioned
8
- * layer above it. Layers default `pointer-events:none`; the eye
3
+ * §3 B1 · D3/D9) — mounts one or more DESIGN-TIME React/Pixi worlds into a
4
+ * caller-owned authoring host. Runtime still uses a z-ordered composite, but
5
+ * the editor supplies a distinct host per world document; only Game presents
6
+ * the composed result. Layers default `pointer-events:none`; the eye
9
7
  * (`world-session-state.ts`'s `hidden`) hides a layer; `interactive` forwards
10
8
  * real pointer events for design-time hover/press testing; `pickLock` is
11
9
  * stored only (B4 consumes it in the pick walk). A layer that throws on
@@ -21,47 +19,74 @@
21
19
  * WHOLE S-B effort — display layer + (B4) stage hit-test only, no
22
20
  * hierarchy/stories — see `mountPixiLayer`'s doc comment.
23
21
  *
24
- * Only NON-focused `react`/`pixijs` children are candidates for a layer: the
25
- * focused threejs world already renders through `ViewportPanel`'s own
26
- * `<canvas>` (its existing slot, deliberately untouched here — a plain,
27
- * un-z-indexed, `position:static` element always paints below every
28
- * POSITIONED layer this module adds, "canvas keeps its slot / accept
29
- * canvas-bottom" per this task's own allowance). A non-focused THREEJS world
30
- * (there is no live scene to show it, and switching focus is out of B1's
31
- * scope — see `edit-mode-authoring.ts`'s `switchFocusedWorld` stub) simply
32
- * keeps its existing Boundary node, untouched by this module.
22
+ * Only `react`/`pixijs` children are candidates for these hosts: the focused
23
+ * threejs world renders through `ViewportPanel`'s own `<canvas>`. A non-focused THREEJS world
24
+ * (there is no live scene to show it — `switchFocusedWorld` in
25
+ * `edit-mode-authoring.ts` changes which world is focused, via a full
26
+ * rebuild, rather than mounting a second live one) simply keeps its existing
27
+ * Boundary node, untouched by this module.
33
28
  */
34
29
 
35
30
  import type { AuthoringAdapter, PickProvider } from '@engine/adapter';
36
31
  import { createAssetCache } from '@engine/assets';
37
32
  import { createGameLoop } from '@engine/core/game-loop';
38
- import type { ResolvedWorldEntry } from '@engine/manifest/load';
33
+ import type { ResolvedAdapterRoot } from '@engine/manifest/load';
39
34
  import { createGame, type Game } from '@engine/runtime/game';
40
35
  import { stackOrder } from '@engine/runtime/input-router';
41
36
  import { PixiSceneGameAdapter } from '@engine/world2d/pixi-game-adapter';
42
- import { createDataSource } from '@vgai/scene-ui/data-source';
43
- import { storiesFromRegistry, type UIStory } from '@vgai/scene-ui/registry';
44
- import { UIDataProvider } from '@vgai/scene-ui/ui-data-context';
45
37
  import { Component, type ReactNode } from 'react';
46
38
  import {
47
39
  fetchScene2DFile,
48
- loadProjectUIRegistry,
40
+ loadPixiComponentRegistryForProject,
49
41
  resolveGameProviderForProject,
50
- resolveReactWorldEntryComponent,
42
+ resolveReactAdapterRootComponent,
51
43
  resolveReactWorldMountRuntime,
52
44
  } from '../adapter-resolver';
53
45
  import { editorConsole } from '../editor-console';
54
46
  import type { EditorStore } from '../editor-store';
55
47
  import { getCurrentProject } from '../project-manager';
48
+ import { mountIsolatedStory } from '../stories/StoryPreviewMount';
49
+ import {
50
+ getDefaultRootPreviewStory,
51
+ getRootPreviewStories,
52
+ type RootPreviewStory,
53
+ refreshProjectStories,
54
+ } from '../stories/story-registry';
55
+
56
+ /** D20: Play tears the design-time adapter down, so the selected CSF state
57
+ * must live one level above that adapter to survive Stop's rebuild. Keyed by
58
+ * project + root so equal root ids in different projects never bleed. */
59
+ const selectedPortableStoryByRoot = new Map<string, string>();
60
+
61
+ function portableStorySelectionKey(projectRoot: string, rootId: string): string {
62
+ return `${projectRoot}\0${rootId}`;
63
+ }
64
+
65
+ function portableDocumentLabel(story: RootPreviewStory): string {
66
+ const authoredTitle = story.title?.split('/').filter(Boolean).at(-1)?.trim();
67
+ if (authoredTitle) return authoredTitle;
68
+ const file = story.modulePath.split('/').pop() ?? story.modulePath;
69
+ const stem = file.replace(/\.stories\.[^.]+$/i, '');
70
+ const words = stem
71
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
72
+ .replace(/[-_]+/g, ' ')
73
+ .trim();
74
+ return words || file;
75
+ }
76
+
56
77
  import { createHttpSourceWriteBackend } from '../ui-source/source-write-backend';
57
78
  import { BoundaryAuthoringAdapter, type BoundaryWorldInfo } from './boundary-authoring-adapter';
58
79
  import type { CompositeAuthoringAdapter } from './composite-authoring-adapter';
59
- import { registerEditModeChild } from './edit-mode-authoring';
80
+ import { EDIT_MODE_REBUILD_EVENT } from './edit-mode-authoring';
60
81
  import { addMountFailureReport, formatMountFailureMessage } from './mount-failure-report';
61
82
  import { ReactWorldAuthoringAdapter } from './react-world-authoring-adapter';
62
- import { mountSceneUILayer } from './scene-ui-layer';
63
83
  import { getStoryOverlay } from './story-overlay';
64
- import { UIAuthoringAdapter } from './ui-authoring-adapter';
84
+ import type { UIComponentRegistry, UIStory } from './story-types';
85
+ import {
86
+ getWorldCanvasViewport,
87
+ resetWorldCanvasViewport,
88
+ subscribeWorldCanvasViewport,
89
+ } from './world-canvas-viewport-state';
65
90
  import {
66
91
  getWorldPan,
67
92
  panTransformValue,
@@ -70,16 +95,16 @@ import {
70
95
  } from './world-pan-state';
71
96
  import { isWorldHidden, isWorldInteractive } from './world-session-state';
72
97
 
73
- /** Bottom-to-top layer z-index base — strictly below `PlayBar`'s z100/
74
- * `ToolStrip`+`ViewportOverlay`'s z1000, so gizmo/orbit-controls/marquee
98
+ /** Bottom-to-top layer z-index base — strictly below the selection overlay's
99
+ * z50 and `ToolStrip`+`ViewportOverlay`'s z1000, so canvas-local editing
75
100
  * chrome is never occluded by a design-time layer. */
76
101
  const BASE_Z_INDEX = 1;
77
102
 
78
103
  /** One candidate world for a design-time layer — read entirely off its
79
104
  * existing `BoundaryAuthoringAdapter` (kind/path/zOrder/pausable are already
80
- * exposed there, independent of whether the composite has a game-root
105
+ * exposed there, independent of whether the composite has a writable manifest
81
106
  * manifest surface — see that adapter's own inspector). */
82
- interface LayerCandidate {
107
+ export interface DesignTimeWorldDescriptor {
83
108
  readonly worldId: string;
84
109
  readonly kind: 'react' | 'pixijs';
85
110
  readonly path: string | undefined;
@@ -87,8 +112,8 @@ interface LayerCandidate {
87
112
  readonly pausable: boolean;
88
113
  }
89
114
 
90
- function readCandidates(composite: CompositeAuthoringAdapter): LayerCandidate[] {
91
- const out: LayerCandidate[] = [];
115
+ function readCandidates(composite: CompositeAuthoringAdapter): DesignTimeWorldDescriptor[] {
116
+ const out: DesignTimeWorldDescriptor[] = [];
92
117
  for (const { worldId, adapter } of composite.childAdapters()) {
93
118
  if (!(adapter instanceof BoundaryAuthoringAdapter)) continue; // the focused (live) world
94
119
  const kind = adapter.inspector.get(worldId, 'kind') as string;
@@ -106,19 +131,29 @@ function readCandidates(composite: CompositeAuthoringAdapter): LayerCandidate[]
106
131
  return out;
107
132
  }
108
133
 
134
+ /** Snapshot the non-Three roots that can own an edit-time document. Capture
135
+ * this before any root mounts: a successful React mount upgrades its
136
+ * Boundary adapter in-place, while the document still needs the manifest
137
+ * path and stacking metadata that Boundary disclosed. */
138
+ export function designTimeWorldDescriptors(
139
+ composite: CompositeAuthoringAdapter,
140
+ ): readonly DesignTimeWorldDescriptor[] {
141
+ return readCandidates(composite);
142
+ }
143
+
109
144
  /**
110
145
  * The inert design-time GameContext stand-in (B1's one genuine design
111
146
  * decision). A REAL `Game` — never a hand-typed duck-typed literal, mirroring
112
147
  * this repo's own test convention (`packages/editor/test/adapter-conformance-kit.ts`'s
113
148
  * `headlessGame()`) — built with a loop that is constructed but NEVER
114
- * started/ticked, and with NO worlds ever registered. This satisfies
149
+ * started/ticked, and with NO roots ever registered. This satisfies
115
150
  * `useGame`/`useGameState` (`@engine/react/game-state`, and any project's own
116
151
  * re-export of it) without throwing, genuinely inertly:
117
152
  * - `state.subscribe` never fires — nothing ever calls
118
153
  * `GameStateBridgeInternal.bump()` (only `GameInternal.runFrame` does, and
119
154
  * this Game's loop never runs a frame);
120
155
  * - `state.frameVersion` stays `0` forever;
121
- * - `queryByComponent`/`world()`/`worlds` are empty/`null` because `worlds`
156
+ * - `queryByComponent`/`world()`/`roots` are empty/`null` because `roots`
122
157
  * stays `[]` (nothing ever calls `registerWorld`).
123
158
  * `defaultWorld`/`components`/`input`/`audio` throw descriptively if touched
124
159
  * (same as any fresh `createGame()` before a world registers) — a HUD that
@@ -157,18 +192,18 @@ class DesignTimeErrorBoundary extends Component<
157
192
  }
158
193
 
159
194
  /**
160
- * A minimal `ResolvedWorldEntry` satisfying `resolveReactWorldEntryComponent`
195
+ * A minimal `ResolvedAdapterRoot` satisfying `resolveReactAdapterRootComponent`
161
196
  * (`../adapter-resolver.ts`, which reads only `id`/`kind`/`scene`/`entry` off
162
197
  * it) — the fields below it (`adapter`/`capabilities`/`loop`/`description`)
163
198
  * are unused by that function but required by the type; filled with the
164
199
  * honest "this is a native default-react world" values.
165
200
  */
166
- function buildResolvedWorldEntryStub(worldId: string, entry: string): ResolvedWorldEntry {
201
+ function buildResolvedAdapterRootStub(worldId: string, entry: string): ResolvedAdapterRoot {
167
202
  return {
168
203
  id: worldId,
169
- kind: 'react',
204
+ surface: 'react',
170
205
  description: undefined,
171
- adapter: { type: 'default', identity: 'default-react' },
206
+ adapter: { type: 'builtin', identity: 'react', surface: 'react' },
172
207
  scene: undefined,
173
208
  entry,
174
209
  zOrder: 0,
@@ -194,6 +229,14 @@ function createLayerElement(container: HTMLElement, worldId: string, zIndex: num
194
229
  layer.style.width = '100%';
195
230
  layer.style.height = '100%';
196
231
  layer.style.zIndex = String(zIndex);
232
+ // Same containment as the runtime's per-world surfaces (create-runtime.ts,
233
+ // world-surface cssText): make this layer the containing block for
234
+ // `position:fixed` descendants and clip to the world's rectangle, so a
235
+ // game's full-screen UI (`fixed; inset:0` — the natural idiom) renders
236
+ // inside the viewport instead of floating over the editor chrome. Also
237
+ // makes behavior pan-invariant: without it, space-pan's transform (below)
238
+ // suddenly captured `fixed` children that were page-anchored un-panned.
239
+ layer.style.contain = 'layout paint';
197
240
  applySessionStyle(layer, worldId);
198
241
  // D4 (spec27 §8 "space-pan" row) — seed this layer with whatever pan is
199
242
  // currently in effect (normally none — `mountDesignTimeLayers` resets pan
@@ -213,6 +256,10 @@ function createLayerElement(container: HTMLElement, worldId: string, zIndex: num
213
256
  * every subsequent pan change (see `world-pan-state.ts`). */
214
257
  function applyPanTransform(layer: HTMLElement): void {
215
258
  layer.style.transform = panTransformValue(getWorldPan()) ?? '';
259
+ layer.style.transformOrigin = '0 0';
260
+ const viewport = getWorldCanvasViewport();
261
+ layer.style.width = viewport.width === null ? '100%' : `${viewport.width}px`;
262
+ layer.style.height = viewport.height === null ? '100%' : `${viewport.height}px`;
216
263
  }
217
264
 
218
265
  interface LayerMountResult {
@@ -249,11 +296,9 @@ interface LayerRenderState {
249
296
 
250
297
  /**
251
298
  * React design-time mount (B1, story wiring B2 · D4, catalog B3) — the entry
252
- * via the EXISTING #31 loader (`resolveReactWorldEntryComponent`, reused
253
- * verbatim), wrapped in the project's own `GameProvider`
254
- * (`resolveGameProviderForProject` — reused verbatim so a project shipping
255
- * its own `src/ui/game-state.tsx` is wrapped by ITS OWN provider, never
256
- * split-context — see that function's doc comment) around the inert
299
+ * via the EXISTING #31 loader (`resolveReactAdapterRootComponent`, reused
300
+ * verbatim), wrapped in the canonical engine `GameProvider`
301
+ * (`resolveGameProviderForProject`) around the inert
257
302
  * `createDesignTimeGame()` stand-in (UNTOUCHED by B2/B3 — see that function's
258
303
  * own doc comment), inside a `UIDataProvider` (B2 — seeded from the active
259
304
  * story's `data`, or `{}` with none applied) and a React error boundary.
@@ -296,7 +341,7 @@ interface LayerRenderState {
296
341
  * reused.
297
342
  */
298
343
  async function mountReactLayer(
299
- candidate: LayerCandidate,
344
+ candidate: DesignTimeWorldDescriptor,
300
345
  layer: HTMLElement,
301
346
  projectRoot: string,
302
347
  store: EditorStore,
@@ -306,38 +351,133 @@ async function mountReactLayer(
306
351
  `design-time react layer "${candidate.worldId}": no \`entry\` declared — nothing to mount.`,
307
352
  );
308
353
  }
309
- const worldStub = buildResolvedWorldEntryStub(candidate.worldId, candidate.path);
310
- const Entry = await resolveReactWorldEntryComponent(worldStub, projectRoot);
311
- const GameProvider = await resolveGameProviderForProject(
312
- projectRoot,
313
- candidate.worldId,
314
- 'default-react',
354
+
355
+ // D20 — portable Storybook CSF is React's canonical design-time state.
356
+ // Discovery feeds the first-class hierarchy documents below this world; the
357
+ // viewport mounts the SAME composed portable story rather than translating
358
+ // it into a VGAI fixture or constructing a fake runtime Game.
359
+ await refreshProjectStories({ rootPath: projectRoot });
360
+ const portableStories = getRootPreviewStories(candidate.worldId);
361
+ const defaultPortableStory = getDefaultRootPreviewStory(candidate.worldId);
362
+ if (defaultPortableStory) {
363
+ const selectionKey = portableStorySelectionKey(projectRoot, candidate.worldId);
364
+ const rememberedStoryId = selectedPortableStoryByRoot.get(selectionKey);
365
+ const initialPortableStory =
366
+ portableStories.find((story) => story.id === rememberedStoryId) ?? defaultPortableStory;
367
+ let disposed = false;
368
+ let mounted: Awaited<ReturnType<typeof mountIsolatedStory>> = null;
369
+
370
+ const mountPortable = async (
371
+ story: RootPreviewStory,
372
+ args: Record<string, unknown> = story.args,
373
+ ): Promise<void> => {
374
+ const next = await mountIsolatedStory(
375
+ layer,
376
+ story.modulePath,
377
+ story.name,
378
+ story.Component,
379
+ args,
380
+ );
381
+ // A newer selection may claim the container while this story's loader
382
+ // is still in flight. `mountIsolatedStory` reports that supersession as
383
+ // null; never let the stale completion erase the newer mount handle,
384
+ // otherwise final teardown could no longer unmount the live React root.
385
+ if (!next) return;
386
+ if (disposed) {
387
+ next.unmount();
388
+ return;
389
+ }
390
+ mounted = next;
391
+ store.notifyIngestEdit();
392
+ };
393
+
394
+ await mountPortable(initialPortableStory);
395
+ if (!mounted) {
396
+ throw new Error(
397
+ `design-time react root "${candidate.worldId}": default CSF story ` +
398
+ `"${initialPortableStory.name}" was superseded before it mounted.`,
399
+ );
400
+ }
401
+
402
+ const byId = new Map(portableStories.map((story) => [story.id, story]));
403
+ const documentsByPath = new Map<string, RootPreviewStory[]>();
404
+ for (const story of portableStories) {
405
+ const siblings = documentsByPath.get(story.modulePath);
406
+ if (siblings) siblings.push(story);
407
+ else documentsByPath.set(story.modulePath, [story]);
408
+ }
409
+ const adapter = new ReactWorldAuthoringAdapter(layer, store, {
410
+ ...(import.meta.env.DEV ? { writeBackend: createHttpSourceWriteBackend() } : {}),
411
+ worldId: candidate.worldId,
412
+ portableStories: portableStories.map((story) => ({
413
+ id: story.id,
414
+ label: story.label,
415
+ args: story.args,
416
+ })),
417
+ portableDocuments: [...documentsByPath.entries()].map(([modulePath, stories]) => ({
418
+ id: `portable-document:${modulePath}`,
419
+ label: portableDocumentLabel(stories[0]!),
420
+ path: modulePath,
421
+ storyIds: stories.map((story) => story.id),
422
+ })),
423
+ activePortableStoryId: initialPortableStory.id,
424
+ onPortableStoryApplied: (storyId) => {
425
+ const story = (storyId ? byId.get(storyId) : null) ?? defaultPortableStory;
426
+ selectedPortableStoryByRoot.set(selectionKey, story.id);
427
+ void mountPortable(story).catch((error: unknown) => {
428
+ editorConsole.error(
429
+ `[design-time-layers] React root "${candidate.worldId}" failed applying portable ` +
430
+ `story "${story.name}": ${formatMountFailureMessage(error)}`,
431
+ 'authoring',
432
+ );
433
+ });
434
+ },
435
+ onPortableStoryArgsChanged: (storyId, args) => {
436
+ const story = byId.get(storyId);
437
+ if (!story) return;
438
+ void mountPortable(story, args).catch((error: unknown) => {
439
+ editorConsole.error(
440
+ `[design-time-layers] React root "${candidate.worldId}" failed updating portable ` +
441
+ `story "${story.name}" args: ${formatMountFailureMessage(error)}`,
442
+ 'authoring',
443
+ );
444
+ });
445
+ },
446
+ });
447
+
448
+ return {
449
+ adapter,
450
+ dispose: () => {
451
+ disposed = true;
452
+ adapter.disposeReactRootAdapter();
453
+ mounted?.unmount();
454
+ },
455
+ };
456
+ }
457
+
458
+ editorConsole.warn(
459
+ `[design-time-layers] React root "${candidate.worldId}" has no portable CSF preview. ` +
460
+ 'Add a *.stories.tsx module with parameters.vgai.root set to this root id. ' +
461
+ 'Falling back to the deprecated inert-Game preview for compatibility.',
462
+ 'authoring',
315
463
  );
464
+
465
+ const worldStub = buildResolvedAdapterRootStub(candidate.worldId, candidate.path);
466
+ const Entry = await resolveReactAdapterRootComponent(worldStub, projectRoot);
467
+ const GameProvider = await resolveGameProviderForProject(projectRoot, candidate.worldId, 'react');
316
468
  // C1 (phase-b/c1-react-mount): this layer's own react-world mount is a
317
469
  // SECOND call site with the identical packaged-runtime dual-React-instance
318
- // exposure `adapter-resolver.ts`'s `ReactWorldAdapter.mount()` has — under
470
+ // exposure `adapter-resolver.ts`'s `ReactRootAdapter.mount()` has — under
319
471
  // the packaged runtime, `createElement`/`createRoot`/`flushSync` must come
320
472
  // from the PROJECT's own react, not this module's (removed) static
321
473
  // imports. See `resolveReactWorldMountRuntime`'s doc comment.
322
474
  const { createElement, createRoot, flushSync } = await resolveReactWorldMountRuntime();
323
475
  const game = createDesignTimeGame();
324
476
 
325
- // D4 (B2) — story sources: the project's registry (states folded in as
326
- // stories) first, an editor-side overlay last (wins on an id collision).
327
- // `loadProjectUIRegistry` stays silent for the common no-registry case
328
- // (its own `hasUiRegistry` probe) — awaited HERE (not the async cache slot
329
- // `scene-ui-layer.ts`'s own registry load populates, which can still be
330
- // settling when this layer mounts, since the two mounts race).
331
- const registry = await loadProjectUIRegistry(projectRoot);
332
477
  const storySources: ReadonlyArray<Record<string, UIStory>> = [
333
- storiesFromRegistry(registry ?? {}),
334
478
  getStoryOverlay(candidate.worldId) ?? {},
335
479
  ];
336
- // B3 — the catalog gate: absent/empty `components` (the common project,
337
- // and every pre-B3 fixture) keeps `ReactWorldAuthoringAdapter`'s hierarchy
338
- // the plain DOM walk, byte-identical (§1's gate — "components NON-EMPTY
339
- // specifically").
340
- const catalogComponents = registry?.components;
480
+ const catalogComponents: UIComponentRegistry = {};
341
481
 
342
482
  const root = createRoot(layer);
343
483
  const renderState: LayerRenderState = { entryStory: null, isolation: null };
@@ -374,11 +514,7 @@ async function mountReactLayer(
374
514
  ? `story:${story.id}`
375
515
  : '__none__',
376
516
  onCaught,
377
- children: createElement(
378
- UIDataProvider,
379
- { data: createDataSource(story?.data ?? {}) },
380
- createElement(GameProvider, { game }, view),
381
- ),
517
+ children: createElement(GameProvider, { game }, view),
382
518
  }),
383
519
  );
384
520
  };
@@ -398,7 +534,6 @@ async function mountReactLayer(
398
534
  ...(import.meta.env.DEV ? { writeBackend: createHttpSourceWriteBackend() } : {}),
399
535
  storySources,
400
536
  worldId: candidate.worldId,
401
- catalogComponents,
402
537
  onStoryApplied: (story) => {
403
538
  renderState.entryStory = story;
404
539
  render((error: unknown) => {
@@ -427,7 +562,7 @@ async function mountReactLayer(
427
562
  // subscription before dropping its DOM, so listeners don't accumulate across
428
563
  // layer remounts (a leaked one is harmless — it early-returns on an empty echo —
429
564
  // but this keeps teardown clean).
430
- adapter.disposeReactWorldAdapter();
565
+ adapter.disposeReactRootAdapter();
431
566
  root.unmount();
432
567
  },
433
568
  };
@@ -464,23 +599,25 @@ function measure(container: HTMLElement): { width: number; height: number } {
464
599
  * hit-test only, no hierarchy/stories) — an explicit, out-of-scope-by-design
465
600
  * non-goal, not an oversight.
466
601
  *
467
- * B1 supports `scene`-declared pixi worlds only (the `.scn2d` display-only
602
+ * B1 supports `scene`-declared pixi roots only (the `.scn2d` display-only
468
603
  * case named in the spec); an `entry`-based (imperative-setup) pixi world has
469
604
  * no scene file to fetch and fails loudly here rather than being silently
470
605
  * skipped — degrading to the same #18 error node as any other mount failure.
471
606
  */
472
607
  async function mountPixiLayer(
473
- candidate: LayerCandidate,
608
+ candidate: DesignTimeWorldDescriptor,
474
609
  layer: HTMLElement,
475
610
  container: HTMLElement,
611
+ projectRoot: string,
476
612
  ): Promise<LayerMountResult> {
477
613
  if (!candidate.path) {
478
614
  throw new Error(
479
615
  `design-time pixi layer "${candidate.worldId}": no \`scene\` declared — nothing to mount ` +
480
- '(B1 display-only mount supports scene-based pixi worlds only, not `entry`).',
616
+ '(B1 display-only mount supports scene-based pixi roots only, not `entry`).',
481
617
  );
482
618
  }
483
619
  const sceneData = await fetchScene2DFile(candidate.path);
620
+ const registry = await loadPixiComponentRegistryForProject(projectRoot);
484
621
  const canvas = document.createElement('canvas');
485
622
  canvas.style.width = '100%';
486
623
  canvas.style.height = '100%';
@@ -488,7 +625,7 @@ async function mountPixiLayer(
488
625
  layer.appendChild(canvas);
489
626
 
490
627
  const { width, height } = measure(container);
491
- const adapter = new PixiSceneGameAdapter({ id: candidate.worldId, sceneData });
628
+ const adapter = new PixiSceneGameAdapter({ id: candidate.worldId, sceneData, registry });
492
629
  const mounted = await adapter.mount({ canvas, width, height, transparent: true });
493
630
  mounted.update?.(0); // one-shot render — see doc comment above
494
631
 
@@ -516,12 +653,11 @@ async function mountPixiLayer(
516
653
  }
517
654
 
518
655
  /**
519
- * Mount a design-time layer for every non-focused react/pixi world declared
520
- * by `composite` (already installed via `installEditModeAuthoringForProject`)
521
- * into `container` (`ViewportPanel`'s own containerRef div — the SAME element
522
- * the editor's threejs `<canvas>` lives in). Returns a disposer; call it from
523
- * the SAME effect cleanup that tears down the composite
524
- * (`exitEditModeAuthoring`) — see `ViewportPanel.tsx`'s wiring.
656
+ * Mount design-time React/Pixi worlds declared by `composite` into a caller's
657
+ * authoring `container`. `descriptors` narrows the mount to one world for the
658
+ * per-world document model; omission preserves the original multi-layer
659
+ * primitive for bounded callers and integration tests. Returns a disposer
660
+ * that must run before the owning composite is released.
525
661
  *
526
662
  * A no-op (returns a no-op disposer) when there is nothing to layer: a
527
663
  * single-world project (back-compat floor, matches `installEditModeAuthoring`'s
@@ -537,16 +673,26 @@ async function mountPixiLayer(
537
673
  * (whatever the entry's own JSX declares, e.g. tri-world's `hud-count`)
538
674
  * simultaneously in one document — not just visually confusing, but a
539
675
  * genuine test/automation hazard (two elements answering to one testid).
540
- * Design-time layers do not remount when play stops again — reload picks
541
- * them back up, exactly like any other edit-mode-only state (undo history,
542
- * panel layout) that a play session doesn't attempt to preserve. This never
543
- * touches the FOCUSED threejs world or the rest of the editor, which stay
544
- * fully functional regardless of play state.
676
+ * Stop symmetrically rebuilds edit-mode authoring and restores the selected
677
+ * portable CSF preview. Play and edit roots never coexist, but neither does
678
+ * Play permanently destroy the authoring surface.
545
679
  */
680
+ let rebuildEventQueued = false;
681
+
682
+ function queueEditModeRebuild(): void {
683
+ if (rebuildEventQueued) return;
684
+ rebuildEventQueued = true;
685
+ queueMicrotask(() => {
686
+ rebuildEventQueued = false;
687
+ window.dispatchEvent(new CustomEvent(EDIT_MODE_REBUILD_EVENT));
688
+ });
689
+ }
690
+
546
691
  export function mountDesignTimeLayers(
547
692
  container: HTMLElement,
548
693
  composite: CompositeAuthoringAdapter,
549
694
  store: EditorStore,
695
+ descriptors?: readonly DesignTimeWorldDescriptor[],
550
696
  ): () => void {
551
697
  // D4 (spec27 §8 "space-pan" row) — a fresh layer stack starts un-panned:
552
698
  // every call here is a "world switch" (initial edit-mode install, or an
@@ -554,24 +700,66 @@ export function mountDesignTimeLayers(
554
700
  // `installAll`), so a stale pan offset from a PREVIOUS layer stack must
555
701
  // never carry over onto a different one.
556
702
  resetWorldPan();
703
+ resetWorldCanvasViewport();
704
+ // ONE-WAY PLAY HANDOFF, ENTRY HALF (the fix for the CI-only
705
+ // `36-r3f-first-party.spec.ts` "roots: " hang — see below). The handoff
706
+ // documented in this module's doc comment was only ever implemented as an
707
+ // EXIT: the `store.subscribe` listener at the bottom of this function flips
708
+ // `playTeardownDone` and calls `suspendForPlay()` when play STARTS. That
709
+ // covers "layers were already mounted, then the user hit Play" — but it is
710
+ // a TRANSITION listener, so it does nothing at all when play is ALREADY
711
+ // running by the time this function is first called.
712
+ //
713
+ // That ordering is real, and it is exactly what CI hits. `ViewportPanel`'s
714
+ // `installAll()` (the only caller) runs at the tail of an async chain —
715
+ // `loadInitialScene()` -> `installEditModeAuthoringForProject()` (which
716
+ // itself fetches `vgai.game.json`) -> here. The e2e clicks Play the moment
717
+ // the editor shell is interactive, so on a slow/contended host (CI's
718
+ // 4-core SwiftShader runners) Play wins that race and `store.playState` is
719
+ // already `'playing'` when we arrive. Without this guard we then mount a
720
+ // design-time react layer anyway — rendering the project's REAL entry
721
+ // component (`examples/r3f-probe/src/hud.tsx`, `data-testid="hud-roots"`)
722
+ // against `createDesignTimeGame()`'s INERT `Game`, whose `roots` array is
723
+ // `[]` forever and whose loop never ticks (see that function's doc comment).
724
+ // The HUD therefore renders literally `"roots: "` — an empty list, from a
725
+ // Game that never registers a world — and, because no further playState
726
+ // TRANSITION ever arrives, `suspendForPlay()` never fires and the layer is
727
+ // never torn down. That is precisely the reported CI symptom ("the HUD
728
+ // element IS mounted; the roots list is EMPTY", for the full 15s budget),
729
+ // and it also carries the duplicate-`data-testid` hazard this module's own
730
+ // doc comment already calls out for the transition case.
731
+ //
732
+ // Play mode owns the react world outright while it runs (it mounts its own
733
+ // live tree over a REAL, ticking Game). So the correct behavior when play
734
+ // is already up is the same one `suspendForPlay()` produces: mount NOTHING.
735
+ // Stop requests a clean edit-mode rebuild below; this entry guard therefore
736
+ // needs to listen for Stop even though it mounted no layers itself.
737
+ if (store.playState !== 'stopped') {
738
+ let disposed = false;
739
+ let rebuildRequested = false;
740
+ const unsubscribe = store.subscribe(() => {
741
+ if (disposed || rebuildRequested || store.playState !== 'stopped') return;
742
+ rebuildRequested = true;
743
+ if (!disposed) queueEditModeRebuild();
744
+ });
745
+ return () => {
746
+ disposed = true;
747
+ unsubscribe();
748
+ };
749
+ }
557
750
  const projectRoot = getCurrentProject()?.rootPath;
558
- const candidates = readCandidates(composite);
751
+ const candidates = descriptors ? [...descriptors] : readCandidates(composite);
559
752
  // C3 — the scene-UI child (at most one, the focused world's): already a
560
753
  // LIVE `UIAuthoringAdapter` at composite-install time (`edit-mode-
561
754
  // authoring.ts` — no Boundary-to-live upgrade dance needed here, unlike
562
755
  // react/pixi), so it's a mount candidate in its own right rather than one
563
756
  // of `readCandidates`'s Boundary-only entries.
564
- const uiChildEntry = composite.childAdapters().find((c) => c.kind === 'scene-ui');
565
- const uiAdapter =
566
- uiChildEntry && uiChildEntry.adapter instanceof UIAuthoringAdapter
567
- ? uiChildEntry.adapter
568
- : null;
569
- if ((candidates.length === 0 && !uiAdapter) || !projectRoot) {
757
+ if (candidates.length === 0 || !projectRoot) {
570
758
  return () => {};
571
759
  }
572
760
 
573
761
  // Bottom-to-top paint order — mirrors `create-runtime.ts`'s own stacking
574
- // pass, over exactly the worlds this module lays out (react/pixi
762
+ // pass, over exactly the roots this module lays out (react/pixi
575
763
  // candidates only; the focused threejs world keeps its untouched canvas
576
764
  // slot below all of these — see the module doc comment).
577
765
  const sorted = stackOrder(candidates.map((c) => ({ id: c.worldId, zOrder: c.zOrder })));
@@ -587,21 +775,7 @@ export function mountDesignTimeLayers(
587
775
  const upgraded = new Set<string>();
588
776
  let torndown = false; // the whole-module disposer (ViewportPanel unmount) ran
589
777
  let playTeardownDone = false; // the one-way play-mode handoff (see doc comment) ran
590
-
591
- // C3 — the scene-UI layer's own disposer, tracked separately from
592
- // `disposers`/`layers` (react/pixi's Boundary-upgrade bookkeeping doesn't
593
- // apply to it — the UI child's adapter stays live throughout; only its DOM
594
- // layer needs tearing down, here and on the play handoff below).
595
- let disposeUILayer: (() => void) | undefined;
596
- if (uiAdapter && uiChildEntry) {
597
- disposeUILayer = mountSceneUILayer(
598
- container,
599
- uiChildEntry.worldId,
600
- uiAdapter,
601
- store,
602
- BASE_Z_INDEX + orderedCandidates.length,
603
- );
604
- }
778
+ let rebuildRequested = false;
605
779
 
606
780
  function disposeEverything(): void {
607
781
  for (const dispose of disposers.splice(0)) {
@@ -616,22 +790,20 @@ export function mountDesignTimeLayers(
616
790
  }
617
791
 
618
792
  /**
619
- * One-way play handoff: dispose the design-time layers AND revert every
793
+ * Play handoff: dispose the design-time layers AND revert every
620
794
  * upgraded world back to a `BoundaryAuthoringAdapter` disclosure node
621
795
  * (#18 — never silently empty). Not called on the whole-module disposer
622
796
  * (`torndown`, ViewportPanel unmount), where the composite is going away
623
- * anyway. Design-time layers do not remount when play stops again — reload
624
- * restores them (see the module doc comment). The scene-UI layer's DOM is
797
+ * anyway. Stop dispatches one edit-mode rebuild after the live runtime has
798
+ * torn down, restoring the CSF-backed design surface. The scene-UI layer's DOM is
625
799
  * torn down the same way (play mode renders scene-UI through the real
626
800
  * engine's HUD mount instead — two live DOM trees for the same testids
627
801
  * would be the same hazard `design-time-layers.ts`'s own doc comment
628
- * already calls out for react worlds); its adapter is left alone (still
802
+ * already calls out for react roots); its adapter is left alone (still
629
803
  * live, just without a mounted layer) since there is no Boundary form of it.
630
804
  */
631
805
  function suspendForPlay(): void {
632
806
  disposeEverything();
633
- disposeUILayer?.();
634
- disposeUILayer = undefined;
635
807
  for (const worldId of upgraded) {
636
808
  const c = candidateByWorld.get(worldId);
637
809
  if (!c) continue;
@@ -647,7 +819,7 @@ export function mountDesignTimeLayers(
647
819
  new BoundaryAuthoringAdapter(
648
820
  store,
649
821
  info,
650
- 'Design-time layer suspended by play mode — reload the editor to restore it.',
822
+ 'Design-time layer suspended by play mode — Stop restores it.',
651
823
  ),
652
824
  );
653
825
  }
@@ -662,7 +834,7 @@ export function mountDesignTimeLayers(
662
834
  const mountPromise: Promise<LayerMountResult> =
663
835
  candidate.kind === 'react'
664
836
  ? mountReactLayer(candidate, layer, projectRoot, store)
665
- : mountPixiLayer(candidate, layer, container);
837
+ : mountPixiLayer(candidate, layer, container, projectRoot);
666
838
 
667
839
  mountPromise
668
840
  .then((result) => {
@@ -672,7 +844,6 @@ export function mountDesignTimeLayers(
672
844
  }
673
845
  if (result.adapter) {
674
846
  composite.replaceChild(candidate.worldId, result.adapter);
675
- registerEditModeChild(result.adapter);
676
847
  upgraded.add(candidate.worldId);
677
848
  } else if (result.pick) {
678
849
  // D12 (B4) — a pixi layer produces a `pick`, not a full adapter
@@ -738,6 +909,9 @@ export function mountDesignTimeLayers(
738
909
  const unsubPan = subscribeWorldPan(() => {
739
910
  for (const layer of layers.values()) applyPanTransform(layer);
740
911
  });
912
+ const unsubViewport = subscribeWorldCanvasViewport(() => {
913
+ for (const layer of layers.values()) applyPanTransform(layer);
914
+ });
741
915
 
742
916
  const unsubStore = store.subscribe(() => {
743
917
  if (!playTeardownDone && store.playState !== 'stopped') {
@@ -745,6 +919,15 @@ export function mountDesignTimeLayers(
745
919
  suspendForPlay();
746
920
  return;
747
921
  }
922
+ if (playTeardownDone && !rebuildRequested && store.playState === 'stopped' && !torndown) {
923
+ rebuildRequested = true;
924
+ // Reuse ViewportPanel's one authoritative reinstall path. The event
925
+ // causes this mount's disposer to run before the replacement composite
926
+ // and layers are installed, so no React root or adapter survives across
927
+ // the Play -> Stop boundary.
928
+ if (!torndown) queueEditModeRebuild();
929
+ return;
930
+ }
748
931
  // Session toggles (eye/interactive) change via `store.notifyIngestEdit()`
749
932
  // (`GameHierarchy.tsx`'s row affordances) — this is a cheap style
750
933
  // re-apply, never a remount.
@@ -770,7 +953,7 @@ export function mountDesignTimeLayers(
770
953
  torndown = true;
771
954
  unsubStore();
772
955
  unsubPan();
956
+ unsubViewport();
773
957
  disposeEverything();
774
- disposeUILayer?.();
775
958
  };
776
959
  }