@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
@@ -36,7 +36,7 @@ import type { DataDoctorFinding } from './data-checks';
36
36
  /** One declared world from the manifest — enough identity to label a verdict row. */
37
37
  export interface DoctorDeclaredWorld {
38
38
  readonly worldId: string;
39
- /** `ResolvedWorldEntry.kind` (e.g. `'threejs'`/`'pixijs'`/`'react'`). */
39
+ /** `ResolvedAdapterRoot.kind` (e.g. `'threejs'`/`'pixijs'`/`'react'`). */
40
40
  readonly kind: string;
41
41
  /** `ResolvedAdapter['identity']` (e.g. `'ingest-three'`/`'module'`/`'default-three'`). */
42
42
  readonly identity: string;
@@ -108,6 +108,17 @@ export interface DoctorSiblingEvidence {
108
108
  readonly authoringPersist: boolean;
109
109
  }
110
110
 
111
+ /**
112
+ * Evidence from the editor's real Play composer. The world id is published
113
+ * only after `mountManifestWorlds` has returned and play-mode has installed
114
+ * the composite authoring surface, so presence proves both runtime mount and
115
+ * editor adoption without inventing kind-specific capture data.
116
+ */
117
+ export interface DoctorRuntimeEvidence {
118
+ readonly runtimeMounted: true;
119
+ readonly authoringPresent: true;
120
+ }
121
+
111
122
  /**
112
123
  * Every raw datum the browser-phase runner scraped off the live page —
113
124
  * data-in for {@link assembleDoctorReport}. Maps/sets are keyed by worldId so
@@ -128,6 +139,7 @@ export interface DoctorRawSignals {
128
139
  * byte-stable — an absent map reads exactly like an empty one.
129
140
  */
130
141
  readonly siblingEvidence?: ReadonlyMap<string, DoctorSiblingEvidence>;
142
+ readonly runtimeEvidence?: ReadonlyMap<string, DoctorRuntimeEvidence>;
131
143
  /**
132
144
  * Per-worldId react-ingest DOM evidence (D-L2, wave 10 Slice B,
133
145
  * docs/WAVE10-REACT-INGEST-DOM-AUTHORING-DESIGN.md) — checked in
@@ -150,6 +162,7 @@ export interface DoctorMountedVerdict extends DoctorVerdictBase {
150
162
  | DoctorIngestEvidence
151
163
  | DoctorModuleEvidence
152
164
  | DoctorSiblingEvidence
165
+ | DoctorRuntimeEvidence
153
166
  | DoctorReactIngestEvidence;
154
167
  }
155
168
 
@@ -165,7 +178,7 @@ export interface DoctorUnverifiableVerdict extends DoctorVerdictBase {
165
178
 
166
179
  export type DoctorVerdict = DoctorMountedVerdict | DoctorFailedVerdict | DoctorUnverifiableVerdict;
167
180
 
168
- /** True iff an ingest-capture evidence bundle proves a real, live, editable mount (D-A4's MOUNTED bar for ingest worlds). */
181
+ /** True iff an ingest-capture evidence bundle proves a real, live, editable mount (D-A4's MOUNTED bar for ingest roots). */
169
182
  function clearsIngestMountedBar(ev: DoctorIngestEvidence): boolean {
170
183
  const reflectedOk = ev.reflectedCount !== null && ev.reflectedCount > 0;
171
184
  const drawOk = ev.drawCountClimbed === true;
@@ -183,7 +196,7 @@ function ingestShortfallReason(worldId: string, ev: DoctorIngestEvidence): strin
183
196
  );
184
197
  }
185
198
 
