@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
@@ -2,9 +2,8 @@
2
2
  * Edit-mode authoring (A1, docs/unified-world-editor/26-unified-editor-spec.md
3
3
  * §2 A1) — project open loads `vgai.game.json` and installs a
4
4
  * {@link CompositeAuthoringAdapter} over EVERY declared world, so a
5
- * multi-world project's composition is disclosed in edit mode (#28) instead
6
- * of only in play mode (`play-mode.ts`'s `installMultiWorldAuthoring`, the
7
- * pattern this mirrors). Unlike play mode, worlds are NOT running here: only
5
+ * project's composition is disclosed in edit mode (#28). Unlike play mode,
6
+ * roots are NOT running here: only
8
7
  * the FOCUSED threejs world is live (the existing `VgaiSceneAuthoringAdapter`
9
8
  * over the store's already-loaded document — `loadInitialScene` is
10
9
  * untouched, still the only thing that ever loads a scene into the store);
@@ -12,47 +11,30 @@
12
11
  * describing it from the manifest, upgraded to a live adapter only once its
13
12
  * design-time layer actually mounts (B1).
14
13
  *
15
- * **Back-compat floor (exact, not "equivalent"):** a single-world project —
16
- * whether there is no manifest at all (synthesized in memory, see
17
- * `installEditModeAuthoringForProject` below) or a manifest that happens to
18
- * declare exactly one world — has NOTHING new to disclose (#28 is a
19
- * multi-world problem), so `installEditModeAuthoring` deliberately does NOT
20
- * touch the shared `setActiveAuthoring` slot in that case. `getActiveAuthoring`'s
21
- * existing default (`new VgaiSceneAuthoringAdapter(store)`, `active-adapter.ts`)
22
- * keeps serving every call site that reads it (the viewport's
23
- * transforms/selection, `SaveStatus`, `DefaultEditorLayout`'s hierarchy/
24
- * inspector panel choice, the autosave guard, undo dispatch) completely
25
- * untouched — `hasAuthoringOverride()` stays `false`, exactly as today. Only
26
- * a GENUINE multi-world manifest (2+ declared worlds) installs the composite
27
- * as the active override, the same "a composite is active -> route through
28
- * the contract-driven panels" pattern `installMultiWorldAuthoring` already
29
- * established for play mode (verified precedent: `19-multi-world.spec.ts` /
30
- * `22-tri-world.spec.ts` assert on `ingest-row`/`ingest-position-*` testids
31
- * once a composite is installed) — A2's `GameHierarchy` replaces that
32
- * temporary routing wholesale; until then this is the honest, already-proven
33
- * mechanism for disclosing a composition #28 says must not stay invisible.
14
+ * D19 completes the migration: a project with one world and a project with
15
+ * many roots use the SAME game/world authoring tree. Cardinality may affect
16
+ * presentation density, never hierarchy semantics, routing, or persistence.
17
+ * Manifest-less projects still synthesize the conventional `main` world, so
18
+ * every project reaches this one path.
34
19
  */
35
20
 
36
- import type { AuthoringAdapter } from '@engine/adapter';
37
21
  import { editorConsole } from '../editor-console';
38
22
  import { getEditorMode } from '../editor-mode';
39
23
  import type { EditorStore } from '../editor-store';
40
- import { resolveInitialScenePathForManifestSynthesis } from '../initial-scene';
24
+ import { hierarchyProjectionFromProjectConfig } from '../hierarchy-projection';
25
+ import { getProjectFileHistory, type ProjectFileHistory } from '../history/project-file-history';
26
+ import { getManifestHistoryBackend } from '../history/project-root-history-backends';
41
27
  import { fetchRawGameManifest } from '../manifest-project';
42
28
  import { getCurrentProject } from '../project-manager';
43
29
  import { getStorageBackend } from '../storage';
44
- import { UIEditStore } from '../ui-editor/ui-edit-store';
45
- import { createHttpSourceWriteBackend } from '../ui-source/source-write-backend';
46
30
  import { getAuthoringOverride, markEditModeOverride, setActiveAuthoring } from './active-adapter';
47
31
  import { BoundaryAuthoringAdapter, type BoundaryWorldInfo } from './boundary-authoring-adapter';
48
32
  import {
49
33
  CompositeAuthoringAdapter,
50
34
  type CompositeChild,
51
- type GameRootManifestProvider,
35
+ type RootManifestProvider,
52
36
  } from './composite-authoring-adapter';
53
- import { UIAuthoringAdapter } from './ui-authoring-adapter';
54
- import { type UndoAdapter, unregisterUndoAdapter } from './undo-timeline';
55
- import { VgaiSceneAuthoringAdapter } from './vgai-scene-authoring-adapter';
37
+ import { fetchSceneDoc, VgaiSceneAuthoringAdapter } from './vgai-scene-authoring-adapter';
56
38
 
57
39
  /**
58
40
  * C3 (docs/unified-world-editor/26-unified-editor-spec.md §4 C3) — dispatched
@@ -68,9 +50,9 @@ export const EDIT_MODE_REBUILD_EVENT = 'editor:edit-mode-rebuild';
68
50
 
69
51
  /**
70
52
  * The display-relevant slice of a manifest world entry this module needs —
71
- * deliberately LOOSE (a bare `string` `kind`, not the engine's validated
72
- * `WorldEntry['kind']` union) so a world that fails schema validation
73
- * (an unknown kind) can still be represented and surfaced as an error node
53
+ * deliberately LOOSE (a bare `string` `surface`, not the engine's validated
54
+ * `AdapterRoot['surface']` union) so a world that fails schema validation
55
+ * (an unknown surface) can still be represented and surfaced as an error node
74
56
  * (#18). Built by the LENIENT parse below (`parseEditModeManifest`), reachable
75
57
  * from the production project-open path — NOT gated on strict Zod, which would
76
58
  * reject the whole file for one bad world and make every declared world vanish
@@ -78,7 +60,7 @@ export const EDIT_MODE_REBUILD_EVENT = 'editor:edit-mode-rebuild';
78
60
  */
