@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
@@ -5,7 +5,9 @@
5
5
  * editor-multi-root-selection seam, generalized to N-ary (T6.1 slice 3,
6
6
  * docs/MULTI-WORLD-DESIGN.md §1.F — GAME-ROOT-DESIGN §6.4's v1 scope, verbatim):
7
7
  * one top-level GROUP NODE per world (`world:<id> (<kind>)`), the child adapter's
8
- * own roots nested beneath it. Routing is by GROUP-NODE OWNERSHIP (which child
8
+ * own roots nested beneath it. The project/game identity already lives in the
9
+ * editor chrome, so the authoring projection starts at those real world roots —
10
+ * it does not add a redundant synthetic Game row. Routing is by GROUP-NODE OWNERSHIP (which child
9
11
  * adapter's tree actually contains a given node id) — not the old 2-child `w2d:`
10
12
  * id-prefix sniff, which cannot generalize past exactly one 2D surface.
11
13
  *
@@ -37,6 +39,7 @@ import type {
37
39
  } from '@engine/adapter';
38
40
  import type * as THREE from 'three';
39
41
  import { editorConsole } from '../editor-console';
42
+ import type { HierarchyProjection, HierarchyProjectionGroup } from '../hierarchy-projection';
40
43
 
41
44
  /** One child world's authoring adapter, ordered as given to the constructor. */
42
45
  export interface CompositeChild {
@@ -44,6 +47,11 @@ export interface CompositeChild {
44
47
  readonly worldId: string;
45
48
  /** World render substrate kind — shown in the group node's label. */
46
49
  readonly kind: string;
50
+ /** Runtime manifest roots are the default. A nested authoring surface is
51
+ * adapter-owned content of its parent world, not another runtime world. */
52
+ readonly role?: 'world' | 'surface';
53
+ /** Optional user-facing label for a nested authoring surface. */
54
+ readonly label?: string;
47
55
  readonly adapter: AuthoringAdapter;
48
56
  /**
49
57
  * D12 (B4) — the world's INSTALL-TIME manifest zOrder (`world.zOrder ?? 0`),
@@ -54,11 +62,14 @@ export interface CompositeChild {
54
62
  * what "install-time" means here.
55
63
  */
56
64
  readonly zOrder?: number;
65
+ /** Manifest pausing policy, retained for read-only play-mode inspection. */
66
+ readonly pausable?: boolean;
67
+ /** Adapter-owned content declaration, retained for play-mode inspection. */
68
+ readonly content?: string;
57
69
  /**
58
70
  * C3 (docs/unified-world-editor/26-unified-editor-spec.md §4 C3) — nest
59
- * this child's group node UNDER another child's group node instead of at
60
- * the top level (directly under the synthetic `game` root, or as a bare
61
- * top-level root when there is no `gameRoot`). Used by the scene-UI child
71
+ * this child's group node UNDER another child's group node instead of
72
+ * directly in the hierarchy root list. Used by the scene-UI child
62
73
  * (`edit-mode-authoring.ts`'s UI child, `kind: 'scene-ui'`) to appear
63
74
  * "under its owning world" in the ONE hierarchy, per the C3 spec text.
64
75
  * Absent ⇒ today's flat top-level shape, unchanged.
@@ -67,7 +78,14 @@ export interface CompositeChild {
67
78
  }
68
79
 
69
80
  const GROUP_PREFIX = 'world:';
70
- const GAME_ROOT_ID = 'game';
81
+ const KIND_PREFIX = 'kind:';
82
+ const PROJECTION_PREFIX = 'projection:';
83
+
84
+ const KIND_LABELS: Readonly<Record<string, string>> = {
85
+ threejs: 'Three.js',
86
+ react: 'React',
87
+ pixijs: 'PixiJS',
88
+ };
71
89
 
72
90
  function groupNodeId(worldId: string): string {
73
91
  return `${GROUP_PREFIX}${worldId}`;
@@ -79,19 +97,34 @@ function isGroupNodeId(id: string): boolean {
79
97
  return id.startsWith(GROUP_PREFIX);
80
98
  }
81
99
 
100
+ function kindNodeId(kind: string): string {
101
+ return `${KIND_PREFIX}${kind}`;
102
+ }
103
+
104
+ function projectionNodeId(id: string): string {
105
+ return `${PROJECTION_PREFIX}${id}`;
106
+ }
107
+
108
+ function isOrganizationNodeId(id: string): boolean {
109
+ return id.startsWith(KIND_PREFIX) || id.startsWith(PROJECTION_PREFIX);
110
+ }
111
+
112
+ function humanizeId(id: string): string {
113
+ const words = id.replace(/[-_]+/g, ' ').trim();
114
+ return words ? words.replace(/\b\w/g, (letter) => letter.toUpperCase()) : id;
115
+ }
116
+
82
117
  /**
83
- * A4 (D8) — the game root's manifest-backed property surface, implemented by
118
+ * A4 (D8) — manifest-backed properties on each authored root, implemented by
84
119
  * `edit-mode-authoring.ts`'s `ManifestAuthoring` over the RAW `vgai.game.json`
85
120
  * (read-modify-write, never round-tripped through Zod). Defined here (not
86
121
  * imported from `edit-mode-authoring.ts`) to avoid a circular import — that
87
122
  * module already imports `CompositeAuthoringAdapter`.
88
123
  */
89
- export interface GameRootManifestProvider {
90
- /** True while no `vgai.game.json` exists on disk yet (a synthesized
91
- * in-memory manifest) — edits buffer in memory instead of writing. */
92
- readonly synthesized: boolean;
93
- getGameName(): string;
94
- setGameName(name: string): void;
124
+ export interface RootManifestProvider {
125
+ /** Adapter-owned content declaration shown on the world boundary (for
126
+ * example `Scene · scenes/default.vscn.json` or `Entry · src/ui/App.tsx`). */
127
+ getWorldContentSource?(worldId: string): string | undefined;
95
128
  getWorldZOrder(worldId: string): number;
96
129
  setWorldZOrder(worldId: string, value: number): void;
97
130
  getWorldPausable(worldId: string): boolean;
@@ -145,12 +178,13 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
145
178
  * mutating it here never aliases a caller's own array.
146
179
  */
147
180
  private readonly children: CompositeChild[];
148
- /** A4 (D8) — present only when the caller wants a synthetic game-root node
149
- * wrapping every world group (edit-mode's manifest-disclosing composite);
150
- * absent for every other composite construction (play-mode's
151
- * `installMultiWorldAuthoring`), which keeps `hierarchy.roots()` returning
152
- * world group nodes directly, UNCHANGED from before A4. */
153
- private readonly gameRoot: GameRootManifestProvider | null;
181
+ /** Optional writable manifest surface for world composition properties.
182
+ * Runtime still has one semantic Game root; the authoring hierarchy does not
183
+ * mirror that project-level wrapper because the editor chrome already owns it. */
184
+ private readonly manifest: RootManifestProvider | null;
185
+ /** Optional editor-only organization; never consulted by runtime mounting. */
186
+ private readonly projection: HierarchyProjection | null;
187
+ private readonly resolvedProjectionGroups: Array<HierarchyProjectionGroup & { roots: string[] }>;
154
188
 
155
189
  /**
156
190
  * N-ary (T6.1 slice 3): an ordered `{ worldId, kind, adapter }[]` — one group
@@ -161,19 +195,23 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
161
195
  * this slice (T6.1 slice 4, docs/MULTI-WORLD-DESIGN.md §1.H): its only
162
196
  * production call site was `world2d-mode.ts`'s dev-console-only hook, deleted
163
197
  * with the rest of the world2d silo. Every production caller now goes through
164
- * the N-ary array constructor (`play-mode.ts`'s `installMultiWorldAuthoring`).
198
+ * the N-ary array constructor (`play-mode.ts`'s `installPlayWorldAuthoring`).
165
199
  *
166
- * `gameRoot` (A4, D8) is an opt-in second argument: `edit-mode-authoring.ts`
167
- * passes a `ManifestAuthoring` instance to wrap every world group under one
168
- * synthetic `game` root; every other caller omits it and gets today's
169
- * shape (world groups as roots) byte-for-byte unchanged.
200
+ * `manifest` is an optional writable provider. Omitting it leaves root
201
+ * composition properties read-only.
170
202
  */
171
- constructor(children: ReadonlyArray<CompositeChild>, gameRoot?: GameRootManifestProvider) {
203
+ constructor(
204
+ children: ReadonlyArray<CompositeChild>,
205
+ manifest?: RootManifestProvider,
206
+ projection?: HierarchyProjection,
207
+ ) {
172
208
  this.children = [...children];
173
- this.gameRoot = gameRoot ?? null;
209
+ this.manifest = manifest ?? null;
210
+ this.projection = projection ?? null;
174
211
  if (children.length === 0) {
175
212
  throw new Error('CompositeAuthoringAdapter: at least one child world is required.');
176
213
  }
214
+ this.resolvedProjectionGroups = this.resolveProjectionGroups();
177
215
  this.capabilities = computeCapabilities(this.children);
178
216
  }
179
217
 
@@ -193,8 +231,7 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
193
231
  * No-op (loud warn, #18 discipline) when `worldId` doesn't name an existing
194
232
  * child — never silently routes to the wrong world or grows the array.
195
233
  * Callers are responsible for their own side effects around the swap
196
- * (undo-timeline registration for a newly-live adapter,
197
- * `store.notifyIngestEdit()` so panels re-render against the new child) —
234
+ * (`store.notifyIngestEdit()` so panels re-render against the new child) —
198
235
  * this method only owns the swap + capability recompute.
199
236
  */
200
237
  replaceChild(worldId: string, adapter: AuthoringAdapter): void {
@@ -212,9 +249,8 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
212
249
  // `{ ..., zOrder }`) because `zOrder` is an OPTIONAL property under this
213
250
  // repo's `exactOptionalPropertyTypes` — an explicit `zOrder: undefined`
214
251
  // is not the same as an absent property to the type checker.
215
- const { kind, zOrder } = this.children[index]!;
216
- this.children[index] =
217
- zOrder === undefined ? { worldId, kind, adapter } : { worldId, kind, adapter, zOrder };
252
+ const previous = this.children[index]!;
253
+ this.children[index] = { ...previous, worldId, adapter };
218
254
  this.capabilities = computeCapabilities(this.children);
219
255
  }
220
256
 
@@ -224,12 +260,17 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
224
260
  return groupNodeId(worldId);
225
261
  }
226
262
 
263
+ /** True for a read-only shell node manufactured only for hierarchy layout. */
264
+ isOrganizationNode(nodeId: string): boolean {
265
+ return isOrganizationNodeId(nodeId);
266
+ }
267
+
227
268
  /** The child that owns `id` — by ACTUAL ownership (its hierarchy resolves the
228
269
  * id), not by any prefix convention. `null` for a group-node id or an id no
229
270
  * child recognizes. O(children) per call; fine at editor-UI scale, and avoids
230
271
  * a cache that could go stale as a live tree adds/removes nodes. */
231
272
  private findOwnerChild(id: string): CompositeChild | null {
232
- if (isGroupNodeId(id)) return null;
273
+ if (isGroupNodeId(id) || isOrganizationNodeId(id)) return null;
233
274
  for (const child of this.children) {
234
275
  if (child.adapter.hierarchy.node(id)) return child;
235
276
  }
@@ -276,24 +317,17 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
276
317
  adapter: AuthoringAdapter;
277
318
  kind: string;
278
319
  zOrder: number;
320
+ role: 'world' | 'surface';
279
321
  }> {
280
322
  return this.children.map((c, index) => ({
281
323
  worldId: c.worldId,
282
324
  adapter: c.adapter,
283
325
  kind: c.kind,
284
326
  zOrder: c.zOrder ?? index,
327
+ role: c.role ?? 'world',
285
328
  }));
286
329
  }
287
330
 
288
- /** PUBLIC (A4, D8) — the synthetic game-root node's id when this composite
289
- * was built with a `gameRoot` manifest surface, else `null`. Mirrors
290
- * `groupNodeId()`'s "ask the adapter, don't hardcode the id" precedent —
291
- * the shell (`GameHierarchy.tsx`) reads this instead of the literal
292
- * string, so it never needs to know the reserved id itself. */
293
- get gameRootId(): string | null {
294
- return this.gameRoot ? GAME_ROOT_ID : null;
295
- }
296
-
297
331
  /**
298
332
  * C3 — a child declared with `parentWorldId` nests its group node UNDER
299
333
  * that parent's group node instead of at the top level: the parent's
@@ -307,16 +341,18 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
307
341
  const self = this.children.find((c) => c.worldId === worldId);
308
342
  const parentId = self?.parentWorldId
309
343
  ? groupNodeId(self.parentWorldId)
310
- : this.gameRoot
311
- ? GAME_ROOT_ID
312
- : null;
344
+ : this.parentForTopLevelWorld(worldId, kind);
313
345
  const nestedGroupIds = this.children
314
346
  .filter((c) => c.parentWorldId === worldId)
315
347
  .map((c) => groupNodeId(c.worldId));
316
348
  return {
317
349
  id: groupNodeId(worldId),
318
- label: `world:${worldId} (${kind})`,
319
- kind: 'group',
350
+ label:
351
+ self?.role === 'surface'
352
+ ? (self.label ?? `${worldId} (${kind})`)
353
+ : this.labelForWorld(worldId, kind),
354
+ role: self?.role === 'surface' ? 'document' : 'root',
355
+ kind,
320
356
  parentId,
321
357
  childIds: [...childRoots.map((r) => r.id), ...nestedGroupIds],
322
358
  flags: { runtimeOnly: true },
@@ -324,45 +360,126 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
324
360
  }
325
361
 
326
362
  /** C3 — every child NOT nested under another (`parentWorldId` unset): the
327
- * top-level group set, either wrapped under the synthetic `game` root or
328
- * returned directly (see callers). A nested child is reached only via its
363
+ * top-level group set. A nested child is reached only via its
329
364
  * parent's `childIds` (`groupNode` above), never listed here too — a node
330
365
  * in two parents' `childIds` would render twice / cycle the row walk. */
331
366
  private topLevelChildren(): CompositeChild[] {
332
367
  return this.children.filter((c) => !c.parentWorldId);
333
368
  }
334
369
 
335
- /** A4 (D8) — the synthetic `game` root wrapping every TOP-LEVEL world group
336
- * node (C3: a nested child, e.g. a scene-UI world, is reached through its
337
- * parent's own `childIds` instead — see `topLevelChildren`). Only ever
338
- * constructed when `this.gameRoot` is set. */
339
- private gameRootNode(): EditorNode {
370
+ private runtimeRootChildren(): CompositeChild[] {
371
+ return this.topLevelChildren().filter((c) => (c.role ?? 'world') === 'world');
372
+ }
373
+
374
+ private resolveProjectionGroups(): Array<HierarchyProjectionGroup & { roots: string[] }> {
375
+ const known = new Set(this.runtimeRootChildren().map((child) => child.worldId));
376
+ const placed = new Set<string>();
377
+ return (this.projection?.groups ?? []).flatMap((group) => {
378
+ const roots = group.roots.filter((rootId) => {
379
+ if (!known.has(rootId)) {
380
+ editorConsole.warn(
381
+ `[CompositeAuthoringAdapter] hierarchy group "${group.label}" references unknown root "${rootId}" — ignoring.`,
382
+ 'authoring',
383
+ );
384
+ return false;
385
+ }
386
+ if (placed.has(rootId)) {
387
+ editorConsole.warn(
388
+ `[CompositeAuthoringAdapter] hierarchy root "${rootId}" is referenced by more than one group; the tree projection uses its first placement.`,
389
+ 'authoring',
390
+ );
391
+ return false;
392
+ }
393
+ placed.add(rootId);
394
+ return true;
395
+ });
396
+ return roots.length > 0 ? [{ ...group, roots }] : [];
397
+ });
398
+ }
399
+
400
+ private projectionGroups(): Array<HierarchyProjectionGroup & { roots: string[] }> {
401
+ return this.resolvedProjectionGroups;
402
+ }
403
+
404
+ private projectionGroupFor(worldId: string): HierarchyProjectionGroup | undefined {
405
+ return this.projectionGroups().find((group) => group.roots.includes(worldId));
406
+ }
407
+
408
+ private unprojectedRoots(): CompositeChild[] {
409
+ const projected = new Set(this.projectionGroups().flatMap((group) => group.roots));
410
+ return this.runtimeRootChildren().filter((child) => !projected.has(child.worldId));
411
+ }
412
+
413
+ private rootsOfKind(kind: string): CompositeChild[] {
414
+ return this.unprojectedRoots().filter((child) => child.kind === kind);
415
+ }
416
+
417
+ private parentForTopLevelWorld(worldId: string, kind: string): string | null {
418
+ const group = this.projectionGroupFor(worldId);
419
+ if (group) return projectionNodeId(group.id);
420
+ return this.rootsOfKind(kind).length > 1 ? kindNodeId(kind) : null;
421
+ }
422
+
423
+ private labelForWorld(worldId: string, kind: string): string {
424
+ const configured = this.projection?.rootLabels?.[worldId]?.trim();
425
+ if (configured) return configured;
426
+ return this.rootsOfKind(kind).length === 1 && !this.projectionGroupFor(worldId)
427
+ ? (KIND_LABELS[kind] ?? humanizeId(kind))
428
+ : humanizeId(worldId);
429
+ }
430
+
431
+ private kindNode(kind: string): EditorNode {
432
+ return {
433
+ id: kindNodeId(kind),
434
+ label: KIND_LABELS[kind] ?? humanizeId(kind),
435
+ role: 'folder',
436
+ kind: 'group',
437
+ parentId: null,
438
+ childIds: this.rootsOfKind(kind).map((child) => groupNodeId(child.worldId)),
439
+ flags: { runtimeOnly: true },
440
+ };
441
+ }
442
+
443
+ private projectionGroupNode(group: HierarchyProjectionGroup & { roots: string[] }): EditorNode {
340
444
  return {
341
- id: GAME_ROOT_ID,
342
- label: this.gameRoot?.getGameName() || 'Game',
445
+ id: projectionNodeId(group.id),
446
+ label: group.label,
447
+ role: 'folder',
343
448
  kind: 'group',
344
449
  parentId: null,
345
- childIds: this.topLevelChildren().map((c) => groupNodeId(c.worldId)),
450
+ childIds: group.roots.map(groupNodeId),
346
451
  flags: { runtimeOnly: true },
347
452
  };
348
453
  }
349
454
 
455
+ private defaultProjectionNodeIds(): string[] {
456
+ const ids: string[] = [];
457
+ const seenKinds = new Set<string>();
458
+ for (const child of this.unprojectedRoots()) {
459
+ if (seenKinds.has(child.kind)) continue;
460
+ seenKinds.add(child.kind);
461
+ const peers = this.rootsOfKind(child.kind);
462
+ ids.push(peers.length > 1 ? kindNodeId(child.kind) : groupNodeId(child.worldId));
463
+ }
464
+ return ids;
465
+ }
466
+
350
467
  readonly hierarchy: HierarchyProvider = {
351
- roots: (): EditorNode[] => {
352
- // A4 (D8) — wrap under a synthetic `game` root ONLY when a manifest
353
- // surface was supplied at construction (edit-mode's composite); every
354
- // other composite (play-mode) returns world groups as roots directly,
355
- // exactly as before A4. Either way, only TOP-LEVEL groups are roots
356
- // (C3) — a nested child (e.g. scene-UI) is reached via its parent's
357
- // own `childIds`, never listed as a root of its own.
358
- if (this.gameRoot) return [this.gameRootNode()];
359
- return this.topLevelChildren().map((c) =>
360
- this.groupNode(c.worldId, c.kind, c.adapter.hierarchy.roots()),
361
- );
362
- },
468
+ roots: (): EditorNode[] => [
469
+ ...this.projectionGroups().map((group) => this.projectionGroupNode(group)),
470
+ ...this.defaultProjectionNodeIds()
471
+ .map((id) => this.hierarchy.node(id))
472
+ .filter((node): node is EditorNode => node !== null),
473
+ ],
363
474
  node: (id) => {
364
- if (id === GAME_ROOT_ID) {
365
- return this.gameRoot ? this.gameRootNode() : null;
475
+ if (id.startsWith(KIND_PREFIX)) {
476
+ const kind = id.slice(KIND_PREFIX.length);
477
+ return this.rootsOfKind(kind).length > 1 ? this.kindNode(kind) : null;
478
+ }
479
+ if (id.startsWith(PROJECTION_PREFIX)) {
480
+ const groupId = id.slice(PROJECTION_PREFIX.length);
481
+ const group = this.projectionGroups().find((candidate) => candidate.id === groupId);
482
+ return group ? this.projectionGroupNode(group) : null;
366
483
  }
367
484
  if (isGroupNodeId(id)) {
368
485
  const worldId = id.slice(GROUP_PREFIX.length);
@@ -380,7 +497,7 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
380
497
  return node.parentId === null ? { ...node, parentId: groupNodeId(owner.worldId) } : node;
381
498
  },
382
499
  object3D: (id) =>
383
- id === GAME_ROOT_ID || isGroupNodeId(id) ? null : this.route(id).hierarchy.object3D(id),
500
+ isGroupNodeId(id) || isOrganizationNodeId(id) ? null : this.route(id).hierarchy.object3D(id),
384
501
  idForObject3D: (o: THREE.Object3D) => {
385
502
  for (const child of this.children) {
386
503
  const id = child.adapter.hierarchy.idForObject3D(o);
@@ -400,7 +517,7 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
400
517
  return [...seen];
401
518
  },
402
519
  set: (ids) => {
403
- // A group-node id (or the game root) is not a real entity in any
520
+ // A group-node id is not a real entity in any
404
521
  // child's tree — route it specially rather than forwarding a bogus id
405
522
  // nobody owns. Route each REAL id to its owning child.
406
523
  //
@@ -419,9 +536,9 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
419
536
  // sharing the backing store reaches every other child too) — but
420
537
  // forward the SYNTHETIC ID ITSELF (not an empty clear) when one was
421
538
  // given. This is why `RightPanel.tsx`'s `[...store.selectedEntityIds][0]`
422
- // (and `Inspector.tsx`'s identical read) can show the game root's/
423
- // a world group's own properties: every stock adapter shares the SAME
424
- // `EditorStore`, so writing `'game'`/`'world:<id>'` into any one
539
+ // (and `Inspector.tsx`'s identical read) can show a world group's own
540
+ // properties: every stock adapter shares the SAME `EditorStore`, so
541
+ // writing `'world:<id>'` into any one
425
542
  // child's selection lands in that one shared `selectedEntityIds` set,
426
543
  // which both those raw-store reads AND this composite's OWN `get()`
427
544
  // (via the owning-adapter's `selection.get()`) then see identically —
@@ -429,7 +546,7 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
429
546
  const byChild = new Map<AuthoringAdapter, string[]>();
430
547
  let synthetic: string | null = null;
431
548
  for (const id of ids) {
432
- if (isGroupNodeId(id) || id === this.gameRootId) {
549
+ if (isGroupNodeId(id) || isOrganizationNodeId(id)) {
433
550
  synthetic = id; // last one wins — only used when NOTHING else claims byChild
434
551
  continue;
435
552
  }
@@ -461,24 +578,40 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
461
578
  };
462
579
 
463
580
  /**
464
- * A4 (D8) — the game root's `name` and each world group's `zOrder`/
465
- * `pausable`/`kind` are a MANIFEST concept, not something any child
581
+ * A4 (D8) — each adapter root's stable `id`, `zOrder`/`pausable`/`kind` are
582
+ * a MANIFEST concept, not something any child
466
583
  * adapter's own inspector knows about — intercepted here, BEFORE `route()`,
467
584
  * so a real per-world adapter is never asked about an id it doesn't own.
468
- * `null` when `id` isn't the game root or a group-node id (or there is no
469
- * `gameRoot` manifest surface at all), letting the caller fall through to
470
- * the ordinary routed behavior unchanged.
585
+ * Without a writable provider, these synthetic nodes still expose read-only
586
+ * composition facts instead of being routed into a child that cannot own them.
471
587
  */
472
- private gameRootProperties(id: string): PropertyDescriptor[] | null {
473
- if (!this.gameRoot) return null;
474
- if (id === GAME_ROOT_ID) {
475
- return [{ path: 'name', label: 'Name', type: 'string' }];
588
+ private rootProperties(id: string): PropertyDescriptor[] | null {
589
+ if (isOrganizationNodeId(id)) {
590
+ return [{ path: 'organization', label: 'Authoring Group', type: 'string', readonly: true }];
476
591
  }
477
- if (isGroupNodeId(id) && this.children.some((c) => groupNodeId(c.worldId) === id)) {
592
+ if (
593
+ isGroupNodeId(id) &&
594
+ this.children.some((c) => (c.role ?? 'world') === 'world' && groupNodeId(c.worldId) === id)
595
+ ) {
596
+ const child = this.children.find((candidate) => groupNodeId(candidate.worldId) === id);
478
597
  return [
598
+ { path: 'id', label: 'Root ID', type: 'string', readonly: true },
479
599
  { path: 'kind', label: 'Kind', type: 'string', readonly: true },
480
- { path: 'zOrder', label: 'Z Order', type: 'number' },
481
- { path: 'pausable', label: 'Pausable', type: 'boolean' },
600
+ ...(this.manifest?.getWorldContentSource || child?.content
601
+ ? ([{ path: 'content', label: 'Content', type: 'string', readonly: true }] as const)
602
+ : []),
603
+ {
604
+ path: 'zOrder',
605
+ label: 'Z Order',
606
+ type: 'number',
607
+ ...(this.manifest ? {} : { readonly: true }),
608
+ },
609
+ {
610
+ path: 'pausable',
611
+ label: 'Pausable',
612
+ type: 'boolean',
613
+ ...(this.manifest ? {} : { readonly: true }),
614
+ },
482
615
  ];
483
616
  }
484
617
  return null;
@@ -486,44 +619,47 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
486
619
 
487
620
  readonly inspector: InspectorProvider = {
488
621
  properties: (id): PropertyDescriptor[] =>
489
- this.gameRootProperties(id) ?? this.route(id).inspector?.properties(id) ?? [],
622
+ this.rootProperties(id) ?? this.route(id).inspector?.properties(id) ?? [],
490
623
  get: (id, path) => {
491
- if (this.gameRoot) {
492
- if (id === GAME_ROOT_ID) {
493
- return path === 'name' ? this.gameRoot.getGameName() : undefined;
624
+ if (isOrganizationNodeId(id)) {
625
+ return path === 'organization' ? 'Editor-only hierarchy projection' : undefined;
626
+ }
627
+ const worldId = isGroupNodeId(id) ? id.slice(GROUP_PREFIX.length) : null;
628
+ const child = worldId ? this.children.find((c) => c.worldId === worldId) : undefined;
629
+ if (child && (child.role ?? 'world') === 'world') {
630
+ if (path === 'id') return child.worldId;
631
+ if (path === 'kind') return child.kind;
632
+ if (path === 'content') {
633
+ return this.manifest?.getWorldContentSource?.(worldId!) ?? child.content;
494
634
  }
495
- const worldId = isGroupNodeId(id) ? id.slice(GROUP_PREFIX.length) : null;
496
- const child = worldId ? this.children.find((c) => c.worldId === worldId) : undefined;
497
- if (child) {
498
- if (path === 'kind') return child.kind;
499
- if (path === 'zOrder') return this.gameRoot.getWorldZOrder(worldId!);
500
- if (path === 'pausable') return this.gameRoot.getWorldPausable(worldId!);
501
- return undefined;
635
+ if (path === 'zOrder') {
636
+ return this.manifest?.getWorldZOrder(worldId!) ?? child.zOrder ?? 0;
502
637
  }
638
+ if (path === 'pausable') {
639
+ return this.manifest?.getWorldPausable(worldId!) ?? child.pausable ?? true;
640
+ }
641
+ return undefined;
503
642
  }
504
643
  return this.route(id).inspector?.get(id, path);
505
644
  },
506
645
  set: (id, path, value) => {
507
- if (this.gameRoot) {
508
- if (id === GAME_ROOT_ID) {
509
- if (path === 'name') this.gameRoot.setGameName(String(value));
510
- return;
511
- }
512
- const worldId = isGroupNodeId(id) ? id.slice(GROUP_PREFIX.length) : null;
513
- const child = worldId ? this.children.find((c) => c.worldId === worldId) : undefined;
514
- if (child) {
515
- if (path === 'zOrder') this.gameRoot.setWorldZOrder(worldId!, Number(value));
516
- else if (path === 'pausable') this.gameRoot.setWorldPausable(worldId!, Boolean(value));
517
- return;
646
+ const worldId = isGroupNodeId(id) ? id.slice(GROUP_PREFIX.length) : null;
647
+ const child = worldId ? this.children.find((c) => c.worldId === worldId) : undefined;
648
+ if (child && (child.role ?? 'world') === 'world') {
649
+ if (this.manifest && path === 'zOrder') {
650
+ this.manifest.setWorldZOrder(worldId!, Number(value));
651
+ } else if (this.manifest && path === 'pausable') {
652
+ this.manifest.setWorldPausable(worldId!, Boolean(value));
518
653
  }
654
+ return;
519
655
  }
520
656
  this.route(id).inspector?.set(id, path, value);
521
657
  },
522
658
  remove: (id, path) => {
523
- // U2 — game-root/world-group synthetic props have no removable override;
659
+ // U2 — root-group synthetic props have no removable override;
524
660
  // everything else routes to the owning child (which optional-omits
525
661
  // `remove` unless it's the react-world adapter).
526
- if (this.gameRoot && (id === GAME_ROOT_ID || isGroupNodeId(id))) return;
662
+ if (isGroupNodeId(id) || isOrganizationNodeId(id)) return;
527
663
  this.route(id).inspector?.remove?.(id, path);
528
664
  },
529
665
  };
@@ -542,6 +678,7 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
542
678
  private resolveStructureTarget(
543
679
  parentId: string | null | undefined,
544
680
  ): { child: CompositeChild; forwardParentId: string | undefined } | null {
681
+ if (parentId != null && isOrganizationNodeId(parentId)) return null;
545
682
  if (parentId != null && isGroupNodeId(parentId)) {
546
683
  const worldId = parentId.slice(GROUP_PREFIX.length);
547
684
  const child = this.children.find((c) => c.worldId === worldId);
@@ -549,7 +686,16 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
549
686
  }
550
687
  if (parentId != null) {
551
688
  const child = this.findOwnerChild(parentId);
552
- return child ? { child, forwardParentId: parentId } : null;
689
+ if (!child) return null;
690
+ const parent = child.adapter.hierarchy.node(parentId);
691
+ return {
692
+ child,
693
+ // Documents and design states organize an adapter's native roots;
694
+ // they are not native parent ids. The structure contract's root
695
+ // spelling is `undefined`/`null`, so forward that explicitly.
696
+ forwardParentId:
697
+ parent?.role === 'document' || parent?.role === 'story' ? undefined : parentId,
698
+ };
553
699
  }
554
700
  const child = this.children.find((c) => c.adapter.structure);
555
701
  return child ? { child, forwardParentId: undefined } : null;
@@ -578,7 +724,7 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
578
724
  return target.child.adapter.structure.create(kind, target.forwardParentId);
579
725
  },
580
726
  remove: (id) => {
581
- if (isGroupNodeId(id)) return; // a whole world isn't removable through this seam
727
+ if (isGroupNodeId(id) || isOrganizationNodeId(id)) return;
582
728
  const owner = this.findOwnerChild(id);
583
729
  if (!owner?.adapter.structure) {
584
730
  editorConsole.warn(
@@ -594,8 +740,35 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
594
740
  // doc comment).
595
741
  return owner.adapter.structure.remove(id);
596
742
  },
743
+ removeMany: async (ids) => {
744
+ const groups = new Map<CompositeChild, string[]>();
745
+ for (const id of ids) {
746
+ if (isGroupNodeId(id) || isOrganizationNodeId(id)) continue;
747
+ const owner = this.findOwnerChild(id);
748
+ if (!owner?.adapter.structure) {
749
+ editorConsole.warn(
750
+ `[CompositeAuthoringAdapter] removeMany: no owner/structure for id "${id}"`,
751
+ 'authoring',
752
+ );
753
+ continue;
754
+ }
755
+ const ownedIds = groups.get(owner);
756
+ if (ownedIds) ownedIds.push(id);
757
+ else groups.set(owner, [id]);
758
+ }
759
+
760
+ // Preserve each child's strongest atomicity guarantee. In the common case
761
+ // (one world's multi-selection), this is one child batch and therefore one
762
+ // history transaction. Cross-world selections remain one ordered batch per
763
+ // owner because no child can soundly mutate another world's document.
764
+ for (const [owner, ownedIds] of groups) {
765
+ const structure = owner.adapter.structure!;
766
+ if (structure.removeMany) await structure.removeMany(ownedIds);
767
+ else for (const id of ownedIds) await structure.remove(id);
768
+ }
769
+ },
597
770
  duplicate: (id) => {
598
- if (isGroupNodeId(id)) return id; // no-op — nothing to duplicate
771
+ if (isGroupNodeId(id) || isOrganizationNodeId(id)) return id;
599
772
  const owner = this.findOwnerChild(id);
600
773
  if (!owner?.adapter.structure) {
601
774
  editorConsole.warn(
@@ -622,16 +795,21 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
622
795
  const newOwner = this.findOwnerChild(newParentId);
623
796
  if (!newOwner || newOwner.worldId !== owner.worldId) {
624
797
  editorConsole.warn(
625
- `[CompositeAuthoringAdapter] reparent: refusing to move "${id}" across worlds ` +
798
+ `[CompositeAuthoringAdapter] reparent: refusing to move "${id}" across roots ` +
626
799
  `(target parent "${newParentId}" is not in world "${owner.worldId}")`,
627
800
  'authoring',
628
801
  );
629
802
  return;
630
803
  }
804
+ const newParent = newOwner.adapter.hierarchy.node(newParentId);
805
+ if (newParent?.role === 'document' || newParent?.role === 'story') {
806
+ owner.adapter.structure.reparent(id, null);
807
+ return;
808
+ }
631
809
  owner.adapter.structure.reparent(id, newParentId);
632
810
  },
633
811
  reorder: (id, beforeSiblingId) => {
634
- if (isGroupNodeId(id)) return;
812
+ if (isGroupNodeId(id) || isOrganizationNodeId(id)) return;
635
813
  const owner = this.findOwnerChild(id);
636
814
  if (!owner) return;
637
815
  // Cross-world guard: a non-null sibling anchor MUST belong to the same
@@ -656,7 +834,7 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
656
834
  // instead since they're triggered from an explicit, always-visible menu
657
835
  // item, same as duplicate/delete).
658
836
  wrap: (id, wrapperTag) => {
659
- if (isGroupNodeId(id)) return;
837
+ if (isGroupNodeId(id) || isOrganizationNodeId(id)) return;
660
838
  const owner = this.findOwnerChild(id);
661
839
  if (!owner?.adapter.structure?.wrap) {
662
840
  editorConsole.warn(
@@ -668,7 +846,7 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
668
846
  owner.adapter.structure.wrap(id, wrapperTag);
669
847
  },
670
848
  unwrap: (id) => {
671
- if (isGroupNodeId(id)) return;
849
+ if (isGroupNodeId(id) || isOrganizationNodeId(id)) return;
672
850
  const owner = this.findOwnerChild(id);
673
851
  if (!owner?.adapter.structure?.unwrap) {
674
852
  editorConsole.warn(
@@ -703,8 +881,12 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
703
881
  const owner = this.findOwnerChild(nodeId);
704
882
  // Unknown real id: same "first child" floor `route()` uses elsewhere, forwarding
705
883
  // the id unchanged (parity with the pre-existing bogus-id behavior).
884
+ const node = owner?.adapter.hierarchy.node(nodeId);
706
885
  return owner
707
- ? { adapter: owner.adapter, forwardId: nodeId }
886
+ ? {
887
+ adapter: owner.adapter,
888
+ forwardId: node?.role === 'document' || node?.role === 'story' ? '' : nodeId,
889
+ }
708
890
  : { adapter: this.children[0]!.adapter, forwardId: nodeId };
709
891
  }
710
892
 
@@ -738,7 +920,7 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
738
920
 
739
921
  /**
740
922
  * D8 — union of every child's own {@link RootGroup}s, each group id prefixed
741
- * `<worldId>:` so groups from different worlds never collide. `activate`
923
+ * `<worldId>:` so groups from different roots never collide. `activate`
742
924
  * strips the prefix back off to find the owning child and the child's own
743
925
  * (unprefixed) group id.
744
926
  */
@@ -844,9 +1026,8 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
844
1026
  * failure without aborting the rest; `destination` joins child destinations
845
1027
  * with `' + '`.
846
1028
  *
847
- * No own undo: the global timeline (`undo-timeline.ts`) already dispatches
848
- * per-adapter, and each child that supports undo registers ITSELF — wrapping
849
- * one in a Composite changes nothing about that registration.
1029
+ * History is resource-driven, so the composite needs no ordering or inverse
1030
+ * logic of its own; its children journal into the open project's service.
850
1031
  */
851
1032
  get persistence(): PersistenceProvider {
852
1033
  // Always a REAL (never `undefined`) provider — see composite-authoring-adapter's
@@ -869,7 +1050,7 @@ export class CompositeAuthoringAdapter implements AuthoringAdapter {
869
1050
  } catch (err) {
870
1051
  console.error(
871
1052
  `[CompositeAuthoringAdapter] save failed for world "${c.worldId}" ` +
872
- `(destination: ${p.destination}) — continuing with remaining worlds:`,
1053
+ `(destination: ${p.destination}) — continuing with remaining roots:`,
873
1054
  err,
874
1055
  );
875
1056
  }