186
- /** True iff react-ingest DOM evidence proves a real, live, editable mount (D-L3's MOUNTED bar for react-ingest worlds) — ALL four legs, never partial credit. */
199
+ /** True iff react-ingest DOM evidence proves a real, live, editable mount (D-L3's MOUNTED bar for react-ingest roots) — ALL four legs, never partial credit. */
187
200
  function clearsReactIngestMountedBar(ev: DoctorReactIngestEvidence): boolean {
188
201
  return (
189
202
  ev.elementCount > 0 && ev.hierarchyNodeCount > 0 && ev.reactRootPresent && ev.setPausedPresent
@@ -292,6 +305,11 @@ export function assembleDoctorReport(
292
305
  };
293
306
  }
294
307
 
308
+ const runtimeEv = raw.runtimeEvidence?.get(worldId);
309
+ if (runtimeEv) {
310
+ return { worldId, kind, identity, verdict: 'MOUNTED', evidence: runtimeEv };
311
+ }
312
+
295
313
  // D-H2 (docs/WAVE9-SIBLING-PERSISTENCE-DOCTOR-DESIGN.md Slice B): checked
296
314
  // LAST among the "some evidence exists" branches, after ingest/module
297
315
  // evidence and react presence — so a world whose ingest evidence exists
@@ -311,10 +329,16 @@ export function assembleDoctorReport(
311
329
  kind,
312
330
  identity,
313
331
  verdict: 'UNVERIFIABLE',
332
+ // Dry-run finding (docs/ACCEPTANCE-DRIVER-BUILD-PLAN.md Wave 6 ledger —
333
+ // "Doctor's UNVERIFIABLE world verdict reads oddly next to 'clean'"):
334
+ // agents reading a healthy world's report couldn't tell from the old
335
+ // wording alone whether UNVERIFIABLE counted as a problem. State the
336
+ // verdict semantics inline, up front, before the per-world detail.
314
337
  reason:
315
- `no headless health signal was observed for world "${worldId}" (kind "${kind}", identity ` +
316
- `"${identity}"), and no mount-failure entry named it either — either this identity has no ` +
317
- 'headless signal wired in doctor this slice, or the mount never ran at all.',
338
+ 'no headless health signal exists for this world kind yet; NOT a failure (doctor treats ' +
339
+ `it as non-blocking) — nothing was observed for world "${worldId}" (kind "${kind}", ` +
340
+ `identity "${identity}"), and no mount-failure entry named it either — either this ` +
341
+ 'identity has no headless signal wired in doctor this slice, or the mount never ran at all.',
318
342
  };
319
343
  });
320
344
  }
@@ -342,30 +366,50 @@ export interface DoctorStaticSummary {
342
366
  * 3 -> 4 (docs/DATA-TOOLS-DESIGN.md §6.6, W5 enforcement): the envelope
343
367
  * gained the optional top-level `data` section (data-asset findings), and an
344
368
  * ERROR-severity data finding now flips the exit code (see
345
- * {@link doctorExitCode}).
369
+ * {@link doctorExitCode}). Bumped 4 -> 5: ordinary manifest roots now gain
370
+ * the {@link DoctorRuntimeEvidence} variant after a real editor Play mount.
346
371
  */
347
- export const DOCTOR_VERSION = 4 as const;
372
+ export const DOCTOR_VERSION = 5 as const;
373
+
374
+ /**
375
+ * The acceptance-suite line item (docs/ACCEPTANCE-DRIVER-SPEC.md §2.4, Task
376
+ * 4.3): how many `tests/acceptance/*.spec.ts` files the project has, plus —
377
+ * iff that count is zero — the advisory message (`run-probe.ts`'s
378
+ * `ZERO_SPECS_MESSAGE`, the exact line `vgai probe` itself prints for an
379
+ * empty suite). ADVISORY ONLY: it never flips the exit code
380
+ * ({@link doctorExitCode} does not read it), so it is a purely additive
381
+ * optional envelope field — no {@link DOCTOR_VERSION} bump, the same
382
+ * shape-evolution pattern the optional `data` section established.
383
+ */
384
+ export interface DoctorAcceptanceSummary {
385
+ readonly specCount: number;
386
+ readonly advisory?: string;
387
+ }
348
388
 
349
389
  export interface DoctorJsonReport {
350
390
  readonly doctorVersion: typeof DOCTOR_VERSION;
351
391
  readonly static: DoctorStaticSummary;
352
- readonly worlds: readonly DoctorVerdict[];
392
+ readonly roots: readonly DoctorVerdict[];
353
393
  /** Data-asset check findings (docs/DATA-TOOLS-DESIGN.md §6.6) — present iff the caller ran the data scan (run-doctor.ts always does; older/other callers may not). */
354
394
  readonly data?: { readonly findings: readonly DataDoctorFinding[] };
395
+ /** Acceptance-suite summary ({@link DoctorAcceptanceSummary}) — present iff the caller scanned `tests/acceptance/` (run-doctor.ts always does; older/other callers may not). Advisory only — never affects the exit code. */
396
+ readonly acceptance?: DoctorAcceptanceSummary;
355
397
  }