79
61
  export interface EditModeWorldSpec {
80
62
  readonly id: string;
81
- readonly kind: string;
63
+ readonly surface: string;
82
64
  readonly adapter?: string | undefined;
83
65
  readonly scene?: string | undefined;
84
66
  readonly entry?: string | undefined;
@@ -87,7 +69,7 @@ export interface EditModeWorldSpec {
87
69
  }
88
70
 
89
71
  export interface EditModeManifest {
90
- readonly worlds: readonly EditModeWorldSpec[];
72
+ readonly roots: readonly EditModeWorldSpec[];
91
73
  }
92
74
 
93
75
  const KNOWN_WORLD_KINDS = new Set(['threejs', 'pixijs', 'react']);
@@ -115,30 +97,38 @@ function displayAdapter(adapter: unknown): string | undefined {
115
97
  }
116
98
 
117
99
  /**
118
- * LENIENT parse (defect 2): read a raw `vgai.game.json` object's `worlds[]`
100
+ * LENIENT parse (defect 2): read a raw `vgai.game.json` object's `roots[]`
119
101
  * defensively into `EditModeWorldSpec[]`, so a single bad world (unknown
120
- * `kind`, missing `scene`+`entry`) becomes an error node (#18) while its VALID
102
+ * `surface`, missing `scene`+`entry`) becomes an error node (#18) while its VALID
121
103
  * siblings still open — never the strict-Zod all-or-nothing of
122
- * `loadGameManifest`. Returns `null` when there is no usable `worlds` array at
104
+ * `loadGameManifest`. Returns `null` when there is no usable `roots` array at
123
105
  * all (a present-but-unusable manifest, distinct from an ABSENT one) so the
124
106
  * caller can fall back to the synthesized single-world floor. A world missing
125
107
  * an `id` gets a positional placeholder so it still surfaces rather than
126
- * vanishing. `kind` is carried through verbatim (even a bogus string) —
108
+ * vanishing. `surface` is carried through verbatim (even a bogus string) —
127
109
  * `resolveWorldFailureReason` decides resolvability downstream.
128
110
  */
129
111
  export function parseEditModeManifest(raw: unknown): EditModeManifest | null {
130
112
  if (!raw || typeof raw !== 'object') return null;
131
- const worldsRaw = (raw as Record<string, unknown>)['worlds'];
113
+ const worldsRaw = (raw as Record<string, unknown>)['roots'];
114
+ if (worldsRaw === undefined) return null;
132
115
  if (!Array.isArray(worldsRaw) || worldsRaw.length === 0) return null;
133
- const worlds: EditModeWorldSpec[] = worldsRaw.map((entry, i) => {
116
+ const roots: EditModeWorldSpec[] = worldsRaw.map((entry, i) => {
134
117
  const w = (entry && typeof entry === 'object' ? entry : {}) as Record<string, unknown>;
135
118
  const zOrderRaw = w['zOrder'];
136
119
  const pausableRaw = w['pausable'];
137
120
  return {
138
121
  id: asString(w['id']) ?? `(world ${i})`,
139
- // Missing/non-string kind is itself unresolvable — carry a sentinel so
122
+ // Missing/non-string surface is itself unresolvable — carry a sentinel so
140
123
  // resolveWorldFailureReason names it, rather than crashing on undefined.
141
- kind: asString(w['kind']) ?? '(missing kind)',
124
+ surface:
125
+ (typeof w['adapter'] === 'string' ? asString(w['adapter']) : undefined) ??
126
+ asString(
127
+ w['adapter'] && typeof w['adapter'] === 'object'
128
+ ? (w['adapter'] as Record<string, unknown>)['surface']
129
+ : undefined,
130
+ ) ??
131
+ '(missing adapter surface)',
142
132
  adapter: displayAdapter(w['adapter']),
143
133
  scene: asString(w['scene']),
144
134
  entry: asString(w['entry']),
@@ -146,7 +136,7 @@ export function parseEditModeManifest(raw: unknown): EditModeManifest | null {
146
136
  pausable: typeof pausableRaw === 'boolean' ? pausableRaw : undefined,
147
137
  };
148
138
  });
149
- return { worlds };
139
+ return { roots };
150
140
  }
151
141
 
152
142
  /**
@@ -158,8 +148,8 @@ export function parseEditModeManifest(raw: unknown): EditModeManifest | null {
158
148
  * out of scope here, where nothing but the focused world is ever fetched.
159
149
  */
160
150
  export function resolveWorldFailureReason(world: EditModeWorldSpec): string | null {
161
- if (!KNOWN_WORLD_KINDS.has(world.kind)) {
162
- return `Unknown world kind "${world.kind}" — expected one of: threejs, pixijs, react.`;
151
+ if (!KNOWN_WORLD_KINDS.has(world.surface)) {
152
+ return `Unknown world surface "${world.surface}" — expected one of: threejs, pixijs, react.`;
163
153
  }
164
154
  if (!world.scene && !world.entry) {
165
155
  return `World "${world.id}" declares neither \`scene\` nor \`entry\` — nothing to mount.`;
@@ -176,17 +166,21 @@ function normalizeScenePath(path: string): string {
176
166
  * the live store by `loadInitialScene`. Mirrors that function's OWN
177
167
  * resolution order (`initial-scene.ts`: `?scene=` URL param wins, else the
178
168
  * project's `defaultScene`); if neither names one of the manifest's threejs
179
- * worlds (or the manifest declares none), falls back to the first threejs
169
+ * roots (or the manifest declares none), falls back to the first threejs
180
170
  * world (matches the synthesized single-world manifest, which only ever has
181
171
  * exactly one). Exported (pure — no `window`/store reads) so it's directly
182
172
  * unit-testable; `installEditModeAuthoring` supplies the ambient
183
173
  * `focusScenePath` from `window.location`/the current project.
184
174
  */
185
175
  export function resolveFocusedThreejsWorldId(
186
- worlds: readonly EditModeWorldSpec[],
176
+ roots: readonly {
177
+ readonly id: string;
178
+ readonly surface: string;
179
+ readonly scene?: string | undefined;
180
+ }[],
187
181
  focusScenePath: string | null | undefined,
188
182
  ): string | null {
189
- const threejsWorlds = worlds.filter((w) => w.kind === 'threejs');
183
+ const threejsWorlds = roots.filter((w) => w.surface === 'threejs');
190
184
  if (threejsWorlds.length === 0) return null;
191
185
  if (focusScenePath) {
192
186
  const normalized = normalizeScenePath(focusScenePath);
@@ -199,26 +193,32 @@ export function resolveFocusedThreejsWorldId(
199
193
  }
200
194
 
201
195
  // ---------------------------------------------------------------------------
202
- // A4 (D8) — ManifestAuthoring: the game root's read-modify-write manifest surface
196
+ // A4 (D8) — ManifestAuthoring: read-modify-write root composition properties
203
197
  // ---------------------------------------------------------------------------
204
198
 
205
199
  /** A raw `vgai.game.json` world entry, read/written defensively — same
206
200
  * looseness as {@link EditModeWorldSpec}, but this is the RAW (unknown)
207
201
  * object, never Zod-parsed, so unrelated fields survive a round trip. */
208
- type RawWorldEntry = Record<string, unknown>;
209
-
210
- function findRawWorld(raw: Record<string, unknown>, worldId: string): RawWorldEntry | undefined {
211
- const worlds = raw['worlds'];
212
- if (!Array.isArray(worlds)) return undefined;
213
- return worlds.find(
214
- (w): w is RawWorldEntry =>
215
- !!w && typeof w === 'object' && (w as RawWorldEntry)['id'] === worldId,
202
+ type RawAdapterRoot = Record<string, unknown>;
203
+
204
+ function findRawWorld(raw: Record<string, unknown>, worldId: string): RawAdapterRoot | undefined {
205
+ const roots = raw['roots'];
206
+ if (!Array.isArray(roots)) return undefined;
207
+ return roots.find(
208
+ (w): w is RawAdapterRoot =>
209
+ !!w && typeof w === 'object' && (w as RawAdapterRoot)['id'] === worldId,
216
210
  );
217
211
  }
218
212
 
213
+ /** Materialize D19's inferred world only when a user edits one of its
214
+ * manifest-backed properties. Until then the raw file remains minimal. */
215
+ function ensureRawWorld(raw: Record<string, unknown>, worldId: string): RawAdapterRoot | undefined {
216
+ return findRawWorld(raw, worldId);
217
+ }
218
+
219
219
  /**
220
- * ManifestAuthoring (A4, D8) — the game root's manifest-backed
221
- * {@link GameRootManifestProvider}: holds the RAW parsed `vgai.game.json`
220
+ * ManifestAuthoring (A4, D8) — authored roots' manifest-backed
221
+ * {@link RootManifestProvider}: holds the RAW parsed `vgai.game.json`
222
222
  * object (read-modify-write — NEVER round-tripped through the strict Zod
223
223
  * schema, so fields this module doesn't know about survive untouched) and
224
224
  * writes it back via `POST /__editor/manifest` (the one manifest-write verb,
@@ -231,30 +231,44 @@ function findRawWorld(raw: Record<string, unknown>, worldId: string): RawWorldEn
231
231
  * - a SYNTHESIZED manifest (`synthesized: true`, no `vgai.game.json` on disk
232
232
  * yet) BUFFERS edits in memory only; nothing is written until the
233
233
  * explicit `materialize()` call (the generic inspector's "Create
234
- * vgai.game.json" action). In production this branch is currently
235
- * UNREACHABLE end-to-end: `installEditModeAuthoring` only ever installs a
236
- * game root when 2+ worlds are declared, and `synthesizeSingleWorldManifest`
237
- * (the only source of a `synthesized: true` manifest) always produces
238
- * exactly ONE world — so a synthesized manifest never gets a game root
239
- * through today's wiring. The buffering logic is still implemented and
240
- * unit-tested for correctness/forward-compatibility (see this module's
241
- * tests), and the limitation is called out explicitly rather than silently
242
- * assumed away.
234
+ * vgai.game.json" action). D19 makes this branch reachable for a
235
+ * manifest-less conventional project because its inferred root is always
236
+ * present.
243
237
  *
244
238
  * File-watcher landmine: `chokidar` only watches `public/` (`editor-server.ts`),
245
239
  * so a manifest write at the PROJECT ROOT emits no SSE — `notifyIngestEdit()`
246
240
  * after every successful write is what keeps the hierarchy panel's badges
247
241
  * live instead of stale until reopen.
248
242
  */
249
- export class ManifestAuthoring implements GameRootManifestProvider {
243
+ export class ManifestAuthoring implements RootManifestProvider {
250
244
  private readonly raw: Record<string, unknown>;
251
- readonly synthesized: boolean;
245
+ private synthesizedValue: boolean;
252
246
  private readonly store: EditorStore;
247
+ private readonly fileHistory: ProjectFileHistory | null;
248
+ private readonly unsubscribeHistory: (() => void) | null;
253
249
 
254
250
  constructor(raw: Record<string, unknown>, synthesized: boolean, store: EditorStore) {
255
251
  this.raw = raw;
256
- this.synthesized = synthesized;
252
+ this.synthesizedValue = synthesized;
257
253
  this.store = store;
254
+ const history = store.projectHistory;
255
+ this.fileHistory = history
256
+ ? getProjectFileHistory(history, getManifestHistoryBackend(), 'project-manifest')
257
+ : null;
258
+ this.unsubscribeHistory = this.fileHistory
259
+ ? this.fileHistory.subscribeAll((path) => {
260
+ if (path !== 'vgai.game.json') return;
261
+ void getManifestHistoryBackend()
262
+ .readBytes(path)
263
+ .then((bytes) => JSON.parse(new TextDecoder().decode(bytes)) as Record<string, unknown>)
264
+ .then((next) => {
265
+ for (const key of Object.keys(this.raw)) delete this.raw[key];
266
+ Object.assign(this.raw, next);
267
+ this.store.notifyIngestEdit();
268
+ })
269
+ .catch(() => {});
270
+ })
271
+ : null;
258
272
  }
259
273
 
260
274
  getGameName(): string {
@@ -262,9 +276,23 @@ export class ManifestAuthoring implements GameRootManifestProvider {
262
276
  return typeof name === 'string' ? name : '';
263
277
  }
264
278
 
279
+ get synthesized(): boolean {
280
+ return this.synthesizedValue;
281
+ }
282
+
265
283
  setGameName(name: string): void {
266
284
  this.raw['name'] = name;
267
- this.persist();
285
+ this.persist('Rename Game');
286
+ }
287
+
288
+ getWorldContentSource(worldId: string): string | undefined {
289
+ const world = findRawWorld(this.raw, worldId);
290
+ if (!world) return undefined;
291
+ const scene = world['scene'];
292
+ if (typeof scene === 'string') return `Scene · ${scene}`;
293
+ const entry = world['entry'];
294
+ if (typeof entry === 'string') return `Entry · ${entry}`;
295
+ return 'Adapter-owned runtime content';
268
296
  }
269
297
 
270
298
  getWorldZOrder(worldId: string): number {
@@ -273,10 +301,10 @@ export class ManifestAuthoring implements GameRootManifestProvider {
273
301
  }
274
302
 
275
303
  setWorldZOrder(worldId: string, value: number): void {
276
- const world = findRawWorld(this.raw, worldId);
304
+ const world = ensureRawWorld(this.raw, worldId);
277
305
  if (!world) return;
278
306
  world['zOrder'] = value;
279
- this.persist();
307
+ this.persist('Change World Order');
280
308
  }
281
309
 
282
310
  getWorldPausable(worldId: string): boolean {
@@ -285,27 +313,42 @@ export class ManifestAuthoring implements GameRootManifestProvider {
285
313
  }
286
314
 
287
315
  setWorldPausable(worldId: string, value: boolean): void {
288
- const world = findRawWorld(this.raw, worldId);
316
+ const world = ensureRawWorld(this.raw, worldId);
289
317
  if (!world) return;
290
318
  world['pausable'] = value;
291
- this.persist();
319
+ this.persist('Change World Pausing');
292
320
  }
293
321
 
294
322
  /** The "Create vgai.game.json" action (generic inspector, while
295
323
  * `synthesized`) — materializes the buffered edits to disk. Also safe to
296
324
  * call on a non-synthesized instance (equivalent to an explicit re-save). */
297
325
  async materialize(): Promise<boolean> {
298
- return this.write();
326
+ const written = await this.write('Create Game Manifest');
327
+ if (written) this.synthesizedValue = false;
328
+ return written;
329
+ }
330
+
331
+ dispose(): void {
332
+ this.unsubscribeHistory?.();
299
333
  }
300
334
 
301
- private persist(): void {
335
+ private persist(label: string): void {
302
336
  if (this.synthesized) return; // buffered only — wait for materialize()
303
- void this.write();
337
+ void this.write(label);
304
338
  }
305
339
 
306
- private async write(): Promise<boolean> {
340
+ private async write(label: string): Promise<boolean> {
307
341
  const content = JSON.stringify(this.raw, null, 2);
308
342
  try {
343
+ if (this.fileHistory) {
344
+ await this.fileHistory.write('vgai.game.json', content, {
345
+ label,
346
+ kind: 'manifest',
347
+ contentType: 'application/json',
348
+ });
349
+ this.store.notifyIngestEdit();
350
+ return true;
351
+ }
309
352
  // Phase A cleanup: `vgai.game.json` is project-root-relative, the same
310
353
  // namespace `authoring/world-overlay.ts`/`authoring/ingest-overlay.ts`
311
354
  // already mode-branch on — browser mode has no `/__editor/*` server at
@@ -328,106 +371,44 @@ export class ManifestAuthoring implements GameRootManifestProvider {
328
371
  this.store.notifyIngestEdit();
329
372
  return true;
330
373
  } catch (err) {
374
+ if (this.fileHistory) {
375
+ void getManifestHistoryBackend()
376
+ .readBytes('vgai.game.json')
377
+ .then((bytes) => JSON.parse(new TextDecoder().decode(bytes)) as Record<string, unknown>)
378
+ .then((next) => {
379
+ for (const key of Object.keys(this.raw)) delete this.raw[key];
380
+ Object.assign(this.raw, next);
381
+ this.store.notifyIngestEdit();
382
+ })
383
+ .catch(() => {});
384
+ }
331
385
  editorConsole.error(`Failed to write vgai.game.json: ${err}`, 'project');
332
386
  return false;
333
387
  }
334
388
  }
335
389
  }
336
390
 
337
- /** Children built by the last `installEditModeAuthoring` call — tracked so
338
- * `exitEditModeAuthoring` can unregister any that self-registered with the
339
- * undo timeline (mirrors `play-mode.ts`'s `_multiWorldChildren`/
340
- * `exitMultiWorldAuthoring`). Boundary adapters never register (no `undo`/
341
- * `redo`); the focused `VgaiSceneAuthoringAdapter` delegates undo/redo to
342
- * the store, which registers ITSELF once at construction
343
- * (`editor-store.ts`) — so this is currently always a no-op in practice,
344
- * kept for the same defensive reason `play-mode.ts`'s guard is: a future
345
- * live child adapter (B1's `ReactWorldAuthoringAdapter`) DOES self-register. */
346
- let _editModeChildren: AuthoringAdapter[] = [];
347
-
348
- /**
349
- * C3 / R2 — the `UIEditStore`(s) built for this session's scene-UI child(ren)
350
- * (today: at most one, the focused world's). Tracked SEPARATELY from
351
- * `_editModeChildren`: the undo timeline registers the `UIEditStore` itself
352
- * (`ui-edit-store.ts`'s constructor, `registerUndoAdapter(this)`), NOT the
353
- * `UIAuthoringAdapter` wrapping it — `exitEditModeAuthoring`'s generic
354
- * `_editModeChildren` loop below unregisters the ADAPTER, which was never in
355
- * the undo timeline's `live` set (a harmless no-op for it), so the real store
356
- * needs its OWN explicit unregister here, exactly what the retired standalone
357
- * UI-edit mode's own teardown used to do.
358
- */
359
- let _editModeUIStores: UIEditStore[] = [];
360
-
361
- /**
362
- * C3 — build the scene-UI child for `focusedWorldId`'s composite when the
363
- * open scene has authored UI roots (`store.getSceneUI().length > 0`,
364
- * §3.1's install gate — a ui-less scene gets no child at all, keeping the
365
- * A1 single-world back-compat floor byte-identical). Mirrors the retired
366
- * standalone UI-edit mode's store/adapter construction, minus its
367
- * demo-fixture fallback (Q3: "should never have existed" — dropped by
368
- * design, not lost; see the harness/15/16 for the demo fixture's only
369
- * remaining home) and its own `setActiveAuthoring` call (the shared
370
- * composite install below already does that once, for every child).
371
- */
372
- function buildUIChild(store: EditorStore, focusedWorldId: string, zOrder: number): CompositeChild {
373
- const uiStore = new UIEditStore(store.getSceneUI(), {
374
- // Persist every committed edit back into the open scene's `ui` + autosave
375
- // (C9) — the exact same commit path the retired standalone UI-edit
376
- // mode's `onCommit` used.
377
- onCommit: (next) => store.setSceneUI(next),
378
- });
379
- _editModeUIStores.push(uiStore);
380
- const adapter = new UIAuthoringAdapter(uiStore, {
381
- ...(import.meta.env.DEV ? { writeBackend: createHttpSourceWriteBackend() } : {}),
382
- });
383
- return {
384
- worldId: `${focusedWorldId}:ui`,
385
- kind: 'scene-ui',
386
- adapter,
387
- zOrder,
388
- parentWorldId: focusedWorldId,
389
- };
390
- }
391
-
392
- /**
393
- * B1 — register a child adapter minted AFTER `installEditModeAuthoring`
394
- * already ran (a design-time layer's successful mount, which upgrades a
395
- * world's Boundary adapter to a live one via `CompositeAuthoringAdapter.
396
- * replaceChild` — `design-time-layers.ts`). Mirrors the constructor-time
397
- * seeding above (`_editModeChildren = children.map(...)`) so `exitEditModeAuthoring`
398
- * unregisters this adapter from the undo timeline too, exactly like every
399
- * child present at install time. A no-op call with an adapter that never
400
- * self-registered (no `undo`/`redo`) is harmless — `exitEditModeAuthoring`'s
401
- * loop already guards on that pair being present.
402
- */
403
- export function registerEditModeChild(adapter: AuthoringAdapter): void {
404
- _editModeChildren.push(adapter);
405
- }
391
+ let _manifestAuthoring: ManifestAuthoring | null = null;
406
392
 
407
393
  /**
408
- * Build the composite over every declared world and — ONLY when there is
409
- * more than one world to disclose — install it as the active authoring
410
- * override (see this module's header comment for why the single-world case
411
- * deliberately leaves the shared slot untouched). Always returns the built
412
- * composite so callers (tests) can inspect it regardless of whether it was
413
- * installed as the override.
394
+ * Build the composite over every declared world and install it as the active
395
+ * authoring override for every non-empty composition
396
+ * (D19). Always returns the built composite so callers can inspect it.
414
397
  *
415
- * The focused threejs world is whichever one's scene `loadInitialScene`
416
- * already loaded into the store (URL `?scene=` / `defaultScene` order); it
417
- * gets the real `VgaiSceneAuthoringAdapter`. A1 deliberately ships NO
418
- * focus-SWITCH (loading a DIFFERENT world's scene into the store) — that
419
- * requires a dirty-check-before-switch prompt + the scene-load path and lands
420
- * in A4 ({@link switchFocusedWorld} is the explicit, loud stub). Accepting a
421
- * "focus this other world" override here would label the new world's identity
422
- * onto the OLD world's still-loaded document — a silent misattribution
423
- * (defect 5) — so that parameter was removed.
398
+ * The focused threejs world is whichever one's scene the store currently
399
+ * holds (URL `?scene=` / `defaultScene` order — `loadInitialScene` at open,
400
+ * {@link switchFocusedWorld} thereafter); it gets the real
401
+ * `VgaiSceneAuthoringAdapter`. This function still accepts NO "focus this
402
+ * other world" parameter — that would label the new world's identity onto
403
+ * the OLD world's still-loaded document (defect 5). A focus change happens
404
+ * ONLY through {@link switchFocusedWorld}, which changes the document and
405
+ * the `?scene=` truth together and then rebuilds through this function.
424
406
  *
425
407
  * `rawManifestInfo` (A4, D8, optional) — the RAW parsed `vgai.game.json` +
426
- * whether it was synthesized (never written to disk); when given AND the
427
- * composite ends up with 2+ children, a `ManifestAuthoring` is built over it
428
- * and passed to `CompositeAuthoringAdapter` as its game-root manifest
429
- * surface. Omitted (every existing unit-test call site) ⇒ no game root, the
430
- * exact pre-A4 shape — see `CompositeAuthoringAdapter`'s own doc comment.
408
+ * whether it was synthesized (never written to disk); when given and the
409
+ * composite has a world, a `ManifestAuthoring` is built over it
410
+ * and passed to `CompositeAuthoringAdapter` as its root-property manifest
411
+ * surface. When omitted, composition properties are read-only.
431
412
  */
432
413
  export function installEditModeAuthoring(
433
414
  store: EditorStore,
@@ -438,9 +419,17 @@ export function installEditModeAuthoring(
438
419
  const sceneParam = urlParams.get('scene');
439
420
  const defaultScene = getCurrentProject()?.config.defaultScene ?? undefined;
440
421
  const focusScenePath = sceneParam ?? defaultScene ?? null;
441
- const focusedWorldId = resolveFocusedThreejsWorldId(manifest.worlds, focusScenePath);
422
+ const focusedWorldId = resolveFocusedThreejsWorldId(manifest.roots, focusScenePath);
423
+
424
+ // A4 — rebuild the focus-candidate map WHOLE (install time is the recompute
425
+ // point): every unfocused threejs world with a loadable manifest scene.
426
+ _focusCandidates = new Map(
427
+ manifest.roots
428
+ .filter((w) => w.surface === 'threejs' && w.id !== focusedWorldId && w.scene !== undefined)
429
+ .map((w) => [w.id, w.scene as string]),
430
+ );
442
431
 
443
- const children: CompositeChild[] = manifest.worlds.map((world) => {
432
+ const children: CompositeChild[] = manifest.roots.map((world) => {
444
433
  // D12 (B4) — install-time zOrder for every child, not just the Boundary's
445
434
  // own inspector-displayed value: `childAdapters()`'s pick/paint-order
446
435
  // consumers (the layered pick walk, the hierarchy zOrder badge) need it
@@ -449,7 +438,7 @@ export function installEditModeAuthoring(
449
438
  if (world.id === focusedWorldId) {
450
439
  return {
451
440
  worldId: world.id,
452
- kind: world.kind,
441
+ kind: world.surface,
453
442
  adapter: new VgaiSceneAuthoringAdapter(store),
454
443
  zOrder,
455
444
  };
@@ -457,7 +446,7 @@ export function installEditModeAuthoring(
457
446
  const reason = resolveWorldFailureReason(world);
458
447
  const info: BoundaryWorldInfo = {
459
448
  id: world.id,
460
- kind: world.kind,
449
+ kind: world.surface,
461
450
  adapter: world.adapter,
462
451
  entryOrScenePath: world.scene ?? world.entry,
463
452
  zOrder,
@@ -465,54 +454,24 @@ export function installEditModeAuthoring(
465
454
  };
466
455
  return {
467
456
  worldId: world.id,
468
- kind: world.kind,
457
+ kind: world.surface,
469
458
  adapter: new BoundaryAuthoringAdapter(store, info, reason ?? undefined),
470
459
  zOrder,
471
460
  };
472
461
  });
473
462
 
474
- // C3 — the focused world's scene-UI child (§3.1): only when the open scene
475
- // actually has UI roots to disclose, and only when there IS a focused
476
- // threejs world to nest it under (a manifest declaring zero threejs worlds
477
- // has nowhere to attach one — an edge case `resolveFocusedThreejsWorldId`
478
- // already surfaces as `null`). Counts toward the `children.length > 1`
479
- // install gate below BY DESIGN (R5): a single-world project that already
480
- // authored UI content gets the composite + the nested "ui (scene-ui)" row;
481
- // a ui-less single-world project's hierarchy shape is untouched.
482
- if (focusedWorldId && store.getSceneUI().length > 0) {
483
- const focusedChild = children.find((c) => c.worldId === focusedWorldId);
484
- children.push(buildUIChild(store, focusedWorldId, focusedChild?.zOrder ?? 0));
485
- }
486
-
487
- // A4 (D8) — the game root only makes sense once there IS a composition to
488
- // disclose (2+ worlds); a single-world project (synthesized or a real
489
- // 1-world manifest) never installs the composite as the override at all
490
- // (below), so it would never be reachable regardless.
491
- const gameRoot =
492
- rawManifestInfo && children.length > 1
463
+ // D19 — every composition, including one root, uses the same adapter projection.
464
+ // A synthesized manifest uses the same tree; its manifest-backed
465
+ // edits remain buffered until an explicit project change materializes it.
466
+ const manifestAuthoring =
467
+ rawManifestInfo && children.length >= 1
493
468
  ? new ManifestAuthoring(rawManifestInfo.raw, rawManifestInfo.synthesized, store)
494
469
  : undefined;
495
- const composite = new CompositeAuthoringAdapter(children, gameRoot);
496
- _editModeChildren = children.map((c) => c.adapter);
497
-
498
- // A1 delta (Wave-1 Phase-A close-out, spec:232-235, spec §3) — the ORIGINAL
499
- // floor here was bare `children.length > 1`: a manifest whose only world is
500
- // react (or any world-set with NO focused threejs world at all) never met
501
- // it, so that world never entered the composite/hierarchy and
502
- // `WorldSelectionOverlay` self-gated off, even though a real world exists
503
- // to disclose. `focusedWorldId === null` (see `resolveFocusedThreejsWorldId`'s
504
- // own doc comment: it returns `null` exactly when the manifest declares NO
505
- // threejs world) with at least one child is that no-threejs case — install
506
- // the composite there too. The single-threejs-world floor above (`children.
507
- // length === 1` AND that one world IS the focused threejs world, so
508
- // `focusedWorldId` is non-null) is UNCHANGED and must stay so — see the doc
509
- // comment at this file's top (~:15-33) explaining why that back-compat
510
- // floor is exact, not "equivalent". `mountDesignTimeLayers` already handles
511
- // the layer mount for the newly-activated no-threejs case unchanged — no
512
- // parallel react-only entry point is added here (spec-27 E3 all-worlds
513
- // generalization).
514
- const noFocusedThreejsWorld = focusedWorldId === null;
515
- if (children.length > 1 || (children.length >= 1 && noFocusedThreejsWorld)) {
470
+ _manifestAuthoring = manifestAuthoring ?? null;
471
+ const projection = hierarchyProjectionFromProjectConfig(getCurrentProject()?.config);
472
+ const composite = new CompositeAuthoringAdapter(children, manifestAuthoring, projection);
473
+
474
+ if (children.length >= 1) {
516
475
  // Brand BEFORE installing so the store's save guard sees it the instant
517
476
  // the override is live (defect 1: the focused first-party child must be
518
477
  // able to Ctrl+S/autosave through the store, unlike a foreign override).
@@ -527,51 +486,115 @@ export function installEditModeAuthoring(
527
486
  /** Teardown symmetric to `installEditModeAuthoring` — clears the override
528
487
  * ONLY if it is still exactly the composite this module last installed
529
488
  * (never clobbers a DIFFERENT session, e.g. ingest/play, that took over
530
- * afterward), and unregisters any child that self-registered with the undo
531
- * timeline. */
489
+ * afterward). */
532
490
  export function exitEditModeAuthoring(installed?: CompositeAuthoringAdapter): void {
533
491
  if (installed && getAuthoringOverride() === installed) {
534
492
  setActiveAuthoring(null);
535
493
  }
536
- for (const adapter of _editModeChildren) {
537
- if (adapter.undo && adapter.redo) {
538
- unregisterUndoAdapter(adapter as AuthoringAdapter & UndoAdapter);
539
- }
540
- }
541
- _editModeChildren = [];
542
- // R2 — see `_editModeUIStores`'s doc comment: the real undo-timeline
543
- // registrant for a scene-UI child is its `UIEditStore`, not the
544
- // `UIAuthoringAdapter` the loop above already (harmlessly, no-op) tried.
545
- for (const uiStore of _editModeUIStores) {
546
- unregisterUndoAdapter(uiStore);
494
+ _manifestAuthoring?.dispose();
495
+ _manifestAuthoring = null;
496
+ }
497
+
498
+ // ---------------------------------------------------------------------------
499
+ // A4 — focus-SWITCH: make a different threejs world the live/focused one
500
+ // ---------------------------------------------------------------------------
501
+
502
+ /**
503
+ * A4 — which UNFOCUSED threejs roots can become focused, captured by the
504
+ * last `installEditModeAuthoring` run (worldId → manifest scene path).
505
+ * Install time IS the recompute point — the map is rebuilt whole on every
506
+ * install/rebuild, never patched. A threejs world with no `scene` (entry-only)
507
+ * is genuinely unswitchable — there is nothing to load into the store — so it
508
+ * never enters the map and the shell shows no affordance for it (spec 28:
509
+ * hide when the subject is absent).
510
+ */
511
+ let _focusCandidates = new Map<string, string>();
512
+
513
+ /** The manifest scene path `switchFocusedWorld` would load for `worldId`, or
514
+ * `null` when that world is not a switchable unfocused threejs world. The
515
+ * shell gates its focus-switch affordances on this (never advertise a switch
516
+ * that will refuse — spec 29 §3). */
517
+ export function worldFocusScenePath(worldId: string): string | null {
518
+ return _focusCandidates.get(worldId) ?? null;
519
+ }
520
+
521
+ /** Dispatched (on `window`) when a focus-switch needs the dirty-gate
522
+ * confirmation dialog — same decoupling pattern as `root-activation.ts`'s
523
+ * `ROOT_ACTIVATION_REQUEST_EVENT`; `DefaultEditorLayout` listens. */
524
+ export const WORLD_FOCUS_REQUEST_EVENT = 'editor:request-world-focus';
525
+
526
+ export interface WorldFocusRequest {
527
+ readonly worldId: string;
528
+ readonly scenePath: string;
529
+ }
530
+
531
+ /**
532
+ * Focus-SWITCH (A4, closing defect 5's deferral): make `worldId` the live
533
+ * threejs world by doing exactly what the initial load does, in its order —
534
+ * flush pending edits (`saveNow`, same as `rootGroups.activate`), load the
535
+ * target world's manifest scene into the store (this clears undo/redo and
536
+ * retargets autosave — `loadDocument`'s own proven semantics), point the
537
+ * ambient focus truth at it (`?scene=`, the SAME param `loadInitialScene`
538
+ * and `installEditModeAuthoring` read — so any later rebuild, and a browser
539
+ * refresh, re-derive THIS focus), then fire the C3 rebuild event so
540
+ * `ViewportPanel.installAll()` reinstalls the composite: the target world
541
+ * gets the real `VgaiSceneAuthoringAdapter`, the previously-focused world
542
+ * downgrades to a Boundary. No identity misattribution is possible because
543
+ * the document and the focus truth change together, before the rebuild.
544
+ */
545
+ export async function switchFocusedWorld(
546
+ store: EditorStore,
547
+ worldId: string,
548
+ scenePath: string,
549
+ ): Promise<void> {
550
+ if (store.isDirty) await store.saveNow();
551
+ const path = normalizeScenePath(scenePath);
552
+ let doc: Awaited<ReturnType<typeof fetchSceneDoc>>;
553
+ try {
554
+ doc = await fetchSceneDoc(path);
555
+ } catch (err) {
556
+ editorConsole.error(
557
+ `Focus-switch to world "${worldId}" failed — could not load its scene "${path}": ${err}`,
558
+ 'project',
559
+ );
560
+ return;
547
561
  }
548
- _editModeUIStores = [];
562
+ store.loadDocument(doc, path);
563
+ const url = new URL(window.location.href);
564
+ url.searchParams.set('scene', path);
565
+ window.history.replaceState(null, '', url.toString());
566
+ window.dispatchEvent(new CustomEvent(EDIT_MODE_REBUILD_EVENT));
549
567
  }
550
568
 
551
569
  /**
552
- * Focus-SWITCH (A4, defect 5): switching which threejs world is live requires
553
- * (a) a dirty-check-before-switch prompt and (b) LOADING the target world's
554
- * scene into the store — neither of which A1 ships. This explicit stub exists
555
- * so a future caller gets a loud, named failure instead of A1 silently
556
- * rebuilding a composite that labels the target world's identity onto the
557
- * currently-loaded document. A1's composite always focuses the world
558
- * `loadInitialScene` already loaded.
570
+ * Request focusing `worldId` — the shell's ONE entry point (both the world
571
+ * row double-click and its context-menu item), mirroring
572
+ * `requestRootActivation`'s dirty-gate: a dirty store gets the shell-rendered
573
+ * confirmation dialog (`DefaultEditorLayout` listens for
574
+ * {@link WORLD_FOCUS_REQUEST_EVENT} and calls {@link switchFocusedWorld} on
575
+ * confirm); a clean store switches immediately.
559
576
  */
560
- export function switchFocusedWorld(): never {
561
- throw new Error(
562
- 'Focus-switch (loading a different threejs world into the store, with a ' +
563
- 'dirty-check prompt) is not implemented in A1 — it lands in A4. A1 installs the ' +
564
- 'composite over the world loadInitialScene already loaded.',
565
- );
577
+ export function requestWorldFocus(store: EditorStore, worldId: string): void {
578
+ const scenePath = worldFocusScenePath(worldId);
579
+ if (scenePath === null) return;
580
+ if (store.isDirty) {
581
+ window.dispatchEvent(
582
+ new CustomEvent(WORLD_FOCUS_REQUEST_EVENT, {
583
+ detail: { worldId, scenePath } satisfies WorldFocusRequest,
584
+ }),
585
+ );
586
+ return;
587
+ }
588
+ void switchFocusedWorld(store, worldId, scenePath);
566
589
  }
567
590
 
568
591
  /**
569
592
  * Project-open wiring: fetch the RAW `vgai.game.json` and install the edit-mode
570
593
  * composite via the LENIENT parse (defect 2) — a manifest with one bad world
571
- * still opens its valid worlds, surfacing the bad one as an error node (#18).
594
+ * still opens its valid roots, surfacing the bad one as an error node (#18).
572
595
  * ONLY a genuinely ABSENT manifest (the route 404s → `fetchRawGameManifest`
573
- * returns `null`), or one so malformed it has no usable `worlds` array,
574
- * synthesizes a single `{ id: 'main', kind: 'threejs', scene: <resolved> }`
596
+ * returns `null`), or one so malformed it has no usable `roots` array,
597
+ * synthesizes a single `{ id: 'main', surface: 'threejs', scene: <resolved> }`
575
598
  * game IN MEMORY (never written to disk), where `<resolved>` mirrors
576
599
  * `loadInitialScene`'s OWN resolution order. This is the only call site that
577
600
  * should call `installEditModeAuthoring` at ordinary project open
@@ -581,13 +604,10 @@ export async function installEditModeAuthoringForProject(
581
604
  store: EditorStore,
582
605
  ): Promise<CompositeAuthoringAdapter> {
583
606
  let manifest: EditModeManifest;
584
- // A4 (D8) — the RAW object backing `ManifestAuthoring` (read-modify-write,
585
- // never round-tripped through Zod). `synthesized: true` when there is no
586
- // real `vgai.game.json` on disk (a 404, or one so malformed it has no
587
- // usable `worlds` array) — see `ManifestAuthoring`'s doc comment for why
588
- // this branch is unreachable end-to-end today (synthesis always produces
589
- // exactly one world; a game root only installs for 2+).
590
- let rawManifestInfo: { raw: Record<string, unknown>; synthesized: boolean };
607
+ // The RAW object backing `ManifestAuthoring` (read-modify-write, never
608
+ // round-tripped through Zod). An omitted roots field is a real manifest
609
+ // using D19's convention, not a synthesized/unsaved manifest.
610
+ let rawManifestInfo: { raw: Record<string, unknown>; synthesized: boolean } | undefined;
591
611
  try {
592
612
  const raw = await fetchRawGameManifest();
593
613
  const parsed = raw !== null && parseEditModeManifest(raw);
@@ -595,49 +615,14 @@ export async function installEditModeAuthoringForProject(
595
615
  manifest = parsed;
596
616
  rawManifestInfo = { raw: raw as Record<string, unknown>, synthesized: false };
597
617
  } else {
598
- manifest = synthesizeSingleWorldManifest();
599
- rawManifestInfo = { raw: synthesizeRawManifest(manifest), synthesized: true };
618
+ throw new Error('manifest must declare a non-empty roots array');
600
619
  }
601
620
  } catch (err) {
602
- editorConsole.error(
603
- `Failed to load vgai.game.json, falling back to a synthesized single-world game: ${err}`,
604
- 'project',
605
- );
606
- manifest = synthesizeSingleWorldManifest();
607
- rawManifestInfo = { raw: synthesizeRawManifest(manifest), synthesized: true };
621
+ const detail = err instanceof Error ? err.message : String(err);
622
+ editorConsole.error(`Failed to load vgai.game.json: ${detail}`, 'project');
623
+ manifest = {
624
+ roots: [{ id: 'manifest', surface: '(invalid manifest)', adapter: detail }],
625
+ };
608
626
  }
609
627
  return installEditModeAuthoring(store, manifest, rawManifestInfo);
610
628
  }
611
-
612
- function synthesizeSingleWorldManifest(): EditModeManifest {
613
- return {
614
- worlds: [
615
- {
616
- id: 'main',
617
- kind: 'threejs',
618
- scene: resolveInitialScenePathForManifestSynthesis(getCurrentProject()) ?? undefined,
619
- },
620
- ],
621
- };
622
- }
623
-
624
- /** A minimally-valid RAW manifest object matching a synthesized in-memory
625
- * game (A4) — never written to disk on its own; only feeds `ManifestAuthoring`
626
- * for the (currently unreachable, see that class's doc comment) 1-world
627
- * "Create vgai.game.json" path. */
628
- function synthesizeRawManifest(manifest: EditModeManifest): Record<string, unknown> {
629
- return {
630
- manifestVersion: 1,
631
- name: getCurrentProject()?.config.name ?? 'My Game',
632
- version: '0.1.0',
633
- engine: { version: '0.0.0' },
634
- worlds: manifest.worlds.map((w) => ({
635
- id: w.id,
636
- kind: w.kind,
637
- ...(w.scene !== undefined ? { scene: w.scene } : {}),
638
- ...(w.entry !== undefined ? { entry: w.entry } : {}),
639
- zOrder: w.zOrder ?? 0,
640
- pausable: w.pausable ?? true,
641
- })),
642
- };
643
- }