@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
@@ -0,0 +1,585 @@
1
+ import { Button, Menu, MenuItem, themeVars } from '../design-system';
2
+ /**
3
+ * W1 (docs/EDITOR-WORKSPACE-SHELL-TODO.md §9/W1) — the first two workspace
4
+ * DOCUMENT implementers (§7.1) and the bridge that keeps the document
5
+ * registry and the editor store's `activeViewportTab` agreeing:
6
+ *
7
+ * - the SCENE document (`workspace:scene`): the active Three scene, titled
8
+ * with its source-owned name (§8 — `Default.vscn`, never `Viewport`),
9
+ * non-closeable (there is always a center document), content =
10
+ * `ViewportPanel` (which keeps owning the renderer/RAF/hotkeys — its
11
+ * overlays ToolStrip/ViewportOverlay/CameraInfo/WorldSelectionOverlay
12
+ * move WITH it into the document, per inventory rows V5–V7/V11);
13
+ * - the GAME document (`workspace:game`): the live player-facing composite,
14
+ * titled `Game` (§8), pinned, always open, and always the leftmost anchor.
15
+ * While stopped it shows an explicit empty state rather than stale runtime
16
+ * pixels. Dockview renders it with
17
+ * `renderer: "always"`, so its canvas container exists before Play's
18
+ * asynchronous setup and survives tab/group changes.
19
+ *
20
+ * THE INPUT-GATE CONTRACT (CLAUDE.md T6.3): `store.activeViewportTab`
21
+ * remains the single source of truth that `play-mode.ts`/`ingest-mode.ts`
22
+ * read (`playState === 'playing' && activeViewportTab === 'game'`) — this
23
+ * module NEVER replaces that predicate, it mirrors registry activation into
24
+ * it (descriptor `onActivate` → `setActiveViewportTab`) and store changes
25
+ * back into registry activation (`syncCenterDocuments`, run on every store
26
+ * notify). Play auto-switch (enter play → Game tab, stop → Scene tab) stays
27
+ * store-driven (`setPlayState`), so `vgai select-tab`-style control commands
28
+ * (`command-listener.ts`'s `set-viewport-tab`) and every ingest/module-mode
29
+ * `setActiveViewportTab` call keep working unchanged.
30
+ *
31
+ * Lifecycle: `enterPlayMode` requires the game container to exist BEFORE
32
+ * play starts (`setGameContainer`). The pinned document plus Dockview's
33
+ * always-rendered panel mode is the one continuous React instance that
34
+ * satisfies that requirement.
35
+ */
36
+
37
+ import {
38
+ Component,
39
+ type ErrorInfo,
40
+ type ReactNode,
41
+ useCallback,
42
+ useEffect,
43
+ useRef,
44
+ useState,
45
+ useSyncExternalStore,
46
+ } from 'react';
47
+ import { DisclosureIcon } from '../design-system';
48
+ import { useEditorStore } from '../EditorContext';
49
+ import { editorConsole } from '../editor-console';
50
+ import { getGameProfiler, resizeGame, setGameContainer } from '../play-mode';
51
+ import { GAME_DOCUMENT_ID, SCENE_DOCUMENT_ID } from '../workspace-document-ids';
52
+ import {
53
+ activateWorkspaceDocument,
54
+ activeWorkspaceDocumentId,
55
+ closeAllWorkspaceDocuments,
56
+ openWorkspaceDocument,
57
+ openWorkspaceDocuments,
58
+ setWorkspaceDocumentDirty,
59
+ setWorkspaceDocumentTitle,
60
+ type WorkspaceDocumentContentProps,
61
+ type WorkspaceDocumentDescriptor,
62
+ } from '../workspace-document-registry';
63
+ import { openDebuggerPanel } from './debugger-panel-state';
64
+ import { GamePerformanceOverlay } from './GamePerformanceOverlay';
65
+ import { MuteButton, type Resolution, ResolutionPicker } from './PlayBar';
66
+ import { ViewportPanel } from './ViewportPanel';
67
+
68
+ /** Stable identity (§7.1) of the single active-scene center document. The
69
+ * SLOT is stable while the scene it shows can change (root activation,
70
+ * prefab edit) — the tab's title tracks `store.savePath` live via
71
+ * `setWorkspaceDocumentTitle`, keeping "there is always a center document"
72
+ * true without close/reopen churn. */
73
+ export { GAME_DOCUMENT_ID, SCENE_DOCUMENT_ID } from '../workspace-document-ids';
74
+ /** Stable identity (§7.1) of the Game preview document. */
75
+
76
+ /** §8 source-owned tab title for the active scene document: the file's own
77
+ * basename with the serialization suffix dropped (`scenes/Default.vscn.json`
78
+ * → `Default.vscn`, `x/Crate.prefab.json` → `Crate.prefab`) — matching the
79
+ * TODO's §4.1 mock. `Untitled` only when no source file exists yet. */
80
+ export function sceneDocumentTitle(savePath: string | null): string {
81
+ if (!savePath) return 'Untitled';
82
+ const base = savePath.split('/').pop() ?? savePath;
83
+ return base.endsWith('.json') ? base.slice(0, -'.json'.length) : base;
84
+ }
85
+
86
+ /** The narrow store surface this module reads/writes — kept minimal so the
87
+ * bridge is unit-testable against a plain fake (no full EditorStore). */
88
+ export interface CenterDocumentsStore {
89
+ subscribe(listener: () => void): () => void;
90
+ readonly playState: 'stopped' | 'playing' | 'paused';
91
+ readonly activeViewportTab: 'scene' | 'game';
92
+ readonly savePath: string | null;
93
+ readonly isDirty: boolean;
94
+ setActiveViewportTab(tab: 'scene' | 'game'): void;
95
+ }
96
+
97
+ interface ViewportErrorBoundaryState {
98
+ error: Error | null;
99
+ }
100
+
101
+ /** A renderer failure must degrade the Scene document, not unmount the whole
102
+ * Dockview workspace. The surrounding hierarchy, Inspector, Console, menus,
103
+ * and project resources remain usable, with an explicit retry after the old
104
+ * renderer/context has been torn down. */
105
+ export class ViewportErrorBoundary extends Component<
106
+ { children: ReactNode },
107
+ ViewportErrorBoundaryState
108
+ > {
109
+ override state: ViewportErrorBoundaryState = { error: null };
110
+
111
+ static getDerivedStateFromError(error: Error): ViewportErrorBoundaryState {
112
+ return { error };
113
+ }
114
+
115
+ override componentDidCatch(error: Error, info: ErrorInfo): void {
116
+ editorConsole.error(`Viewport failed to start: ${error.message}`, 'editor');
117
+ // Keep the component stack in the browser console for developer diagnosis.
118
+ // biome-ignore lint/suspicious/noConsole: renderer startup failures need their React component stack in developer tools as well as the user-facing editor Console entry
119
+ console.error('[vgai] Viewport failed to start', error, info.componentStack);
120
+ }
121
+
122
+ override render() {
123
+ if (!this.state.error) return this.props.children;
124
+ return (
125
+ <div
126
+ data-testid="viewport-startup-error"
127
+ role="alert"
128
+ style={{
129
+ position: 'absolute',
130
+ inset: 0,
131
+ display: 'flex',
132
+ flexDirection: 'column',
133
+ alignItems: 'center',
134
+ justifyContent: 'center',
135
+ gap: 8,
136
+ padding: 24,
137
+ background: themeVars.surface.shell,
138
+ color: themeVars.content.muted,
139
+ textAlign: 'center',
140
+ }}
141
+ >
142
+ <div style={{ color: themeVars.content.primary, fontSize: 14 }}>Viewport unavailable</div>
143
+ <div style={{ maxWidth: 520, color: themeVars.content.dim, fontSize: 11 }}>
144
+ {this.state.error.message}
145
+ </div>
146
+ <Button
147
+ type="button"
148
+ className="vgai-btn"
149
+ data-testid="retry-viewport"
150
+ onClick={() => this.setState({ error: null })}
151
+ >
152
+ Retry Viewport
153
+ </Button>
154
+ </div>
155
+ );
156
+ }
157
+ }
158
+
159
+ function SceneDocumentContent(_props: WorkspaceDocumentContentProps) {
160
+ return (
161
+ <ViewportErrorBoundary>
162
+ <ViewportPanel />
163
+ </ViewportErrorBoundary>
164
+ );
165
+ }
166
+
167
+ // --- Game document view state (W2, inventory row V4) -----------------------
168
+ // Resolution/scale are shared between the Game document's CONTENT (which
169
+ // owns the canvas resize/scale math) and its document-local TOOLBAR (which
170
+ // hosts the picker + mute, §6.3 "Game mute/resolution controls appear only
171
+ // in the Game document") — a tiny module-scope store in the house
172
+ // `useSyncExternalStore` shape, session-scoped like the old GamePanel state.
173
+
174
+ let _gameResolution: Resolution = { label: 'Fill', width: null, height: null };
175
+ let _gameScale = 1;
176
+ let _gameStatsVisible = false;
177
+ let _gameViewVersion = 0;
178
+ const _gameViewListeners = new Set<() => void>();
179
+
180
+ function notifyGameView(): void {
181
+ _gameViewVersion++;
182
+ for (const fn of _gameViewListeners) fn();
183
+ }
184
+
185
+ function subscribeGameView(fn: () => void): () => void {
186
+ _gameViewListeners.add(fn);
187
+ return () => {
188
+ _gameViewListeners.delete(fn);
189
+ };
190
+ }
191
+
192
+ const gameViewVersion = () => _gameViewVersion;
193
+
194
+ /** The Game document's current resolution selection (exported for tests). */
195
+ export function gameDocumentResolution(): Resolution {
196
+ return _gameResolution;
197
+ }
198
+
199
+ /** Set the Game document's resolution (exported for tests). */
200
+ export function setGameDocumentResolution(resolution: Resolution): void {
201
+ _gameResolution = resolution;
202
+ notifyGameView();
203
+ }
204
+
205
+ export function gameStatsVisible(): boolean {
206
+ return _gameStatsVisible;
207
+ }
208
+
209
+ export function setGameStatsVisible(visible: boolean): void {
210
+ if (_gameStatsVisible === visible) return;
211
+ _gameStatsVisible = visible;
212
+ if (visible) {
213
+ const profiler = getGameProfiler();
214
+ if (profiler) profiler.enabled = true;
215
+ }
216
+ notifyGameView();
217
+ }
218
+
219
+ function setGameScale(scale: number): void {
220
+ if (scale === _gameScale) return;
221
+ _gameScale = scale;
222
+ notifyGameView();
223
+ }
224
+
225
+ function GameStatsMenu() {
226
+ const [open, setOpen] = useState(false);
227
+ const ref = useRef<HTMLDivElement>(null);
228
+ useEffect(() => {
229
+ if (!open) return;
230
+ const onPointerDown = (event: PointerEvent) => {
231
+ if (!ref.current?.contains(event.target as Node)) setOpen(false);
232
+ };
233
+ document.addEventListener('pointerdown', onPointerDown);
234
+ return () => document.removeEventListener('pointerdown', onPointerDown);
235
+ }, [open]);
236
+ return (
237
+ <div ref={ref} style={{ position: 'relative' }}>
238
+ <Button
239
+ type="button"
240
+ variant="outline"
241
+ size="compact"
242
+ data-testid="game-stats-menu"
243
+ aria-expanded={open}
244
+ onClick={() => setOpen((value) => !value)}
245
+ >
246
+ <span>Stats</span>
247
+ <DisclosureIcon direction="down" />
248
+ </Button>
249
+ {open && (
250
+ <Menu
251
+ style={{
252
+ position: 'absolute',
253
+ right: 0,
254
+ top: 'calc(100% + 3px)',
255
+ zIndex: 20,
256
+ minWidth: 190,
257
+ }}
258
+ >
259
+ <MenuItem onClick={() => setGameStatsVisible(!_gameStatsVisible)}>
260
+ {_gameStatsVisible ? '✓ ' : ' '}Show live overlay
261
+ </MenuItem>
262
+ <MenuItem
263
+ onClick={() => {
264
+ const profiler = getGameProfiler();
265
+ if (profiler) {
266
+ const recording = profiler.getSnapshot().recording;
267
+ recording ? profiler.stopRecording() : profiler.startRecording();
268
+ notifyGameView();
269
+ }
270
+ }}
271
+ >
272
+ {getGameProfiler()?.getSnapshot().recording ? 'Stop capture' : 'Start capture'}
273
+ </MenuItem>
274
+ <MenuItem onClick={() => openDebuggerPanel('profiler')}>Open Profiler</MenuItem>
275
+ <MenuItem onClick={() => openDebuggerPanel('network')}>Open Network Diagnostics</MenuItem>
276
+ </Menu>
277
+ )}
278
+ </div>
279
+ );
280
+ }
281
+
282
+ /**
283
+ * The Game document's LOCAL toolbar (§7.1 toolbar contribution, rendered by
284
+ * `WorkspaceDock` inside the Game document only while that panel is
285
+ * active): resolution picker + mute — the V4 remainder deferred from W1.
286
+ * Transport (play/pause/stop) deliberately stays in the global `PlayBar`.
287
+ */
288
+ export function GameDocumentToolbar(_props: WorkspaceDocumentContentProps) {
289
+ useSyncExternalStore(subscribeGameView, gameViewVersion);
290
+ return (
291
+ <div
292
+ data-testid="game-doc-toolbar"
293
+ style={{
294
+ display: 'flex',
295
+ alignItems: 'center',
296
+ justifyContent: 'flex-end',
297
+ gap: 6,
298
+ padding: '2px 8px',
299
+ }}
300
+ >
301
+ <ResolutionPicker
302
+ resolution={_gameResolution}
303
+ onResolutionChange={setGameDocumentResolution}
304
+ scale={_gameScale}
305
+ title="Change game resolution"
306
+ />
307
+ <MuteButton />
308
+ <GameStatsMenu />
309
+ </div>
310
+ );
311
+ }
312
+
313
+ /** Game panel — a container div that play-mode.ts fills with a canvas.
314
+ * (Moved verbatim from `DefaultEditorLayout.tsx` in W1 — inventory row V3.) */
315
+ function GamePanel({ style }: { style?: React.CSSProperties }) {
316
+ const store = useEditorStore();
317
+ const isRunning = store.playState !== 'stopped';
318
+ const outerRef = useRef<HTMLDivElement>(null);
319
+ // W2 (V4): resolution comes from the shared game-view store above — the
320
+ // document-local toolbar writes it, this content applies it.
321
+ useSyncExternalStore(subscribeGameView, gameViewVersion);
322
+ const resolution = _gameResolution;
323
+ const [containerSize, setContainerSize] = useState({ width: 0, height: 0 });
324
+
325
+ // Callback ref so setGameContainer is called even when the element swaps (fill ↔ device)
326
+ const prevRef = useRef<HTMLDivElement | null>(null);
327
+ const canvasRef = useCallback((el: HTMLDivElement | null) => {
328
+ if (el === prevRef.current) return;
329
+ // Move game canvas to new container
330
+ if (prevRef.current && el) {
331
+ while (prevRef.current.firstChild) el.appendChild(prevRef.current.firstChild);
332
+ }
333
+ prevRef.current = el;
334
+ setGameContainer(el);
335
+ }, []);
336
+
337
+ // Track outer container size for scale calculation
338
+ useEffect(() => {
339
+ const el = outerRef.current;
340
+ if (!el) return;
341
+ const observer = new ResizeObserver(([entry]) => {
342
+ if (!entry) return;
343
+ setContainerSize({ width: entry.contentRect.width, height: entry.contentRect.height });
344
+ });
345
+ observer.observe(el);
346
+ return () => observer.disconnect();
347
+ }, []);
348
+
349
+ // Resize the game renderer when resolution or container size changes
350
+ const isFill = resolution.width === null;
351
+ useEffect(() => {
352
+ if (isFill) {
353
+ // Fill mode: resize to match available container space
354
+ if (containerSize.width > 0 && containerSize.height > 0) {
355
+ resizeGame(containerSize.width, containerSize.height);
356
+ }
357
+ } else {
358
+ resizeGame(resolution.width!, resolution.height!);
359
+ }
360
+ }, [isFill, resolution.width, resolution.height, containerSize.width, containerSize.height]);
361
+
362
+ // Scale: fit the device resolution into the available space, never upscale
363
+ let scale = 1;
364
+ if (!isFill && containerSize.width > 0 && containerSize.height > 0) {
365
+ const scaleX = containerSize.width / resolution.width!;
366
+ const scaleY = containerSize.height / resolution.height!;
367
+ scale = Math.min(scaleX, scaleY, 1);
368
+ }
369
+ // Publish for the toolbar's percentage readout (effect: setState-in-render
370
+ // on ANOTHER component's store is a React error; post-commit is correct).
371
+ useEffect(() => {
372
+ setGameScale(scale);
373
+ }, [scale]);
374
+
375
+ return (
376
+ <div style={{ overflow: 'hidden', position: 'relative', flexDirection: 'column', ...style }}>
377
+ <div
378
+ ref={outerRef}
379
+ style={{
380
+ flex: 1,
381
+ overflow: 'hidden',
382
+ display: 'flex',
383
+ alignItems: 'center',
384
+ justifyContent: 'center',
385
+ position: 'relative',
386
+ }}
387
+ >
388
+ {isFill ? (
389
+ <div
390
+ ref={canvasRef}
391
+ data-testid="game-runtime-container"
392
+ aria-hidden="true"
393
+ style={{ position: 'absolute', inset: 0, visibility: isRunning ? 'visible' : 'hidden' }}
394
+ />
395
+ ) : (
396
+ /* Wrapper sized to the scaled dimensions so flexbox centering works.
397
+ The inner canvasRef is full device resolution, CSS-scaled down. */
398
+ <div
399
+ style={{
400
+ width: resolution.width! * scale,
401
+ height: resolution.height! * scale,
402
+ overflow: 'hidden',
403
+ visibility: isRunning ? 'visible' : 'hidden',
404
+ }}
405
+ >
406
+ <div
407
+ ref={canvasRef}
408
+ data-testid="game-runtime-container"
409
+ aria-hidden="true"
410
+ style={{
411
+ width: resolution.width!,
412
+ height: resolution.height!,
413
+ transformOrigin: 'top left',
414
+ transform: `scale(${scale})`,
415
+ }}
416
+ />
417
+ </div>
418
+ )}
419
+ {!isRunning && (
420
+ <div
421
+ data-testid="game-stopped-placeholder"
422
+ style={{
423
+ position: 'absolute',
424
+ inset: 0,
425
+ display: 'flex',
426
+ flexDirection: 'column',
427
+ alignItems: 'center',
428
+ justifyContent: 'center',
429
+ gap: 6,
430
+ background: themeVars.surface.shell,
431
+ color: themeVars.content.dim,
432
+ fontSize: 11,
433
+ pointerEvents: 'none',
434
+ }}
435
+ >
436
+ <span style={{ color: themeVars.content.primary, fontSize: 13 }}>Game is stopped</span>
437
+ <span>Press Play to start the composed runtime.</span>
438
+ </div>
439
+ )}
440
+ {/* Same overlay ViewportPanel renders on the Scene tab (T-R1a #6) — the
441
+ Game tab is where play auto-switches to, so it must render here too,
442
+ not only on the Scene-tab viewport it would otherwise be hidden behind. */}
443
+ {store.playState !== 'stopped' && _gameStatsVisible && <GamePerformanceOverlay />}
444
+ </div>
445
+ {/* W4 (V4): the PlayBar copy that rendered here moved to the global
446
+ command header (`ProjectHeader.tsx`) — transport is now
447
+ document-independent; resolution/mute stay in this document's
448
+ local toolbar (`GameDocumentToolbar`). */}
449
+ </div>
450
+ );
451
+ }
452
+
453
+ /** The Game document's content host. Rendered warm (hidden) even while the
454
+ * document is closed — see this file's header. */
455
+ function GameDocumentContent(_props: WorkspaceDocumentContentProps) {
456
+ const store = useEditorStore();
457
+ useSyncExternalStore(store.subscribe, store.getSnapshot);
458
+ const isPlaying = store.playState !== 'stopped';
459
+ return (
460
+ <GamePanel
461
+ style={{
462
+ flex: 1,
463
+ position: 'relative',
464
+ display: 'flex',
465
+ flexDirection: 'column',
466
+ pointerEvents: 'auto',
467
+ // L-20 — playing/paused edge treatment from the shared themeVars.semantic.success/themeVars.semantic.warning
468
+ // tokens (same "healthy"/"attention" semantic SaveStatus uses).
469
+ ...(store.playState === 'playing'
470
+ ? { outline: `2px solid ${themeVars.semantic.success}`, outlineOffset: -2 }
471
+ : isPlaying
472
+ ? { outline: `2px solid ${themeVars.semantic.warning}`, outlineOffset: -2 }
473
+ : {}),
474
+ }}
475
+ />
476
+ );
477
+ }
478
+
479
+ /** Build the W1 document descriptors bound to a store. `onActivate` is the
480
+ * registry→store half of the bridge: activating a tab (click, open,
481
+ * close-neighbor fallback) writes the store's `activeViewportTab`, which is
482
+ * what the T6.3 input gate reads. */
483
+ export function createCenterDocumentDescriptors(store: CenterDocumentsStore): {
484
+ scene: WorkspaceDocumentDescriptor;
485
+ game: WorkspaceDocumentDescriptor;
486
+ } {
487
+ const scene: WorkspaceDocumentDescriptor = {
488
+ id: SCENE_DOCUMENT_ID,
489
+ title: sceneDocumentTitle(store.savePath),
490
+ kind: 'scene',
491
+ ...(store.savePath ? { provenance: { sourcePath: store.savePath } } : {}),
492
+ Content: SceneDocumentContent,
493
+ closeable: false,
494
+ onActivate: () => store.setActiveViewportTab('scene'),
495
+ };
496
+ const game: WorkspaceDocumentDescriptor = {
497
+ id: GAME_DOCUMENT_ID,
498
+ title: 'Game',
499
+ kind: 'game',
500
+ Content: GameDocumentContent,
501
+ // W2 (V4): the document-local toolbar — resolution + mute appear only
502
+ // while the Game document is active (§6.3).
503
+ Toolbar: GameDocumentToolbar,
504
+ closeable: false,
505
+ onActivate: () => store.setActiveViewportTab('game'),
506
+ };
507
+ return { scene, game };
508
+ }
509
+
510
+ /**
511
+ * The store→registry half of the bridge. Idempotent — safe (and designed) to
512
+ * run on EVERY store notify; re-entrant calls triggered by its own
513
+ * `onActivate` → `setActiveViewportTab` → notify chain settle as no-ops.
514
+ *
515
+ * 1. the scene document is always open (opens it on first run);
516
+ * 2. its tab title/dirty dot track `savePath`/`isDirty` live;
517
+ * 3. the Game document is always open. Dockview's `renderer: "always"`
518
+ * preserves its canvas before play begins and across tab switches;
519
+ * 4. the active document mirrors `activeViewportTab` (auto-switch on
520
+ * play/stop, `set-viewport-tab` control commands, ingest-mode's direct
521
+ * writes). A `game` tab value with no Game document open (transient
522
+ * store states) falls back to the scene document — there is always an
523
+ * active center document.
524
+ */
525
+ export function syncCenterDocuments(
526
+ store: CenterDocumentsStore,
527
+ docs: { scene: WorkspaceDocumentDescriptor; game: WorkspaceDocumentDescriptor },
528
+ ): void {
529
+ const isOpen = (id: string) => openWorkspaceDocuments().some((d) => d.descriptor.id === id);
530
+ // Opening a registry document activates it. Capture the caller's requested
531
+ // viewport before ensuring the permanently mounted Game tab, then restore
532
+ // that intent below; merely making Game discoverable must not switch a
533
+ // stopped editor away from Scene.
534
+ const requestedViewportTab = store.activeViewportTab;
535
+
536
+ // Registry order is the canonical tab order for a fresh layout. Game is
537
+ // permanently first—like the player preview in established game editors—
538
+ // while Scene remains the initial active authoring document when stopped.
539
+ if (!isOpen(GAME_DOCUMENT_ID)) openWorkspaceDocument(docs.game);
540
+ if (!isOpen(SCENE_DOCUMENT_ID)) openWorkspaceDocument(docs.scene);
541
+ setWorkspaceDocumentTitle(SCENE_DOCUMENT_ID, sceneDocumentTitle(store.savePath));
542
+ setWorkspaceDocumentDirty(SCENE_DOCUMENT_ID, store.isDirty);
543
+
544
+ // Mirror the store tab into registry activation — W2-aware: since asset
545
+ // and story documents joined the center, a THIRD-party document being
546
+ // active is a legitimate state (its own onActivate already asserted the
547
+ // store's 'scene' tab, so the T6.3 gate is off). The mirror therefore only
548
+ // corrects genuine disagreements about the SCENE/GAME pair:
549
+ // - tab says 'game' (play auto-switch, `set-viewport-tab game`) → the
550
+ // Game document must be active;
551
+ // - tab says 'scene' while the GAME document is active (external
552
+ // `set-viewport-tab scene` during play) → back to the scene document;
553
+ // - no active document at all (first run) → the scene document.
554
+ // A 'scene' tab with an asset/story document active is CONSISTENT and must
555
+ // not be "corrected" — forcing the scene document here (the W1 behavior)
556
+ // would instantly steal the tab back from every non-Game document on the
557
+ // next store notify.
558
+ const activeId = activeWorkspaceDocumentId();
559
+ if (requestedViewportTab === 'game' && isOpen(GAME_DOCUMENT_ID)) {
560
+ if (activeId !== GAME_DOCUMENT_ID) activateWorkspaceDocument(GAME_DOCUMENT_ID);
561
+ } else if (activeId === GAME_DOCUMENT_ID || activeId === null) {
562
+ // Covers both the external scene-tab write during play AND the
563
+ // transient "tab says game but no Game document is open" fallback —
564
+ // there is always an active center document.
565
+ activateWorkspaceDocument(SCENE_DOCUMENT_ID);
566
+ }
567
+ }
568
+
569
+ /**
570
+ * Layout hook: installs the W1 center documents for this editor session and
571
+ * keeps them synced to the store. Teardown closes every workspace document
572
+ * (project switch / return to project screen), firing their dispose hooks.
573
+ */
574
+ export function useCenterDocuments(store: CenterDocumentsStore): void {
575
+ useEffect(() => {
576
+ const docs = createCenterDocumentDescriptors(store);
577
+ const run = () => syncCenterDocuments(store, docs);
578
+ run();
579
+ const unsubscribe = store.subscribe(run);
580
+ return () => {
581
+ unsubscribe();
582
+ closeAllWorkspaceDocuments();
583
+ };
584
+ }, [store]);
585
+ }