356
398
 
357
- /** Build the stable `--json` envelope (D-A5: "stable shape, top-level `doctorVersion`, static-phase result included"; the version literal itself is {@link DOCTOR_VERSION}, currently `4`). The `data` section is included only when findings were actually scanned — never fabricated as an empty pass. */
399
+ /** Build the stable `--json` envelope (D-A5: "stable shape, top-level `doctorVersion`, static-phase result included"; the version literal itself is {@link DOCTOR_VERSION}, currently `5`). The `data` and `acceptance` sections are included only when their scans actually ran — never fabricated as an empty pass. */
358
400
  export function buildDoctorJsonReport(
359
401
  staticSummary: DoctorStaticSummary,
360
402
  worldVerdicts: readonly DoctorVerdict[],
361
403
  dataFindings?: readonly DataDoctorFinding[],
404
+ acceptance?: DoctorAcceptanceSummary,
362
405
  ): DoctorJsonReport {
363
406
  const base: DoctorJsonReport = {
364
407
  doctorVersion: DOCTOR_VERSION,
365
408
  static: staticSummary,
366
- worlds: worldVerdicts,
409
+ roots: worldVerdicts,
367
410
  };
368
- return dataFindings ? { ...base, data: { findings: dataFindings } } : base;
411
+ const withData = dataFindings ? { ...base, data: { findings: dataFindings } } : base;
412
+ return acceptance ? { ...withData, acceptance } : withData;
369
413
  }
370
414
 
371
415
  /**
@@ -373,7 +417,7 @@ export function buildDoctorJsonReport(
373
417
  * UNVERIFIABLE does not flip the exit code." Extended by W5 enforcement
374
418
  * (docs/DATA-TOOLS-DESIGN.md §6.6): an ERROR-severity data-asset finding
375
419
  * (schema failure, dangling ref) also exits 1 — warn/info findings never do,
376
- * mirroring how UNVERIFIABLE worlds never do.
420
+ * mirroring how UNVERIFIABLE roots never do.
377
421
  */
