@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
@@ -6,22 +6,38 @@
6
6
  * Projects can also be switched at runtime via `switchProject()`.
7
7
  */
8
8
 
9
- import { getCurrentServerProject, openProjectOnServer } from './editor-api';
9
+ import { loadGameManifest } from '@engine/manifest/load';
10
+ import { BUNDLED_EDITOR_COMPATIBILITY } from './build-identity';
11
+ import {
12
+ getCurrentServerProject,
13
+ openProjectOnServer,
14
+ requireEditorCompatibility,
15
+ } from './editor-api';
16
+ import { assertProjectCompatibility } from './editor-compatibility';
10
17
  import { getEditorMode } from './editor-mode';
18
+ import { type HierarchyProjection, parseHierarchyProjection } from './hierarchy-projection';
11
19
  import { getStorageBackend, restorePersistedProjectRoot } from './storage';
12
20
  import { readProjectManifest, seedIfEmpty } from './storage/seed';
13
21
 
14
22
  export interface ProjectConfig {
23
+ /** Project-file schema version. Kept distinct from game and engine versions. */
24
+ manifestVersion: 2;
15
25
  name: string;
26
+ /** The authored game's own release version. */
27
+ version: string;
28
+ /** Exact engine identity the project is pinned to. */
29
+ engine: { version: string };
30
+ /** Editor-only organization. It never changes which runtime roots mount. */
31
+ authoring?: { hierarchy?: HierarchyProjection | undefined } | undefined;
16
32
  resolution?: { width: number; height: number } | undefined;
17
33
  appId?: string | undefined;
34
+ /** Stable preferred port generated with the project; launchers may fall forward when occupied. */
35
+ editorPort?: number | undefined;
18
36
  /**
19
37
  * The project's default scene path, or `null` when the manifest's first
20
38
  * world declares no scene (e.g. an `entry`-only world — §7.1-12). Always
21
- * present when synthesized from a manifest (`readProjectView`, server-side);
22
- * a hand-authored legacy `project.json` may still omit the key entirely
23
- * (verbatim pass-through — §1C), which `loadInitialScene` treats the same
24
- * as `null`. Consumers must NOT default a missing/null value to a guessed
39
+ * present when derived from the manifest (`readProjectView`, server-side).
40
+ * Consumers must NOT default a missing/null value to a guessed
25
41
  * path — that was the bug (initial-scene.ts guessing
26
42
  * 'scenes/default.vscn.json' and silently writing a placeholder scene).
27
43
  */
@@ -31,7 +47,7 @@ export interface ProjectConfig {
31
47
  export interface ActiveProject {
32
48
  /** Absolute path to the project root (as passed via URL param / env). */
33
49
  rootPath: string;
34
- /** Parsed project.json contents. */
50
+ /** Parsed vgai.game.json project configuration plus derived editor fields. */
35
51
  config: ProjectConfig;
36
52
  /**
37
53
  * Set when this project is a BUNDLED EXAMPLE opened in the hosted (browser)
@@ -50,13 +66,74 @@ function _notify(): void {
50
66
  for (const fn of _listeners) fn();
51
67
  }
52
68
 
53
- /** Open a project by its root path. Fetches project.json from the server. */
69
+ function describeManifestError(error: unknown): string {
70
+ const issues =
71
+ error && typeof error === 'object' && Array.isArray((error as { issues?: unknown }).issues)
72
+ ? (error as { issues: unknown[] }).issues
73
+ : null;
74
+ if (!issues) return error instanceof Error ? error.message : String(error);
75
+
76
+ return issues
77
+ .map((issue) => {
78
+ if (!issue || typeof issue !== 'object') return String(issue);
79
+ const candidate = issue as { path?: unknown; message?: unknown };
80
+ const path = Array.isArray(candidate.path)
81
+ ? candidate.path.map(String).join('.') || '(root)'
82
+ : '(root)';
83
+ const message =
84
+ typeof candidate.message === 'string' ? candidate.message : 'Invalid manifest value';
85
+ return `${path}: ${message}`;
86
+ })
87
+ .join('\n');
88
+ }
89
+
90
+ /** Open a project by its root path. Fetches the sole v2 project manifest. */
54
91
  export async function openProject(rootPath: string): Promise<ActiveProject> {
55
- const res = await fetch('/project.json');
92
+ const compatibility = await requireEditorCompatibility();
93
+ const res = await fetch('/vgai.game.json');
56
94
  if (!res.ok) {
57
- throw new Error(`Failed to load project.json: ${res.status} ${res.statusText}`);
95
+ throw new Error(
96
+ `Could not open project at ${rootPath}.\n` +
97
+ `Failed to load vgai.game.json: ${res.status} ${res.statusText}`,
98
+ );
99
+ }
100
+ let rawManifest: unknown;
101
+ try {
102
+ rawManifest = await res.json();
103
+ } catch (error) {
104
+ throw new Error(
105
+ `Could not open project at ${rootPath}.\n` +
106
+ `vgai.game.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
107
+ );
108
+ }
109
+ assertProjectCompatibility(
110
+ rawManifest && typeof rawManifest === 'object'
111
+ ? (rawManifest as { manifestVersion?: unknown; engine?: { version?: unknown } })
112
+ : {},
113
+ compatibility,
114
+ );
115
+ let manifest: ReturnType<typeof loadGameManifest>;
116
+ try {
117
+ manifest = loadGameManifest(rawManifest);
118
+ } catch (error) {
119
+ throw new Error(
120
+ `Could not open project at ${rootPath}.\n` +
121
+ `vgai.game.json does not match the current project format:\n${describeManifestError(error)}`,
122
+ );
58
123
  }
59
- const config = (await res.json()) as ProjectConfig;
124
+ const config: ProjectConfig = {
125
+ manifestVersion: manifest.manifestVersion,
126
+ name: manifest.name,
127
+ version: manifest.version,
128
+ engine: manifest.engine,
129
+ appId: manifest.appId,
130
+ editorPort: manifest.editorPort,
131
+ resolution: manifest.resolution,
132
+ authoring: manifest.authoring
133
+ ? { hierarchy: parseHierarchyProjection(manifest.authoring.hierarchy) }
134
+ : undefined,
135
+ defaultScene: manifest.roots.find((root) => root.scene !== undefined)?.scene ?? null,
136
+ };
60
137
  _activeProject = { rootPath, config };
61
138
  _notify();
62
139
  return _activeProject;
@@ -114,8 +191,10 @@ export function getProjectPathFromURL(): string | null {
114
191
  * Used in production where __VGAI_PROJECT_PATH__ isn't available at build time.
115
192
  */
116
193
  export async function detectServerProject(): Promise<ActiveProject | null> {
194
+ const compatibility = await requireEditorCompatibility();
117
195
  const info = await getCurrentServerProject();
118
196
  if (!info) return null;
197
+ assertProjectCompatibility(info.config, compatibility);
119
198
  _activeProject = { rootPath: info.path, config: info.config as ProjectConfig };
120
199
  _notify();
121
200
  return _activeProject;
@@ -157,6 +236,7 @@ export async function detectProject(): Promise<ActiveProject | null> {
157
236
  const backend = getStorageBackend();
158
237
  await seedIfEmpty(backend);
159
238
  const config = await readProjectManifest(backend);
239
+ assertProjectCompatibility(config, BUNDLED_EDITOR_COMPATIBILITY);
160
240
  _activeProject = { rootPath: restored?.status === 'granted' ? 'fsa' : 'browser', config };
161
241
  _notify();
162
242
  return _activeProject;
@@ -0,0 +1,112 @@
1
+ import { connectOperationFileEvents } from './asset-events';
2
+ import { getEditorMode } from './editor-mode';
3
+ import { onProjectChange } from './project-manager';
4
+
5
+ export interface ProjectOperationCatalogEntry {
6
+ name: string;
7
+ summary: string;
8
+ description: string;
9
+ sourcePath: string;
10
+ inputSchema: unknown;
11
+ resultSchema: unknown;
12
+ errors: Array<{ code: string; summary: string; dataSchema?: unknown }>;
13
+ requires: Record<string, boolean | undefined>;
14
+ host: 'node' | 'editor-browser' | 'runtime-page';
15
+ mutates: boolean;
16
+ supportsDryRun: boolean;
17
+ longRunning: boolean;
18
+ permission: { risk: 'read' | 'write' | 'destructive'; summary: string };
19
+ }
20
+
21
+ export interface ProjectOperationCatalog {
22
+ operations: ProjectOperationCatalogEntry[];
23
+ loadErrors: Array<{ sourcePath: string; message: string }>;
24
+ }
25
+
26
+ export type ProjectOperationOutcome =
27
+ | { ok: true; data: unknown }
28
+ | { ok: false; error: { code: string; message: string; data?: unknown; issues?: unknown[] } };
29
+
30
+ const EMPTY_CATALOG: ProjectOperationCatalog = { operations: [], loadErrors: [] };
31
+ let catalog = EMPTY_CATALOG;
32
+ let version = 0;
33
+ const listeners = new Set<() => void>();
34
+
35
+ function publish(next: ProjectOperationCatalog): void {
36
+ catalog = next;
37
+ version++;
38
+ for (const listener of listeners) listener();
39
+ }
40
+
41
+ export function getProjectOperations(): ProjectOperationCatalog {
42
+ return catalog;
43
+ }
44
+
45
+ export function projectOperationsVersion(): number {
46
+ return version;
47
+ }
48
+
49
+ export function subscribeProjectOperations(listener: () => void): () => void {
50
+ listeners.add(listener);
51
+ return () => listeners.delete(listener);
52
+ }
53
+
54
+ export async function refreshProjectOperations(): Promise<ProjectOperationCatalog> {
55
+ if (getEditorMode() === 'browser') {
56
+ publish(EMPTY_CATALOG);
57
+ return EMPTY_CATALOG;
58
+ }
59
+ try {
60
+ const response = await fetch('/__editor/project-operations');
61
+ if (!response.ok) throw new Error(`catalog request failed (${response.status})`);
62
+ const next = (await response.json()) as ProjectOperationCatalog;
63
+ publish(next);
64
+ return next;
65
+ } catch (error) {
66
+ const next = {
67
+ operations: [],
68
+ loadErrors: [
69
+ {
70
+ sourcePath: 'src/operations',
71
+ message: error instanceof Error ? error.message : String(error),
72
+ },
73
+ ],
74
+ };
75
+ publish(next);
76
+ return next;
77
+ }
78
+ }
79
+
80
+ export async function runProjectOperation(
81
+ name: string,
82
+ input: unknown,
83
+ options: { confirm?: boolean } = {},
84
+ ): Promise<ProjectOperationOutcome> {
85
+ const response = await fetch('/__editor/project-operations/run', {
86
+ method: 'POST',
87
+ headers: { 'Content-Type': 'application/json' },
88
+ body: JSON.stringify({ name, input, confirm: options.confirm === true }),
89
+ });
90
+ const outcome = (await response.json()) as ProjectOperationOutcome;
91
+ if (!outcome || typeof outcome !== 'object' || typeof outcome.ok !== 'boolean') {
92
+ throw new Error(`Operation request returned an invalid response (${response.status}).`);
93
+ }
94
+ return outcome;
95
+ }
96
+
97
+ /** Editor-init lifecycle for Node-side project operation discovery. */
98
+ export function startProjectOperationDiscovery(): () => void {
99
+ void refreshProjectOperations();
100
+ const stopProjectWatch = onProjectChange(() => void refreshProjectOperations());
101
+ const stopFileWatch = connectOperationFileEvents(() => void refreshProjectOperations());
102
+ return () => {
103
+ stopProjectWatch();
104
+ stopFileWatch();
105
+ };
106
+ }
107
+
108
+ export function __resetProjectOperationsForTest(): void {
109
+ catalog = EMPTY_CATALOG;
110
+ version = 0;
111
+ listeners.clear();
112
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Project Storybook discovery lifecycle. Story navigation is a project
3
+ * capability, not a side effect of mounting a particular React world or
4
+ * opening a Story document, so the editor starts discovery once at init.
5
+ */
6
+
7
+ import { connectStoryFileEvents } from './asset-events';
8
+ import { getCurrentProject, onProjectChange } from './project-manager';
9
+ import { refreshProjectStories } from './stories/story-registry';
10
+
11
+ function refresh(): void {
12
+ void refreshProjectStories(getCurrentProject());
13
+ }
14
+
15
+ /** Discover immediately, then re-discover on project switches and CSF file
16
+ * add/remove events. In-flight refreshes are allowed to settle. */
17
+ export function startProjectStoryDiscovery(): () => void {
18
+ refresh();
19
+ const stopProjectWatch = onProjectChange(refresh);
20
+ const stopStoryFileWatch = connectStoryFileEvents(refresh);
21
+ return () => {
22
+ stopProjectWatch();
23
+ stopStoryFileWatch();
24
+ };
25
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Project-tool discovery LIFECYCLE (W0, docs/EDITOR-WORKSPACE-SHELL-TODO.md
3
+ * §7.2/§9 W0 — "project tool discovery must move out of `BottomPanel` and
4
+ * into project/editor initialization").
5
+ *
6
+ * Until W0, `BottomPanel.tsx` anchored discovery in its own mount effects:
7
+ * the dock was both the DISCOVERER and one consumer of project tools, so
8
+ * inspector-placement tools only loaded because the bottom dock happened to
9
+ * be mounted. This module owns that lifecycle instead; the editor shell
10
+ * starts it once at editor initialization (`EditorContext.tsx`'s
11
+ * `EditorProvider`), and the placement hosts (center tool documents,
12
+ * workspace-utility registrations — `components/tool-documents.tsx`,
13
+ * tool-loader.ts's `getGlobalTools`/`subscribeGlobalTools`) are plain
14
+ * consumers of the already-discovered store — proven by
15
+ * `test/project-tool-discovery.test.ts`, which runs discovery with no
16
+ * component module imported at all.
17
+ *
18
+ * The pass itself is unchanged (`refreshProjectTools`, tool-loader.ts):
19
+ * discover on start, re-discover when the open project changes, and
20
+ * re-discover on server-watched `src/tools/` add/unlink events (W6a,
21
+ * docs/DATA-TOOLS-DESIGN.md §7). Edit-HMR stays where it was — tool-loader's
22
+ * own module-level `vgai:script-update` listener.
23
+ */
24
+
25
+ import { connectToolFileEvents } from './asset-events';
26
+ import { onProjectChange } from './project-manager';
27
+ import { refreshProjectTools } from './tool-loader';
28
+
29
+ /**
30
+ * Start the project-tool discovery lifecycle: one immediate discovery pass,
31
+ * plus re-discovery on project change and on tool-file add/unlink events.
32
+ * Returns a stop function that disconnects both subscriptions (the pass in
33
+ * flight is left to finish — `refreshProjectTools` never throws).
34
+ */
35
+ export function startProjectToolDiscovery(): () => void {
36
+ void refreshProjectTools();
37
+ const stopProjectWatch = onProjectChange(() => void refreshProjectTools());
38
+ const stopToolFileWatch = connectToolFileEvents(() => void refreshProjectTools());
39
+ return () => {
40
+ stopProjectWatch();
41
+ stopToolFileWatch();
42
+ };
43
+ }
@@ -10,7 +10,6 @@
10
10
  */
11
11
 
12
12
  import type { SceneEntity, SceneEnvironment, SceneFile } from '@engine/scene/scene-types';
13
- import type { UIRoot } from '@engine/scene/schema';
14
13
  import { getUserData } from '@engine/scene/user-data';
15
14
  import type * as THREE from 'three';
16
15
 
@@ -18,8 +17,6 @@ export interface SceneMetadata {
18
17
  version: number;
19
18
  name: string;
20
19
  environment?: SceneEnvironment;
21
- /** Scene-graph UI (the `ui` field) — preserved through load/serialize (A1/C9). */
22
- ui?: UIRoot[];
23
20
  }
24
21
 
25
22
  type Transform = NonNullable<SceneEntity['transform']>;
@@ -68,9 +65,6 @@ export function serializeScene(scene: THREE.Scene, meta: SceneMetadata): SceneFi
68
65
  if (meta.environment) {
69
66
  doc.environment = meta.environment;
70
67
  }
71
- if (meta.ui && meta.ui.length > 0) {
72
- doc.ui = meta.ui;
73
- }
74
68
 
75
69
  return doc;
76
70
  }
package/src/scene-sync.ts CHANGED
@@ -81,6 +81,7 @@ const prefabPreviewCache = new Map<string, PrefabFile>();
81
81
 
82
82
  /** Cache for loaded .mat.json materials. */
83
83
  const materialRefCache = new Map<string, SceneMaterial>();
84
+ let materialRefCacheGeneration = 0;
84
85
 
85
86
  /** Cache for processed environment map textures. */
86
87
  const envTextureCache = new Map<string, THREE.Texture>();
@@ -134,6 +135,7 @@ export function clearPrefabPreviewCache(): void {
134
135
 
135
136
  /** Invalidate the material ref cache (e.g. when a .mat.json is saved). */
136
137
  export function clearMaterialRefCache(): void {
138
+ materialRefCacheGeneration++;
137
139
  materialRefCache.clear();
138
140
  }
139
141
 
@@ -160,11 +162,12 @@ function resolveEntityMaterial(
160
162
  // unreachable/404 file).
161
163
  const ref = entity.materialRef;
162
164
  const id = entity.id;
163
- fetch(resolveUrl(ref))
165
+ const generation = materialRefCacheGeneration;
166
+ fetch(resolveUrl(ref), { cache: 'no-store' })
164
167
  .then((res) => (res.ok ? res.json() : null))
165
168
  .then((json) => (json === null ? null : parseMaterialFile(json, ref)))
166
169
  .then((file) => {
167
- if (!file) return;
170
+ if (!file || generation !== materialRefCacheGeneration) return;
168
171
  materialRefCache.set(ref, file.material);
169
172
  // Rebuild the entity's preview object with the loaded material
170
173
  if (id) {
@@ -384,6 +387,7 @@ export function updateEntityPreview(
384
387
 
385
388
  const existing = objectMap.get(entity.id);
386
389
  if (!existing) return;
390
+ const skeletonVisible = Boolean(getUserData(existing, 'skeletonVisible'));
387
391
 
388
392
  // Unregister old particle systems (only on the entity itself, not child entities)
389
393
  unregisterParticles(existing, batchedRenderer ?? null);
@@ -416,6 +420,7 @@ export function updateEntityPreview(
416
420
  visualEntity = resolveEntityMaterial(entity, objectMap, scene);
417
421
  }
418
422
  const newObj = createEntityObject(visualEntity);
423
+ setUserData(newObj, 'skeletonVisible', skeletonVisible);
419
424
  // Restore the original (unresolved) entity as the source of truth.
420
425
  // This preserves prefab ref, materialRef, and any instance-level overrides.
421
426
  setUserData(newObj, 'entity', JSON.parse(JSON.stringify(entity)) as SceneEntity);
@@ -72,8 +72,7 @@ function extractProjectScripts(
72
72
  if (!adapter && !setup) {
73
73
  throw new Error(
74
74
  `Project script at ${scriptEntry} exports neither \`adapter\` nor \`setup\` — every ` +
75
- 'first-party entry module exports at least one (see ' +
76
- 'packages/editor/template/src/scripts/main.ts).',
75
+ 'explicit first-party entry module exports at least one.',
77
76
  );
78
77
  }
79
78
 
@@ -109,7 +108,8 @@ export function setLoadedComponents(components: Record<string, GameComponentClas
109
108
  /**
110
109
  * Load project scripts from the given project path.
111
110
  *
112
- * The project's entry point is `src/scripts/main.ts`, which must export
111
+ * A legacy or explicitly declared project entry (defaulting to
112
+ * `src/scripts/main.ts` for compatibility) must export
113
113
  * EITHER (or both — the EDITOR prefers `setup`, which alone threads
114
114
  * `editorPreview` + the firstParty HMR brand, see
115
115
  * `resolveDefaultThreeAdapter`; standalone prefers `adapter`,
@@ -0,0 +1,57 @@
1
+ import { getUserData, setUserData } from '@engine/scene/user-data';
2
+ import * as THREE from 'three';
3
+ import { EDITOR_LAYER } from './editor-layers';
4
+
5
+ const SKELETON_HELPER_TYPE = 'skeletons';
6
+ const SKELETON_PARENT_COLOR = new THREE.Color(0x00e5ff);
7
+ const SKELETON_CHILD_COLOR = new THREE.Color(0xffb000);
8
+
9
+ function applyEditorSkeletonColors(helper: THREE.SkeletonHelper): void {
10
+ const colors = helper.geometry.getAttribute('color');
11
+ // SkeletonHelper emits two vertices per bone link. Three's stock pure
12
+ // blue/green endpoints disappear against vgai's dark viewport, especially
13
+ // in captures. Keep the native helper geometry, but use editor-contrast
14
+ // colors so the inspection affordance is actually visible.
15
+ for (let i = 0; i < colors.count; i += 2) {
16
+ colors.setXYZ(i, SKELETON_PARENT_COLOR.r, SKELETON_PARENT_COLOR.g, SKELETON_PARENT_COLOR.b);
17
+ if (i + 1 < colors.count) {
18
+ colors.setXYZ(i + 1, SKELETON_CHILD_COLOR.r, SKELETON_CHILD_COLOR.g, SKELETON_CHILD_COLOR.b);
19
+ }
20
+ }
21
+ colors.needsUpdate = true;
22
+ }
23
+
24
+ export function ensureSkeletonHelper(root: THREE.Object3D): THREE.SkeletonHelper | null {
25
+ let existing: THREE.SkeletonHelper | null = null;
26
+ let hasSkinnedMesh = false;
27
+ root.traverse((object) => {
28
+ if (getUserData(object, 'editorHelperType') === SKELETON_HELPER_TYPE) {
29
+ existing = object as THREE.SkeletonHelper;
30
+ }
31
+ if ((object as THREE.SkinnedMesh).isSkinnedMesh) hasSkinnedMesh = true;
32
+ });
33
+ if (existing || !hasSkinnedMesh) return existing;
34
+
35
+ const helper = new THREE.SkeletonHelper(root);
36
+ if (helper.bones.length === 0) {
37
+ helper.dispose();
38
+ return null;
39
+ }
40
+ applyEditorSkeletonColors(helper);
41
+
42
+ // SkeletonHelper normally expects to be a scene-root sibling. Keeping it
43
+ // under the entity makes lifecycle cleanup automatic, so use entity-local
44
+ // coordinates instead of applying the entity world transform twice.
45
+ helper.matrix = new THREE.Matrix4();
46
+ helper.matrixAutoUpdate = false;
47
+ helper.name = 'EditorSkeletonHelper';
48
+ helper.renderOrder = 999;
49
+ helper.visible = Boolean(getUserData(root, 'skeletonVisible'));
50
+ setUserData(helper, 'editorHelper', true);
51
+ setUserData(helper, 'editorHelperType', SKELETON_HELPER_TYPE);
52
+ setUserData(helper, 'skeletonEnabled', helper.visible);
53
+ helper.traverse((child) => child.layers.set(EDITOR_LAYER));
54
+ root.add(helper);
55
+ root.updateMatrixWorld(true);
56
+ return helper;
57
+ }
@@ -9,12 +9,15 @@
9
9
  * require routing the engine's asset pipeline through the backend.
10
10
  */
11
11
 
12
+ import { loadGameManifest } from '@engine/manifest/load';
12
13
  import { parseSceneFile } from '@engine/scene/parse';
14
+ import { BUNDLED_ENGINE_VERSION } from '../build-identity';
13
15
  import type { StorageBackend } from './types';
14
16
 
15
17
  export const STARTER_SCENE_PATH = 'scenes/default.vscn.json';
16
18
  const STARTER_INPUTMAP_PATH = 'inputmaps/default.inputmap.json';
17
- const PROJECT_MANIFEST_PATH = 'project.json';
19
+ const PROJECT_MANIFEST_PATH = 'vgai.game.json';
20
+ const STARTER_UI_PATH = 'src/ui/game.tsx';
18
21
 
19
22
  /** Minimal, asset-free scene: a camera, a light, a ground plane, and a box. */
20
23
  const STARTER_SCENE = {
@@ -64,10 +67,47 @@ const STARTER_INPUTMAP = {
64
67
  };
65
68
 
66
69
  const PROJECT_MANIFEST = {
70
+ manifestVersion: 2 as const,
67
71
  name: 'Browser Project',
68
- defaultScene: STARTER_SCENE_PATH,
72
+ version: '0.1.0',
73
+ engine: { version: BUNDLED_ENGINE_VERSION },
74
+ roots: [
75
+ { id: 'world', adapter: 'threejs', scene: STARTER_SCENE_PATH },
76
+ { id: 'ui', adapter: 'react', entry: STARTER_UI_PATH, zOrder: 100 },
77
+ ],
69
78
  };
70
79
 
80
+ const STARTER_UI = `export default function GameUI() {
81
+ return <div style={{ position: 'absolute', left: 24, top: 20, color: 'white' }}>Browser Project</div>;
82
+ }\n`;
83
+
84
+ async function refreshSeededManifestIdentity(backend: StorageBackend): Promise<void> {
85
+ if (!(await backend.exists(PROJECT_MANIFEST_PATH))) {
86
+ await backend.write(PROJECT_MANIFEST_PATH, JSON.stringify(PROJECT_MANIFEST, null, 2));
87
+ return;
88
+ }
89
+ try {
90
+ const current = JSON.parse(await backend.read(PROJECT_MANIFEST_PATH)) as Record<
91
+ string,
92
+ unknown
93
+ >;
94
+ const engine = current['engine'];
95
+ if (
96
+ current['name'] === PROJECT_MANIFEST.name &&
97
+ engine &&
98
+ typeof engine === 'object' &&
99
+ (engine as Record<string, unknown>)['version'] !== BUNDLED_ENGINE_VERSION
100
+ ) {
101
+ await backend.write(
102
+ PROJECT_MANIFEST_PATH,
103
+ JSON.stringify({ ...current, engine: { version: BUNDLED_ENGINE_VERSION } }, null, 2),
104
+ );
105
+ }
106
+ } catch {
107
+ // Manifest parsing reports its own actionable error when the project opens.
108
+ }
109
+ }
110
+
71
111
  /**
72
112
  * Seed the Starter project into `backend` if it has not been seeded yet.
73
113
  * Idempotent — returns immediately once a VALID default scene exists.
@@ -79,6 +119,7 @@ const PROJECT_MANIFEST = {
79
119
  * bare scene on every visit.
80
120
  */
81
121
  export async function seedIfEmpty(backend: StorageBackend): Promise<void> {
122
+ await refreshSeededManifestIdentity(backend);
82
123
  if (await backend.exists(STARTER_SCENE_PATH)) {
83
124
  // Validate the existing scene; only re-seed the scene file if it's broken.
84
125
  try {
@@ -91,16 +132,35 @@ export async function seedIfEmpty(backend: StorageBackend): Promise<void> {
91
132
  }
92
133
  await backend.write(STARTER_SCENE_PATH, JSON.stringify(STARTER_SCENE, null, 2));
93
134
  await backend.write(STARTER_INPUTMAP_PATH, JSON.stringify(STARTER_INPUTMAP, null, 2));
94
- await backend.write(PROJECT_MANIFEST_PATH, JSON.stringify(PROJECT_MANIFEST, null, 2));
135
+ await backend.write(STARTER_UI_PATH, STARTER_UI);
95
136
  }
96
137
 
97
138
  /** Read the project manifest from a backend, or a default if absent. */
98
- export async function readProjectManifest(
99
- backend: StorageBackend,
100
- ): Promise<{ name: string; defaultScene: string }> {
139
+ export async function readProjectManifest(backend: StorageBackend): Promise<{
140
+ manifestVersion: 2;
141
+ name: string;
142
+ version: string;
143
+ engine: { version: string };
144
+ defaultScene: string;
145
+ }> {
101
146
  try {
102
- return JSON.parse(await backend.read(PROJECT_MANIFEST_PATH));
147
+ const manifest = loadGameManifest(JSON.parse(await backend.read(PROJECT_MANIFEST_PATH)));
148
+ return {
149
+ manifestVersion: manifest.manifestVersion,
150
+ name: manifest.name,
151
+ version: manifest.version,
152
+ engine: manifest.engine,
153
+ defaultScene:
154
+ manifest.roots.find((candidate) => candidate.scene !== undefined)?.scene ??
155
+ STARTER_SCENE_PATH,
156
+ };
103
157
  } catch {
104
- return { ...PROJECT_MANIFEST };
158
+ return {
159
+ manifestVersion: PROJECT_MANIFEST.manifestVersion,
160
+ name: PROJECT_MANIFEST.name,
161
+ version: PROJECT_MANIFEST.version,
162
+ engine: PROJECT_MANIFEST.engine,
163
+ defaultScene: STARTER_SCENE_PATH,
164
+ };
105
165
  }
106
166
  }