378
422
  export function doctorExitCode(
379
423
  staticSummary: DoctorStaticSummary,
package/src/editor-api.ts CHANGED
@@ -33,10 +33,22 @@
33
33
  */
34
34
 
35
35
  import { resolveUrl } from '@engine/loader';
36
+ import {
37
+ assertEditorCompatibility,
38
+ assertProjectCompatibility,
39
+ type EditorServerCompatibility,
40
+ editorServerUnavailableError,
41
+ isStartupRecovery,
42
+ missingCompatibilityError,
43
+ ProjectCompatibilityError,
44
+ } from './editor-compatibility';
36
45
  import { getEditorMode } from './editor-mode';
46
+ import type { HarnessChatAction, HarnessChatSnapshot } from './harness-chat-types';
47
+ import { parseHarnessChatSnapshot, unavailableHarnessChatSnapshot } from './harness-chat-types';
37
48
  import { getStorageBackend } from './storage';
38
49
 
39
50
  const BASE = '/__editor';
51
+ const EDITOR_CLIENT_ID = globalThis.crypto?.randomUUID?.() ?? `editor-${Date.now()}`;
40
52
 
41
53
  /** Browser mode has no Node dev server — every `/__editor/*` op below either
42
54
  * routes through `getStorageBackend()` or degrades explicitly; this helper
@@ -45,6 +57,55 @@ function isBrowserMode(): boolean {
45
57
  return getEditorMode() === 'browser';
46
58
  }
47
59
 
60
+ function parseEditorCompatibility(value: unknown): EditorServerCompatibility | null {
61
+ if (!value || typeof value !== 'object') return null;
62
+ const candidate = value as Partial<EditorServerCompatibility>;
63
+ if (
64
+ candidate.apiVersion !== 1 ||
65
+ (typeof candidate.engineVersion !== 'string' && candidate.engineVersion !== null) ||
66
+ typeof candidate.manifestVersion !== 'number' ||
67
+ typeof candidate.startedAt !== 'string' ||
68
+ !candidate.source ||
69
+ (candidate.source.state !== 'current' && candidate.source.state !== 'restart-required')
70
+ ) {
71
+ return null;
72
+ }
73
+ if (
74
+ candidate.source.state === 'restart-required' &&
75
+ (typeof candidate.source.changedPath !== 'string' ||
76
+ typeof candidate.source.changedAt !== 'string')
77
+ ) {
78
+ return null;
79
+ }
80
+ return candidate as EditorServerCompatibility;
81
+ }
82
+
83
+ /** Read the immutable identity of the local Node process serving this editor. */
84
+ export async function getEditorCompatibility(): Promise<EditorServerCompatibility | null> {
85
+ if (isBrowserMode()) return null;
86
+ let res: Response;
87
+ try {
88
+ res = await fetch(`${BASE}/compatibility`, { cache: 'no-store' });
89
+ } catch (error) {
90
+ throw editorServerUnavailableError(error instanceof Error ? error.message : String(error));
91
+ }
92
+ if (res.status >= 500) throw editorServerUnavailableError(`HTTP ${res.status}`);
93
+ if (!res.ok || !res.headers.get('content-type')?.includes('application/json')) return null;
94
+ try {
95
+ return parseEditorCompatibility(await res.json());
96
+ } catch {
97
+ return null;
98
+ }
99
+ }
100
+
101
+ /** Require a current handshake before any operation that activates a project. */
102
+ export async function requireEditorCompatibility(): Promise<EditorServerCompatibility> {
103
+ const identity = await getEditorCompatibility();
104
+ if (!identity) throw missingCompatibilityError();
105
+ assertEditorCompatibility(identity);
106
+ return identity;
107
+ }
108
+
48
109
  /**
49
110
  * Read a project text file, storage-backend first, then static HTTP fallback.
50
111
  *
@@ -71,6 +132,45 @@ export async function readProjectTextFile(path: string): Promise<string | null>
71
132
  return null;
72
133
  }
73
134
 
135
+ /**
136
+ * Check whether a project file exists WITHOUT reading its content.
137
+ *
138
+ * Storage-backend-first (`StorageBackend.exists`, already cheap on every
139
+ * backend: `HttpStorage.exists` stats via the parent directory LISTING
140
+ * (`/__editor/assets?dir=`), never a body fetch; OPFS/FSA resolve a handle),
141
+ * then an HTTP HEAD fallback against the static serving path for anything the
142
+ * storage layer doesn't know about. Prefer this over
143
+ * `readProjectTextFile(...) !== null` when only existence is needed — e.g. a
144
+ * restore-time verification probe — since that reads and discards the whole
145
+ * body.
146
+ *
147
+ * HEAD-support caveat (verified live with `curl -I` against the Vite dev
148
+ * server): real project files answer HEAD with a genuine Content-Type/
149
+ * Content-Length, so the fallback IS worth using. However the dev server's
150
+ * SPA fallback also answers a HEAD/GET for a MISSING path with 200 (it serves
151
+ * `index.html` rather than 404), so `res.ok` alone cannot fully distinguish
152
+ * "exists" from "missing" through this fallback path. This is not a new gap —
153
+ * the prior full-GET-based check had the identical blind spot — and it's only
154
+ * reached when the storage backend itself doesn't have the file (e.g. a
155
+ * static-shipped asset not yet materialized in OPFS/FSA); a false positive
156
+ * here just means a subsequent real open attempt fails through its own error
157
+ * path instead of at this cheap pre-check.
158
+ */
159
+ export async function projectFileExists(path: string): Promise<boolean> {
160
+ const key = path.replace(/^\//, '');
161
+ try {
162
+ if (await getStorageBackend().exists(key)) return true;
163
+ } catch {
164
+ /* fall through to HTTP */
165
+ }
166
+ try {
167
+ const res = await fetch(resolveUrl(path), { method: 'HEAD' });
168
+ return res.ok;
169
+ } catch {
170
+ return false;
171
+ }
172
+ }
173
+
74
174
  export interface AssetEntry {
75
175
  name: string;
76
176
  type: 'file' | 'directory';
@@ -172,7 +272,9 @@ let sharedRefCount = 0;
172
272
  */
173
273
  export function connectEvents(): EditorEventSource {
174
274
  if (!sharedSource) {
175
- sharedSource = new EventSource(`${BASE}/events`);
275
+ sharedSource = new EventSource(
276
+ `${BASE}/events?clientId=${encodeURIComponent(EDITOR_CLIENT_ID)}`,
277
+ );
176
278
  }
177
279
  const real = sharedSource;
178
280
  sharedRefCount++;
@@ -207,13 +309,91 @@ export function connectEvents(): EditorEventSource {
207
309
  };
208
310
  }
209
311
 
312
+ /** Fetch the complete project-scoped coding-harness Chat state. The static
313
+ * hosted editor cannot inspect local processes, so browser mode degrades
314
+ * honestly while keeping the permanent Chat surface understandable. */
315
+ export async function getHarnessChatSnapshot(): Promise<HarnessChatSnapshot> {
316
+ if (isBrowserMode()) {
317
+ return unavailableHarnessChatSnapshot(
318
+ 'Local coding sessions are available when this project is opened with `vgai edit`.',
319
+ );
320
+ }
321
+ try {
322
+ const res = await fetch(`${BASE}/harness-chat`);
323
+ if (!res.ok) {
324
+ const body = (await res.json().catch(() => null)) as { error?: string } | null;
325
+ return unavailableHarnessChatSnapshot(body?.error ?? 'Harness Chat is unavailable.');
326
+ }
327
+ return parseHarnessChatSnapshot(await res.json());
328
+ } catch (error) {
329
+ return unavailableHarnessChatSnapshot(error instanceof Error ? error.message : String(error));
330
+ }
331
+ }
332
+
333
+ /** Perform one explicit Chat mutation. Collapsing the panel deliberately
334
+ * does not call this API: presentation cannot attach, detach, or hand off
335
+ * session control. */
336
+ export async function performHarnessChatAction(
337
+ action: HarnessChatAction,
338
+ ): Promise<HarnessChatSnapshot> {
339
+ if (isBrowserMode()) {
340
+ throw new Error('Local coding sessions require the Node editor opened by `vgai edit`.');
341
+ }
342
+ const res = await fetch(`${BASE}/harness-chat/action`, {
343
+ method: 'POST',
344
+ headers: { 'Content-Type': 'application/json' },
345
+ body: JSON.stringify(action),
346
+ });
347
+ const body = (await res.json()) as {
348
+ error?: string;
349
+ snapshot?: HarnessChatSnapshot;
350
+ } & Partial<HarnessChatSnapshot>;
351
+ if (!res.ok) {
352
+ throw new HarnessChatRequestError(
353
+ body.error ?? 'Harness Chat action failed.',
354
+ body.snapshot ?? null,
355
+ );
356
+ }
357
+ return parseHarnessChatSnapshot(body);
358
+ }
359
+
360
+ export class HarnessChatRequestError extends Error {
361
+ constructor(
362
+ message: string,
363
+ readonly snapshot: HarnessChatSnapshot | null,
364
+ ) {
365
+ super(message);
366
+ this.name = 'HarnessChatRequestError';
367
+ }
368
+ }
369
+
370
+ /** Subscribe through the editor's one shared EventSource. */
371
+ export function connectHarnessChatEvents(
372
+ onSnapshot: (snapshot: HarnessChatSnapshot) => void,
373
+ ): () => void {
374
+ if (isBrowserMode()) return () => {};
375
+ const events = connectEvents();
376
+ const listener = (event: MessageEvent) => {
377
+ try {
378
+ onSnapshot(parseHarnessChatSnapshot(JSON.parse(event.data as string)));
379
+ } catch {
380
+ /* A malformed optional update cannot break the rest of the editor. */
381
+ }
382
+ };
383
+ events.addEventListener('harness-chat', listener);
384
+ return () => {
385
+ events.removeEventListener('harness-chat', listener);
386
+ events.close();
387
+ };
388
+ }
389
+
210
390
  /** Report the current editor state to the server (called by command-listener). */
211
391
  export async function reportEditorState(state: Record<string, unknown>): Promise<void> {
212
392
  try {
213
393
  await fetch(`${BASE}/state`, {
214
394
  method: 'POST',
215
395
  headers: { 'Content-Type': 'application/json' },
216
- body: JSON.stringify(state),
396
+ body: JSON.stringify({ ...state, _clientId: EDITOR_CLIENT_ID }),
217
397
  });
218
398
  } catch {
219
399
  /* best-effort */
@@ -222,8 +402,10 @@ export async function reportEditorState(state: Record<string, unknown>): Promise
222
402
 
223
403
  /** Report a command result back to the server so the SDK/CLI gets the response.
224
404
  * `data` carries an optional payload for commands that answer with data
225
- * (e.g. `capture-viewport`'s base64 PNG) — passed through verbatim to the
226
- * SDK/CLI caller's response body. */
405
+ * (e.g. `capture-viewport`'s base64 PNG, the Wave 5 debug-seam reads) —
406
+ * forwarded verbatim through `/__editor/command-result` →
407
+ * `commandResponseFor` → the SDK transports, on the failure path too (where
408
+ * it carries structured `{ code: ... }` markers). */
227
409
  export async function reportCommandResult(
228
410
  requestId: string,
229
411
  ok: boolean,
@@ -289,7 +471,7 @@ export async function listExamples(): Promise<ExampleInfo[]> {
289
471
  export async function createProject(
290
472
  name: string,
291
473
  location: string,
292
- template: 'starter' | 'empty' | 'example',
474
+ template: 'default' | '2d' | 'react' | 'example',
293
475
  exampleId?: string,
294
476
  ): Promise<{ ok: boolean; path?: string; config?: { name: string }; error?: string }> {
295
477
  if (isBrowserMode()) {
@@ -370,7 +552,13 @@ export async function saveThumbnail(dataUrl: string): Promise<boolean> {
370
552
  /** Query the server for the currently active project (if any). */
371
553
  export async function getCurrentServerProject(): Promise<{
372
554
  path: string;
373
- config: { name: string };
555
+ config: {
556
+ manifestVersion: 2;
557
+ name: string;
558
+ version: string;
559
+ engine: { version: string };
560
+ [key: string]: unknown;
561
+ };
374
562
  /** Whether the project ships src/ui/registry.ts (scene-UI project mode, D9). */
375
563
  hasUiRegistry?: boolean;
376
564
  /** Whether this server is the no-monorepo-checkout packaged runtime
@@ -387,7 +575,13 @@ export async function getCurrentServerProject(): Promise<{
387
575
  const data = (await res.json()) as {
388
576
  project: {
389
577
  path: string;
390
- config: { name: string };
578
+ config: {
579
+ manifestVersion: 2;
580
+ name: string;
581
+ version: string;
582
+ engine: { version: string };
583
+ [key: string]: unknown;
584
+ };
391
585
  hasUiRegistry?: boolean;
392
586
  packaged?: boolean;
393
587
  } | null;
@@ -399,21 +593,56 @@ export async function getCurrentServerProject(): Promise<{
399
593
  }
400
594
 
401
595
  /** Open/switch to a project by its filesystem path. */
402
- export async function openProjectOnServer(
403
- projectPath: string,
404
- ): Promise<{ ok: boolean; config?: { name: string } }> {
596
+ export async function openProjectOnServer(projectPath: string): Promise<{
597
+ ok: boolean;
598
+ config?: {
599
+ manifestVersion: 2;
600
+ name: string;
601
+ version: string;
602
+ engine: { version: string };
603
+ [key: string]: unknown;
604
+ };
605
+ }> {
405
606
  if (isBrowserMode()) {
406
607
  throw new Error(
407
608
  'Opening a project by filesystem path needs the Node dev server. In browser mode, use ' +
408
609
  '"Open Folder…" instead — it picks a real on-disk folder via File System Access.',
409
610
  );
410
611
  }
612
+ const compatibility = await requireEditorCompatibility();
411
613
  const res = await fetch(`${BASE}/open-project`, {
412
614
  method: 'POST',
413
615
  headers: { 'Content-Type': 'application/json' },
414
616
  body: JSON.stringify({ path: projectPath }),
415
617
  });
416
- return (await res.json()) as { ok: boolean; config?: { name: string } };
618
+ let payload: unknown;
619
+ try {
620
+ payload = await res.json();
621
+ } catch {
622
+ throw editorServerUnavailableError(`HTTP ${res.status} returned an invalid response`);
623
+ }
624
+ if (!res.ok) {
625
+ if (payload && typeof payload === 'object') {
626
+ const failure = payload as Record<string, unknown>;
627
+ if (typeof failure['error'] === 'string' && isStartupRecovery(failure['recovery'])) {
628
+ throw new ProjectCompatibilityError(failure['error'], failure['recovery']);
629
+ }
630
+ if (typeof failure['error'] === 'string') throw new Error(failure['error']);
631
+ }
632
+ throw new Error(`Could not open project at ${projectPath}: HTTP ${res.status}`);
633
+ }
634
+ const result = payload as {
635
+ ok: boolean;
636
+ config?: {
637
+ manifestVersion: 2;
638
+ name: string;
639
+ version: string;
640
+ engine: { version: string };
641
+ [key: string]: unknown;
642
+ };
643
+ };
644
+ if (result.config) assertProjectCompatibility(result.config, compatibility);
645
+ return result;
417
646
  }
418
647
 
419
648
  // ---------------------------------------------------------------------------
@@ -475,6 +704,12 @@ export interface LogEntry {
475
704
  sub?: string;
476
705
  msg: string;
477
706
  meta?: Record<string, unknown>;
707
+ /** Engine fixed-step counter at write time (SYNTHETIC-PLAYER-SPEC §3.3) —
708
+ * present only while a live play session's debug seam can be read, so
709
+ * entries correlate frame-exactly with `ctx.debug.emit` events. */
710
+ tick?: number;
711
+ /** Accumulated sim-seconds at write time — same source as `tick`. */
712
+ simT?: number;
478
713
  }
479
714
 
480
715
  /** Start a new log session. Returns the filename, or null on failure. Always
@@ -539,8 +774,8 @@ export async function flushLogEntries(entries: LogEntry[]): Promise<boolean> {
539
774
  // The whole feature is a Node-only proxy to Poly Haven/ambientcg (CORS +
540
775
  // SSRF-allowlist reasons to stay server-side) that writes into
541
776
  // `<project>/public/asset-library/...` via raw `node:fs`, not
542
- // `StorageBackend` — there is no browser-mode equivalent. `BottomPanel.tsx`
543
- // hides the whole "Library" tab in browser mode; the functions below are a
777
+ // `StorageBackend` — there is no browser-mode equivalent. The server-only
778
+ // Asset Library navigator is unavailable in browser mode; the functions below are a
544
779
  // defensive backstop for any other caller: read-only lookups degrade to an
545
780
  // honest empty result (no fetch attempted), the one mutating call
546
781
  // (`downloadOnlineAsset`) throws instead.
@@ -559,6 +794,20 @@ export interface OnlineAsset {
559
794
  author?: string;
560
795
  sourceUrl?: string;
561
796
  cloudHosted?: boolean;
797
+ variantId?: string;
798
+ variantCount?: number;
799
+ sizeBytes?: number;
800
+ animationCount?: number;
801
+ deliveryKinds?: ('local-ssd' | 'cloud' | 'remote')[];
802
+ addedAt?: string;
803
+ capabilities?: {
804
+ previewable: boolean;
805
+ importable: boolean;
806
+ runtimeReady: boolean;
807
+ animated: boolean;
808
+ deliveryAvailable: boolean;
809
+ health: 'healthy' | 'source-only' | 'unsupported';
810
+ };
562
811
  }
563
812
 
564
813
  export interface AssetFileOption {
@@ -569,31 +818,31 @@ export interface AssetFileOption {
569
818
  includes?: { relativePath: string; url: string; size: number }[];
570
819
  }
571
820
 
821
+ export interface OnlineAssetPreview {
822
+ path: string;
823
+ format: string;
824
+ label: string;
825
+ sourceAssetId: string;
826
+ materialPath?: string;
827
+ }
828
+
572
829
  // ---------------------------------------------------------------------------
573
- // Downloaded-asset cache (client-side, keyed by "source:slug")
830
+ // Downloaded-asset cache (client-side, keyed by exact provider identity)
574
831
  // ---------------------------------------------------------------------------
575
832
 
576
- /** Maps "source:slug" → local serving path (e.g. "/asset-library/polyhaven/arm-chair-01/..."). */
833
+ /** Maps "source:asset-id" → local serving path. Display names are never identity. */
577
834
  const downloadedAssetPaths = new Map<string, string>();
578
835
 
579
836
  /** Listeners notified when the download cache changes. */
580
837
  const downloadListeners = new Set<() => void>();
581
838
 
582
- /** Same slug logic as the server's download endpoint. */
583
- function toSlug(nameOrId: string): string {
584
- return nameOrId
585
- .toLowerCase()
586
- .replace(/[^a-z0-9]+/g, '-')
587
- .replace(/^-|-$/g, '');
588
- }
589
-
590
- function assetCacheKey(source: string, nameOrId: string): string {
591
- return `${source}:${toSlug(nameOrId)}`;
839
+ function assetCacheKey(source: string, assetId: string): string {
840
+ return `${source}:${assetId}`;
592
841
  }
593
842
 
594
843
  /** Check if an online asset has already been downloaded. Returns local path or null. */
595
- export function getDownloadedAssetPath(source: string, nameOrId: string): string | null {
596
- return downloadedAssetPaths.get(assetCacheKey(source, nameOrId)) ?? null;
844
+ export function getDownloadedAssetPath(source: string, assetId: string): string | null {
845
+ return downloadedAssetPaths.get(assetCacheKey(source, assetId)) ?? null;
597
846
  }
598
847
 
599
848
  /** Subscribe to download cache changes. Returns unsubscribe function. */
@@ -610,10 +859,10 @@ export async function loadDownloadedAssets(): Promise<void> {
610
859
  const res = await fetch(`${BASE}/asset-library/downloaded`);
611
860
  if (!res.ok) return;
612
861
  const data = (await res.json()) as {
613
- downloaded: { source: string; slug: string; path: string }[];
862
+ downloaded: { source: string; id: string; path: string }[];
614
863
  };
615
864
  for (const entry of data.downloaded) {
616
- downloadedAssetPaths.set(`${entry.source}:${entry.slug}`, `/${entry.path}`);
865
+ downloadedAssetPaths.set(assetCacheKey(entry.source, entry.id), `/${entry.path}`);
617
866
  }
618
867
  for (const cb of downloadListeners) cb();
619
868
  } catch {
@@ -649,11 +898,26 @@ export async function getOnlineAssetFiles(source: string, id: string): Promise<A
649
898
  if (isBrowserMode()) return [];
650
899
  const qs = new URLSearchParams({ source, id });
651
900
  const res = await fetch(`${BASE}/asset-library/files?${qs}`);
652
- if (!res.ok) return [];
901
+ if (!res.ok) {
902
+ const body = (await res.json().catch(() => null)) as { error?: string } | null;
903
+ throw new Error(body?.error ?? `Asset delivery lookup failed (${res.status}).`);
904
+ }
653
905
  const data = (await res.json()) as { files: AssetFileOption[] };
654
906
  return data.files;
655
907
  }
656
908
 
909
+ /** Resolve a read-only Asset Lab model from the editor's catalog cache. */
910
+ export async function getOnlineAssetPreview(
911
+ source: string,
912
+ id: string,
913
+ ): Promise<OnlineAssetPreview | null> {
914
+ if (isBrowserMode()) return null;
915
+ const qs = new URLSearchParams({ source, id });
916
+ const res = await fetch(`${BASE}/asset-library/preview?${qs}`);
917
+ if (!res.ok) return null;
918
+ return (await res.json()) as OnlineAssetPreview;
919
+ }
920
+
657
921
  /** Download an online asset into the project's public/asset-library/ folder. */
658
922
  export async function downloadOnlineAsset(params: {
659
923
  source: string;
@@ -682,7 +946,7 @@ export async function downloadOnlineAsset(params: {
682
946
  };
683
947
  // Cache the local path on success
684
948
  if (result.ok && result.path) {
685
- downloadedAssetPaths.set(assetCacheKey(params.source, params.name), `/${result.path}`);
949
+ downloadedAssetPaths.set(assetCacheKey(params.source, params.id), `/${result.path}`);
686
950
  for (const cb of downloadListeners) cb();
687
951
  }
688
952
  return result;