@vgai/editor 0.2.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 (519) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +35 -0
  3. package/dist/a/b/c/test-nested.json +1 -0
  4. package/dist/assets/CCDIKSolver-Dz-ysxZF.js +1 -0
  5. package/dist/assets/SelectionBox-BXsv_6aH.js +1 -0
  6. package/dist/assets/browser-project-scripts-DOru4L1x.js +2 -0
  7. package/dist/assets/browserAll-CEkWYlFk.js +1 -0
  8. package/dist/assets/collision-world-DniMB0qq.glb +0 -0
  9. package/dist/assets/create-runtime-DYIWr3va.js +1 -0
  10. package/dist/assets/data-asset-Bb9sIM7i.js +5 -0
  11. package/dist/assets/esbuild-BHljloGq.wasm +0 -0
  12. package/dist/assets/fake-three-BMTQ9rl_.js +1 -0
  13. package/dist/assets/game-8SDZPPgH.js +1 -0
  14. package/dist/assets/game-BI111o5f.js +10 -0
  15. package/dist/assets/game-CevorxyV.js +1 -0
  16. package/dist/assets/game-Di1H0gFj.js +1 -0
  17. package/dist/assets/game-Di7XlywD.js +1 -0
  18. package/dist/assets/game-DjnCaw8R.js +1 -0
  19. package/dist/assets/game-Q9A0aXSV.js +1 -0
  20. package/dist/assets/game-pt_1lQLv.js +1 -0
  21. package/dist/assets/game-source-CvidN7AB.js +1 -0
  22. package/dist/assets/game-xsOho_wg.js +83 -0
  23. package/dist/assets/index-Bp5cecD6.js +12 -0
  24. package/dist/assets/index-CPVV6pjw.js +1 -0
  25. package/dist/assets/index-CpOnYyeD.js +213 -0
  26. package/dist/assets/index-Cs8xwv3e.js +1 -0
  27. package/dist/assets/index-DL7RcQ0n.js +27 -0
  28. package/dist/assets/index-Dr8H_6Rl.js +1 -0
  29. package/dist/assets/index-_bCaipUk.js +13650 -0
  30. package/dist/assets/ingest-siblings-Dz8eEqrV.js +1 -0
  31. package/dist/assets/mount-manifest-Bhk0ohsl.js +1 -0
  32. package/dist/assets/recast-navigation.wasm-compat-CvP4UGK6.js +1 -0
  33. package/dist/assets/scene-query-BivAHhJu.js +1 -0
  34. package/dist/assets/stats.module--VATS4Kh.js +1 -0
  35. package/dist/assets/webworkerAll-BLrH0F6F.js +1 -0
  36. package/dist/assets/xstate-animation-binding-1NGLmw6y.js +1 -0
  37. package/dist/assets/xstate.esm-BgUD33nl.js +3 -0
  38. package/dist/index.html +37 -0
  39. package/dist/ingest/racing-game/UPSTREAM.md +68 -0
  40. package/dist/ingest/racing-game/a/Inter-Black.woff +0 -0
  41. package/dist/ingest/racing-game/a/Inter-Black.woff2 +0 -0
  42. package/dist/ingest/racing-game/a/Inter-BlackItalic.woff +0 -0
  43. package/dist/ingest/racing-game/a/Inter-BlackItalic.woff2 +0 -0
  44. package/dist/ingest/racing-game/a/Inter-Bold.woff +0 -0
  45. package/dist/ingest/racing-game/a/Inter-Bold.woff2 +0 -0
  46. package/dist/ingest/racing-game/a/Inter-BoldItalic.woff +0 -0
  47. package/dist/ingest/racing-game/a/Inter-BoldItalic.woff2 +0 -0
  48. package/dist/ingest/racing-game/a/Inter-ExtraBold.woff +0 -0
  49. package/dist/ingest/racing-game/a/Inter-ExtraBold.woff2 +0 -0
  50. package/dist/ingest/racing-game/a/Inter-ExtraBoldItalic.woff +0 -0
  51. package/dist/ingest/racing-game/a/Inter-ExtraBoldItalic.woff2 +0 -0
  52. package/dist/ingest/racing-game/a/Inter-ExtraLight.woff +0 -0
  53. package/dist/ingest/racing-game/a/Inter-ExtraLight.woff2 +0 -0
  54. package/dist/ingest/racing-game/a/Inter-ExtraLightItalic.woff +0 -0
  55. package/dist/ingest/racing-game/a/Inter-ExtraLightItalic.woff2 +0 -0
  56. package/dist/ingest/racing-game/a/Inter-Italic.woff +0 -0
  57. package/dist/ingest/racing-game/a/Inter-Italic.woff2 +0 -0
  58. package/dist/ingest/racing-game/a/Inter-Light.woff +0 -0
  59. package/dist/ingest/racing-game/a/Inter-Light.woff2 +0 -0
  60. package/dist/ingest/racing-game/a/Inter-LightItalic.woff +0 -0
  61. package/dist/ingest/racing-game/a/Inter-LightItalic.woff2 +0 -0
  62. package/dist/ingest/racing-game/a/Inter-Medium.woff +0 -0
  63. package/dist/ingest/racing-game/a/Inter-Medium.woff2 +0 -0
  64. package/dist/ingest/racing-game/a/Inter-MediumItalic.woff +0 -0
  65. package/dist/ingest/racing-game/a/Inter-MediumItalic.woff2 +0 -0
  66. package/dist/ingest/racing-game/a/Inter-Regular.woff +0 -0
  67. package/dist/ingest/racing-game/a/Inter-Regular.woff2 +0 -0
  68. package/dist/ingest/racing-game/a/Inter-SemiBold.woff +0 -0
  69. package/dist/ingest/racing-game/a/Inter-SemiBold.woff2 +0 -0
  70. package/dist/ingest/racing-game/a/Inter-SemiBoldItalic.woff +0 -0
  71. package/dist/ingest/racing-game/a/Inter-SemiBoldItalic.woff2 +0 -0
  72. package/dist/ingest/racing-game/a/Inter-Thin.woff +0 -0
  73. package/dist/ingest/racing-game/a/Inter-Thin.woff2 +0 -0
  74. package/dist/ingest/racing-game/a/Inter-ThinItalic.woff +0 -0
  75. package/dist/ingest/racing-game/a/Inter-ThinItalic.woff2 +0 -0
  76. package/dist/ingest/racing-game/a/Inter-italic.var.woff2 +0 -0
  77. package/dist/ingest/racing-game/a/Inter-roman.var.woff2 +0 -0
  78. package/dist/ingest/racing-game/a/Inter.var.woff2 +0 -0
  79. package/dist/ingest/racing-game/a/index.css +1 -0
  80. package/dist/ingest/racing-game/images/bronze.png +0 -0
  81. package/dist/ingest/racing-game/images/gold.png +0 -0
  82. package/dist/ingest/racing-game/images/silver.png +0 -0
  83. package/dist/ingest/racing-game/index.html +15 -0
  84. package/dist/ingest/racing-game/models/chassis-draco.glb +0 -0
  85. package/dist/ingest/racing-game/models/track-draco.glb +0 -0
  86. package/dist/ingest/racing-game/models/wheel-draco.glb +0 -0
  87. package/dist/ingest/racing-game/rg-entry.js +1 -0
  88. package/dist/ingest/racing-game/sounds/accelerate.mp3 +0 -0
  89. package/dist/ingest/racing-game/sounds/boost.mp3 +0 -0
  90. package/dist/ingest/racing-game/sounds/crash.mp3 +0 -0
  91. package/dist/ingest/racing-game/sounds/engine.mp3 +0 -0
  92. package/dist/ingest/racing-game/sounds/honk.mp3 +0 -0
  93. package/dist/ingest/racing-game/sounds/tire-brake.mp3 +0 -0
  94. package/dist/ingest/racing-game/sounds/train.mp3 +0 -0
  95. package/dist/ingest/racing-game/sounds/water.mp3 +0 -0
  96. package/dist/ingest/racing-game/textures/cursor.svg +3 -0
  97. package/dist/ingest/racing-game/textures/dikhololo_night_1k.hdr +0 -0
  98. package/dist/ingest/racing-game/textures/heightmap_1024.png +0 -0
  99. package/dist/ingest/racing-game/textures/mask.svg +11 -0
  100. package/dist/ingest/racing-game/textures/no_sound.png +0 -0
  101. package/dist/ingest/racing-game/three-r139.module.js +50617 -0
  102. package/dist/ingest/racing-game/vendor.js +179 -0
  103. package/dist/ingest/racing-game/vgai.game.json +20 -0
  104. package/dist/ingest/simcity/UPSTREAM.md +65 -0
  105. package/dist/ingest/simcity/favicon.ico +0 -0
  106. package/dist/ingest/simcity/fonts/Bebas-Regular.otf +0 -0
  107. package/dist/ingest/simcity/fonts/Digitalt.otf +0 -0
  108. package/dist/ingest/simcity/fonts/Stanberry.ttf +0 -0
  109. package/dist/ingest/simcity/icons/bulldozer-color.png +0 -0
  110. package/dist/ingest/simcity/icons/bulldozer.png +0 -0
  111. package/dist/ingest/simcity/icons/calendar.png +0 -0
  112. package/dist/ingest/simcity/icons/factory-color.png +0 -0
  113. package/dist/ingest/simcity/icons/factory.png +0 -0
  114. package/dist/ingest/simcity/icons/house-color.png +0 -0
  115. package/dist/ingest/simcity/icons/house.png +0 -0
  116. package/dist/ingest/simcity/icons/job.png +0 -0
  117. package/dist/ingest/simcity/icons/office.png +0 -0
  118. package/dist/ingest/simcity/icons/pause-color.png +0 -0
  119. package/dist/ingest/simcity/icons/pause.png +0 -0
  120. package/dist/ingest/simcity/icons/person.png +0 -0
  121. package/dist/ingest/simcity/icons/play-color.png +0 -0
  122. package/dist/ingest/simcity/icons/play.png +0 -0
  123. package/dist/ingest/simcity/icons/power-color.png +0 -0
  124. package/dist/ingest/simcity/icons/power-line-color.png +0 -0
  125. package/dist/ingest/simcity/icons/power.png +0 -0
  126. package/dist/ingest/simcity/icons/road-color.png +0 -0
  127. package/dist/ingest/simcity/icons/road.png +0 -0
  128. package/dist/ingest/simcity/icons/select-color.png +0 -0
  129. package/dist/ingest/simcity/icons/select.png +0 -0
  130. package/dist/ingest/simcity/icons/store-color.png +0 -0
  131. package/dist/ingest/simcity/index.html +94 -0
  132. package/dist/ingest/simcity/index.js +58 -0
  133. package/dist/ingest/simcity/main.css +233 -0
  134. package/dist/ingest/simcity/models/armored-truck.glb +0 -0
  135. package/dist/ingest/simcity/models/building-block-4floor-corner.glb +0 -0
  136. package/dist/ingest/simcity/models/building-block-4floor-short.glb +0 -0
  137. package/dist/ingest/simcity/models/building-block-5floor.glb +0 -0
  138. package/dist/ingest/simcity/models/building-burger-joint.glb +0 -0
  139. package/dist/ingest/simcity/models/building-cafe.glb +0 -0
  140. package/dist/ingest/simcity/models/building-casino.glb +0 -0
  141. package/dist/ingest/simcity/models/building-cinema.glb +0 -0
  142. package/dist/ingest/simcity/models/building-house-block-big.glb +0 -0
  143. package/dist/ingest/simcity/models/building-house-family-large.glb +0 -0
  144. package/dist/ingest/simcity/models/building-house-family-small.glb +0 -0
  145. package/dist/ingest/simcity/models/building-office-balcony.glb +0 -0
  146. package/dist/ingest/simcity/models/building-office-big.glb +0 -0
  147. package/dist/ingest/simcity/models/building-office-pyramid.glb +0 -0
  148. package/dist/ingest/simcity/models/building-office-tall.glb +0 -0
  149. package/dist/ingest/simcity/models/building-office.glb +0 -0
  150. package/dist/ingest/simcity/models/building-restaurant.glb +0 -0
  151. package/dist/ingest/simcity/models/building-skyscraper.glb +0 -0
  152. package/dist/ingest/simcity/models/car-ambulance-pickup.glb +0 -0
  153. package/dist/ingest/simcity/models/car-baywatch.glb +0 -0
  154. package/dist/ingest/simcity/models/car-hippie-van.glb +0 -0
  155. package/dist/ingest/simcity/models/car-passenger-race.glb +0 -0
  156. package/dist/ingest/simcity/models/car-passenger.glb +0 -0
  157. package/dist/ingest/simcity/models/car-police.glb +0 -0
  158. package/dist/ingest/simcity/models/car-taxi.glb +0 -0
  159. package/dist/ingest/simcity/models/car-tow-truck.glb +0 -0
  160. package/dist/ingest/simcity/models/car-truck-dump.glb +0 -0
  161. package/dist/ingest/simcity/models/car-veteran.glb +0 -0
  162. package/dist/ingest/simcity/models/construction-small.glb +0 -0
  163. package/dist/ingest/simcity/models/data-center.glb +0 -0
  164. package/dist/ingest/simcity/models/industry-factory-old.glb +0 -0
  165. package/dist/ingest/simcity/models/industry-factory.glb +0 -0
  166. package/dist/ingest/simcity/models/industry-refinery.glb +0 -0
  167. package/dist/ingest/simcity/models/industry-warehouse.glb +0 -0
  168. package/dist/ingest/simcity/models/power_line_pole_modified.glb +0 -0
  169. package/dist/ingest/simcity/models/tile-plain_grass.glb +0 -0
  170. package/dist/ingest/simcity/models/tile-road-curve.glb +0 -0
  171. package/dist/ingest/simcity/models/tile-road-end.glb +0 -0
  172. package/dist/ingest/simcity/models/tile-road-intersection-t.glb +0 -0
  173. package/dist/ingest/simcity/models/tile-road-intersection.glb +0 -0
  174. package/dist/ingest/simcity/models/tile-road-straight.glb +0 -0
  175. package/dist/ingest/simcity/models/truck.glb +0 -0
  176. package/dist/ingest/simcity/statusIcons/no-power.png +0 -0
  177. package/dist/ingest/simcity/statusIcons/no-road-access.png +0 -0
  178. package/dist/ingest/simcity/textures/base.png +0 -0
  179. package/dist/ingest/simcity/textures/grid.png +0 -0
  180. package/dist/ingest/simcity/textures/specular.png +0 -0
  181. package/dist/ingest/simcity/three-r155.module.js +51466 -0
  182. package/dist/ingest/simcity/vgai.game.json +20 -0
  183. package/dist/ingest/tanks/UPSTREAM.md +69 -0
  184. package/dist/ingest/tanks/index.html +214 -0
  185. package/dist/ingest/tanks/models/pixelTank.fbx +0 -0
  186. package/dist/ingest/tanks/tanks-entry.js +92 -0
  187. package/dist/ingest/tanks/three-r170.module.js +54571 -0
  188. package/dist/ingest/tanks/vgai.game.json +20 -0
  189. package/dist/inputmaps/default.inputmap.json +41 -0
  190. package/dist/jsm/libs/draco/gltf/draco_decoder.js +33 -0
  191. package/dist/jsm/libs/draco/gltf/draco_decoder.wasm +0 -0
  192. package/dist/jsm/libs/draco/gltf/draco_wasm_wrapper.js +116 -0
  193. package/dist/scenes/untitled.vscn.json +29 -0
  194. package/dist/textures/waternormals.jpg +0 -0
  195. package/dist-server/packaged.mjs +18155 -0
  196. package/package.json +36 -0
  197. package/server/asset-library-routes.ts +833 -0
  198. package/server/canonical-path.ts +50 -0
  199. package/server/cloud-asset-catalog.ts +101 -0
  200. package/server/dev.ts +350 -0
  201. package/server/editor-server.ts +1537 -0
  202. package/server/editor-sse.ts +73 -0
  203. package/server/game-globals-shadow.ts +58 -0
  204. package/server/local-asset-catalog.ts +163 -0
  205. package/server/packaged.ts +571 -0
  206. package/server/prod.ts +75 -0
  207. package/server/project-optimize-deps-entries.ts +149 -0
  208. package/server/project-view.ts +65 -0
  209. package/server/server-utils.ts +430 -0
  210. package/server/session-registry.ts +93 -0
  211. package/src/EditorContext.tsx +87 -0
  212. package/src/achieved-tier.ts +126 -0
  213. package/src/action-registry.ts +268 -0
  214. package/src/adapter-resolver.ts +1858 -0
  215. package/src/asset-events.ts +100 -0
  216. package/src/authoring/active-adapter.ts +98 -0
  217. package/src/authoring/active-systems.ts +50 -0
  218. package/src/authoring/boundary-authoring-adapter.ts +178 -0
  219. package/src/authoring/composite-authoring-adapter.ts +891 -0
  220. package/src/authoring/design-time-layers.ts +776 -0
  221. package/src/authoring/edit-mode-authoring.ts +643 -0
  222. package/src/authoring/embed-only-adapter.ts +57 -0
  223. package/src/authoring/ephemeral-persistence.ts +26 -0
  224. package/src/authoring/eyedropper-session.ts +69 -0
  225. package/src/authoring/first-party-hierarchy-menu.ts +74 -0
  226. package/src/authoring/first-party-inspector/AddSectionMenu.tsx +118 -0
  227. package/src/authoring/first-party-inspector/EnvironmentSection.tsx +550 -0
  228. package/src/authoring/first-party-inspector/HeaderSection.tsx +329 -0
  229. package/src/authoring/first-party-inspector/is-first-party-selection.ts +44 -0
  230. package/src/authoring/first-party-inspector/register.tsx +108 -0
  231. package/src/authoring/ingest-authoring-adapter.ts +475 -0
  232. package/src/authoring/ingest-capture.ts +94 -0
  233. package/src/authoring/ingest-game-adapter.ts +291 -0
  234. package/src/authoring/ingest-iframe-adapter.ts +38 -0
  235. package/src/authoring/ingest-iframe-reachable-adapter.ts +401 -0
  236. package/src/authoring/ingest-overlay.ts +193 -0
  237. package/src/authoring/layered-pick.ts +87 -0
  238. package/src/authoring/mount-failure-report.ts +112 -0
  239. package/src/authoring/overlay-report.ts +52 -0
  240. package/src/authoring/project-scenes.ts +111 -0
  241. package/src/authoring/react-dom-authoring-adapter.ts +795 -0
  242. package/src/authoring/react-inspector-section.tsx +1041 -0
  243. package/src/authoring/react-world-authoring-adapter.ts +2400 -0
  244. package/src/authoring/root-activation.ts +61 -0
  245. package/src/authoring/scene-ui-layer.ts +485 -0
  246. package/src/authoring/story-overlay.ts +48 -0
  247. package/src/authoring/ui-authoring-adapter.ts +325 -0
  248. package/src/authoring/ui-hierarchy-menu.ts +40 -0
  249. package/src/authoring/ui-inspector-sections.tsx +156 -0
  250. package/src/authoring/undo-timeline.ts +145 -0
  251. package/src/authoring/vgai-scene-authoring-adapter.ts +746 -0
  252. package/src/authoring/viewport-pick-context.ts +31 -0
  253. package/src/authoring/viewport-raycast.ts +80 -0
  254. package/src/authoring/world-overlay.ts +76 -0
  255. package/src/authoring/world-pan-state.ts +115 -0
  256. package/src/authoring/world-session-state.ts +102 -0
  257. package/src/authoring/world2d-authoring-adapter.ts +313 -0
  258. package/src/browser-external-watch.ts +98 -0
  259. package/src/browser-play-entry.ts +112 -0
  260. package/src/browser-project-scripts.ts +112 -0
  261. package/src/browser-transpile.ts +246 -0
  262. package/src/browser-watch-reload.ts +100 -0
  263. package/src/command-listener.ts +326 -0
  264. package/src/components/AlignToolbar.tsx +189 -0
  265. package/src/components/AppRoot.tsx +73 -0
  266. package/src/components/AssetBrowser.tsx +502 -0
  267. package/src/components/BottomPanel.tsx +326 -0
  268. package/src/components/BuildPanel.tsx +455 -0
  269. package/src/components/CameraInfo.tsx +56 -0
  270. package/src/components/CommandPalette.tsx +265 -0
  271. package/src/components/ConsolePanel.tsx +272 -0
  272. package/src/components/DataPanel.tsx +1268 -0
  273. package/src/components/DefaultEditorLayout.tsx +509 -0
  274. package/src/components/FolderTree.tsx +179 -0
  275. package/src/components/GameHierarchy.tsx +1349 -0
  276. package/src/components/Inspector.tsx +671 -0
  277. package/src/components/InspectorToolSection.tsx +74 -0
  278. package/src/components/MountFailureBanner.tsx +110 -0
  279. package/src/components/NetworkSection.tsx +109 -0
  280. package/src/components/OnlineAssetBrowser.tsx +448 -0
  281. package/src/components/OverlayReportBanner.tsx +122 -0
  282. package/src/components/PlayBar.tsx +356 -0
  283. package/src/components/PrefabSaveDialog.tsx +201 -0
  284. package/src/components/ProjectHeader.tsx +174 -0
  285. package/src/components/ProjectScreen.tsx +1004 -0
  286. package/src/components/RightPanel.tsx +166 -0
  287. package/src/components/RootActivateDialog.tsx +105 -0
  288. package/src/components/SaveDialog.tsx +197 -0
  289. package/src/components/SaveStatus.tsx +69 -0
  290. package/src/components/StatsOverlay.tsx +35 -0
  291. package/src/components/ToolHost.tsx +80 -0
  292. package/src/components/Toolbar.tsx +307 -0
  293. package/src/components/ViewportOverlay.tsx +234 -0
  294. package/src/components/ViewportPanel.tsx +369 -0
  295. package/src/components/WorldContextMenu.tsx +257 -0
  296. package/src/components/WorldSelectionOverlay.tsx +2003 -0
  297. package/src/components/WorldTextEditor.tsx +86 -0
  298. package/src/components/asset-viewers/AssetViewer.tsx +38 -0
  299. package/src/components/asset-viewers/AudioViewer.tsx +149 -0
  300. package/src/components/asset-viewers/GenericJsonViewer.tsx +77 -0
  301. package/src/components/asset-viewers/ImageViewer.tsx +65 -0
  302. package/src/components/asset-viewers/InputMapViewer.tsx +206 -0
  303. package/src/components/asset-viewers/ModelViewer.tsx +180 -0
  304. package/src/components/asset-viewers/OnlineAssetDetail.tsx +236 -0
  305. package/src/components/data-csv.ts +301 -0
  306. package/src/components/entity-name-commit.ts +18 -0
  307. package/src/components/inspector-property-grouping.ts +65 -0
  308. package/src/components/inspector-widgets/AlignmentGrid.tsx +188 -0
  309. package/src/components/inspector-widgets/BorderEditor.tsx +305 -0
  310. package/src/components/inspector-widgets/ColorPicker.tsx +548 -0
  311. package/src/components/inspector-widgets/FilterEditor.tsx +108 -0
  312. package/src/components/inspector-widgets/FontPicker.tsx +196 -0
  313. package/src/components/inspector-widgets/GradientEditor.tsx +277 -0
  314. package/src/components/inspector-widgets/ScrubbableInput.tsx +152 -0
  315. package/src/components/inspector-widgets/ShadowEditor.tsx +332 -0
  316. package/src/components/inspector-widgets/color-utils.ts +202 -0
  317. package/src/components/inspector-widgets/gallery-main.tsx +167 -0
  318. package/src/components/inspector-widgets/index.ts +22 -0
  319. package/src/components/inspector-widgets/shared.tsx +135 -0
  320. package/src/components/inspectors/AnimationSection.tsx +143 -0
  321. package/src/components/inspectors/AudioSection.tsx +215 -0
  322. package/src/components/inspectors/BoneAttachmentSection.tsx +82 -0
  323. package/src/components/inspectors/CameraSection.tsx +105 -0
  324. package/src/components/inspectors/ComponentsSection.tsx +358 -0
  325. package/src/components/inspectors/JointsSection.tsx +255 -0
  326. package/src/components/inspectors/LightSection.tsx +116 -0
  327. package/src/components/inspectors/MaterialSection.tsx +1189 -0
  328. package/src/components/inspectors/MeshSection.tsx +99 -0
  329. package/src/components/inspectors/NavigationSection.tsx +58 -0
  330. package/src/components/inspectors/ParticleSection.tsx +681 -0
  331. package/src/components/inspectors/PhysicsSection.tsx +326 -0
  332. package/src/components/inspectors/PostProcessingSection.tsx +1290 -0
  333. package/src/components/inspectors/RenderingSection.tsx +109 -0
  334. package/src/components/inspectors/ShadowSection.tsx +84 -0
  335. package/src/components/inspectors/SplineSection.tsx +191 -0
  336. package/src/components/inspectors/TransformSection.tsx +191 -0
  337. package/src/components/inspectors/json-schema-fields.ts +281 -0
  338. package/src/components/inspectors/multi-edit-utils.ts +49 -0
  339. package/src/components/inspectors/zod-fields.ts +184 -0
  340. package/src/components/primitives/ColorInput.tsx +109 -0
  341. package/src/components/primitives/JsonInput.tsx +98 -0
  342. package/src/components/primitives/NumberInput.tsx +164 -0
  343. package/src/components/primitives/Panel.tsx +68 -0
  344. package/src/components/primitives/SectionHeader.tsx +45 -0
  345. package/src/components/primitives/Tooltip.tsx +82 -0
  346. package/src/components/primitives/Vec3Input.tsx +62 -0
  347. package/src/components/world-overlay-gestures.ts +1438 -0
  348. package/src/doctor/data-checks.ts +285 -0
  349. package/src/doctor/report.ts +386 -0
  350. package/src/editor-api.ts +700 -0
  351. package/src/editor-console.ts +194 -0
  352. package/src/editor-hotkeys.ts +441 -0
  353. package/src/editor-layers.ts +2 -0
  354. package/src/editor-mode.ts +28 -0
  355. package/src/editor-store.ts +2225 -0
  356. package/src/editor-viewport.ts +1924 -0
  357. package/src/entity-factory.ts +221 -0
  358. package/src/game-globals-prelude.ts +27 -0
  359. package/src/gated-globals.ts +131 -0
  360. package/src/generated/setup-by-example.generated.ts +53 -0
  361. package/src/gizmo-registry.ts +608 -0
  362. package/src/handle-system.ts +221 -0
  363. package/src/hierarchy-menu-registry.ts +70 -0
  364. package/src/hierarchy-rows.ts +73 -0
  365. package/src/hosted-example.ts +160 -0
  366. package/src/hotkeys.ts +142 -0
  367. package/src/ingest/discovery-react.ts +132 -0
  368. package/src/ingest/discovery.ts +205 -0
  369. package/src/ingest/discovery2d.ts +204 -0
  370. package/src/ingest/esm-entry-resolution.ts +102 -0
  371. package/src/ingest/games/bloom-composer/game.js +99 -0
  372. package/src/ingest/games/bloom-composer/vgai.game.json +21 -0
  373. package/src/ingest/games/embed-bundle/embed.html +10 -0
  374. package/src/ingest/games/embed-bundle/vgai.game.json +21 -0
  375. package/src/ingest/games/games-fps/assets/collision-world.glb +0 -0
  376. package/src/ingest/games/games-fps/game.js +479 -0
  377. package/src/ingest/games/games-fps/vgai.game.json +24 -0
  378. package/src/ingest/games/iframe-reachable/game-source.js +58 -0
  379. package/src/ingest/games/iframe-reachable/vgai.game.json +21 -0
  380. package/src/ingest/games/interactive-cubes/game.js +139 -0
  381. package/src/ingest/games/interactive-cubes/vgai.game.json +21 -0
  382. package/src/ingest/games/mismatched-three/fake-three.js +38 -0
  383. package/src/ingest/games/mismatched-three/game.js +10 -0
  384. package/src/ingest/games/mismatched-three/vgai.game.json +22 -0
  385. package/src/ingest/games/own-three-bundled/game.js +43 -0
  386. package/src/ingest/games/own-three-bundled/vgai.game.json +21 -0
  387. package/src/ingest/games/pointerlock-maze/game.js +288 -0
  388. package/src/ingest/games/pointerlock-maze/vgai.game.json +22 -0
  389. package/src/ingest/games-2d/_fixtures/first-party-scene.scn2d.json +124 -0
  390. package/src/ingest/games-2d/bubbo-bubbo/vgai.game.json +25 -0
  391. package/src/ingest/games-2d/bunnymark/game.ts +88 -0
  392. package/src/ingest/games-2d/bunnymark/vgai.game.json +21 -0
  393. package/src/ingest/games-2d/flappy/game.ts +148 -0
  394. package/src/ingest/games-2d/flappy/vgai.game.json +21 -0
  395. package/src/ingest/games-2d/flappy-pixi/vgai.game.json +25 -0
  396. package/src/ingest/games-2d/puzzling-potions/vgai.game.json +25 -0
  397. package/src/ingest/games-2d/tilemap-camera/game.ts +115 -0
  398. package/src/ingest/games-2d/tilemap-camera/vgai.game.json +21 -0
  399. package/src/ingest/games-react/react-rpg/entry.tsx +185 -0
  400. package/src/ingest/games-react/react-rpg/shims.d.ts +26 -0
  401. package/src/ingest/games-react/react-rpg/src/ui/game-state.tsx +129 -0
  402. package/src/ingest/games-react/react-rpg/vgai.game.json +21 -0
  403. package/src/ingest/host-namespace-registry.ts +196 -0
  404. package/src/ingest/types.ts +127 -0
  405. package/src/ingest-hierarchy-rows.ts +207 -0
  406. package/src/ingest-mode.ts +2092 -0
  407. package/src/ingest-siblings.ts +466 -0
  408. package/src/initial-scene.ts +194 -0
  409. package/src/inspector-section-registry.ts +117 -0
  410. package/src/interactive-edit-scope.ts +34 -0
  411. package/src/main.tsx +23 -0
  412. package/src/manifest-project.ts +43 -0
  413. package/src/model-thumbnail.ts +156 -0
  414. package/src/module-mode.ts +513 -0
  415. package/src/navmesh-handler.ts +94 -0
  416. package/src/packaged-runtime.ts +49 -0
  417. package/src/play-mode.ts +1148 -0
  418. package/src/playground/main.ts +206 -0
  419. package/src/project-manager.ts +173 -0
  420. package/src/scene-diff.ts +112 -0
  421. package/src/scene-serializer.ts +144 -0
  422. package/src/scene-sync.ts +587 -0
  423. package/src/scene-ui-mode/active-scene-ui.ts +58 -0
  424. package/src/scene-ui-mode/scene-ui-palette-panel.tsx +152 -0
  425. package/src/scene-ui-mode/scene-ui-timeline-tab.tsx +29 -0
  426. package/src/scene-ui-mode/ui-prefab-dialog-host.tsx +74 -0
  427. package/src/scene-ui-mode/ui-prefab-hierarchy-menu.ts +49 -0
  428. package/src/scene-ui-mode/ui-prefab-io.ts +76 -0
  429. package/src/script-loader.ts +143 -0
  430. package/src/storage/fsa-storage.ts +55 -0
  431. package/src/storage/handle-storage.ts +150 -0
  432. package/src/storage/handle-store.ts +116 -0
  433. package/src/storage/http-storage.ts +137 -0
  434. package/src/storage/index.ts +147 -0
  435. package/src/storage/mem-storage.ts +135 -0
  436. package/src/storage/opfs-storage.ts +25 -0
  437. package/src/storage/paths.ts +26 -0
  438. package/src/storage/seed.ts +106 -0
  439. package/src/storage/types.ts +110 -0
  440. package/src/tool-loader.ts +291 -0
  441. package/src/ui-editor/inspector.tsx +563 -0
  442. package/src/ui-editor/node-ops.ts +266 -0
  443. package/src/ui-editor/overlay.tsx +380 -0
  444. package/src/ui-editor/palette.tsx +114 -0
  445. package/src/ui-editor/react-store.ts +78 -0
  446. package/src/ui-editor/timeline-panel.tsx +443 -0
  447. package/src/ui-editor/ui-edit-store.ts +552 -0
  448. package/src/ui-editor/ui-prefab.ts +95 -0
  449. package/src/ui-source/inspect.ts +544 -0
  450. package/src/ui-source/oid-transform.ts +133 -0
  451. package/src/ui-source/source-write-backend.ts +203 -0
  452. package/src/ui-source/tailwind-map.ts +174 -0
  453. package/src/ui-source/writer.ts +1175 -0
  454. package/src/widgets/index.ts +73 -0
  455. package/template/.claude/skills/editor/SKILL.md +252 -0
  456. package/template/CLAUDE.md +512 -0
  457. package/template/LICENSE +30 -0
  458. package/template/emit-schemas.ts +42 -0
  459. package/template/index.html +17 -0
  460. package/template/package.json +53 -0
  461. package/template/project.json +9 -0
  462. package/template/public/audio/ambient-arena.wav +0 -0
  463. package/template/public/audio/ambient-music.wav +0 -0
  464. package/template/public/audio/ambient-nature.wav +0 -0
  465. package/template/public/audio/ambient-wind.wav +0 -0
  466. package/template/public/inputmaps/default.inputmap.json +41 -0
  467. package/template/public/jsm/libs/draco/gltf/draco_decoder.js +33 -0
  468. package/template/public/jsm/libs/draco/gltf/draco_decoder.wasm +0 -0
  469. package/template/public/jsm/libs/draco/gltf/draco_encoder.js +33 -0
  470. package/template/public/jsm/libs/draco/gltf/draco_wasm_wrapper.js +116 -0
  471. package/template/public/scenes/default.vscn.json +43 -0
  472. package/template/public/scenes/untitled.vscn.json +29 -0
  473. package/template/public/textures/waternormals.jpg +0 -0
  474. package/template/server/colyseus-setup.ts +85 -0
  475. package/template/server/main.ts +34 -0
  476. package/template/server/rooms/arena-room.ts +420 -0
  477. package/template/server/rooms/game-room.ts +162 -0
  478. package/template/server/rooms.ts +15 -0
  479. package/template/server/tsconfig.json +19 -0
  480. package/template/src/data/README.md +40 -0
  481. package/template/src/data/assets.ts +31 -0
  482. package/template/src/data/tuning.data.json +5 -0
  483. package/template/src/data/tuning.schema.json +21 -0
  484. package/template/src/data/tuning.schema.ts +34 -0
  485. package/template/src/data/tuning.ts +24 -0
  486. package/template/src/main.ts +157 -0
  487. package/template/src/scripts/components/data-spinner.ts +45 -0
  488. package/template/src/scripts/components/scene-camera.ts +40 -0
  489. package/template/src/scripts/main.ts +94 -0
  490. package/template/src/scripts/registry.ts +46 -0
  491. package/template/src/tools/README.md +14 -0
  492. package/template/src/tools/example.tool.tsx +91 -0
  493. package/template/src/tools/spin.tool.tsx +101 -0
  494. package/template/src/ui/Button.tsx +27 -0
  495. package/template/src/ui/DefaultHud.tsx +40 -0
  496. package/template/src/ui/KeyHint.tsx +25 -0
  497. package/template/src/ui/Panel.tsx +52 -0
  498. package/template/src/ui/RebindPanel.tsx +129 -0
  499. package/template/src/ui/Slider.tsx +28 -0
  500. package/template/src/ui/StatusDot.tsx +22 -0
  501. package/template/src/ui/Toggle.tsx +19 -0
  502. package/template/src/ui/game-state.tsx +37 -0
  503. package/template/src/ui/index.ts +7 -0
  504. package/template/src/ui/mount-default-hud.tsx +19 -0
  505. package/template/src/ui/mount-ui.ts +15 -0
  506. package/template/tsconfig.json +36 -0
  507. package/template/validate-asset-content.ts +78 -0
  508. package/template/validate-manifest.ts +55 -0
  509. package/template/validate-scenes.ts +211 -0
  510. package/template/vgai.game.json +21 -0
  511. package/template/vite.config.ts +128 -0
  512. package/vite-plugin-example-assets.ts +60 -0
  513. package/vite-plugin-game-globals.ts +46 -0
  514. package/vite-plugin-game-static.ts +71 -0
  515. package/vite-plugin-project-game-static.ts +84 -0
  516. package/vite-plugin-react-game-static.ts +98 -0
  517. package/vite-plugin-react-world-provider.ts +81 -0
  518. package/vite-plugin-react-world-runtime.ts +76 -0
  519. package/vite-plugin-ui-oid.ts +561 -0
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Browser `StorageBackend` over the Origin Private File System (OPFS).
3
+ *
4
+ * OPFS is a sandboxed, origin-scoped filesystem available in all modern browsers
5
+ * via `navigator.storage.getDirectory()`. It needs no server and no permission
6
+ * prompt — the zero-friction default for a self-contained, server-less editor.
7
+ * To edit a user's REAL on-disk folder instead, use `FsaStorage` (same logic,
8
+ * different root handle).
9
+ *
10
+ * All behavior lives in `HandleStorage`; this subclass only supplies the OPFS
11
+ * root handle.
12
+ */
13
+
14
+ import { HandleStorage } from './handle-storage';
15
+
16
+ export class OpfsStorage extends HandleStorage {
17
+ readonly id = 'opfs';
18
+
19
+ private rootPromise: Promise<FileSystemDirectoryHandle> | null = null;
20
+
21
+ protected rootHandle(): Promise<FileSystemDirectoryHandle> {
22
+ if (!this.rootPromise) this.rootPromise = navigator.storage.getDirectory();
23
+ return this.rootPromise;
24
+ }
25
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Path helpers for storage backends. Paths are POSIX-style, root-relative,
3
+ * '/'-separated, with no leading/trailing slashes.
4
+ */
5
+
6
+ /** Split a path into non-empty segments, collapsing `.` and redundant slashes. */
7
+ export function segments(path: string): string[] {
8
+ return path.split('/').filter((s) => s.length > 0 && s !== '.');
9
+ }
10
+
11
+ /** Normalize to a canonical root-relative path (`''` for the root). */
12
+ export function normalize(path: string): string {
13
+ return segments(path).join('/');
14
+ }
15
+
16
+ /** Split into `[parentDir, baseName]`. Root entries have `parent === ''`. */
17
+ export function dirAndBase(path: string): [string, string] {
18
+ const segs = segments(path);
19
+ const base = segs.pop() ?? '';
20
+ return [segs.join('/'), base];
21
+ }
22
+
23
+ /** Join path parts into a normalized path. */
24
+ export function join(...parts: string[]): string {
25
+ return normalize(parts.join('/'));
26
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * First-run seeding for the browser-first build.
3
+ *
4
+ * On a fresh OPFS (or any empty backend) the editor would otherwise have no
5
+ * project. `seedIfEmpty` writes a MINIMAL Starter — one asset-free scene, a
6
+ * default input map, and a project manifest — so the browser editor opens to a
7
+ * usable scene with no server and no download. Asset-heavy examples (models,
8
+ * textures, audio) are intentionally NOT seeded; they load on demand and would
9
+ * require routing the engine's asset pipeline through the backend.
10
+ */
11
+
12
+ import { parseSceneFile } from '@engine/scene/parse';
13
+ import type { StorageBackend } from './types';
14
+
15
+ export const STARTER_SCENE_PATH = 'scenes/default.vscn.json';
16
+ const STARTER_INPUTMAP_PATH = 'inputmaps/default.inputmap.json';
17
+ const PROJECT_MANIFEST_PATH = 'project.json';
18
+
19
+ /** Minimal, asset-free scene: a camera, a light, a ground plane, and a box. */
20
+ const STARTER_SCENE = {
21
+ version: 1,
22
+ name: 'Starter',
23
+ entities: [
24
+ {
25
+ name: 'Camera',
26
+ camera: { type: 'perspective' },
27
+ transform: { position: [4, 3, 5], rotation: [-0.18, 0.36, 0.07, 0.91] },
28
+ },
29
+ {
30
+ name: 'Sun',
31
+ light: { type: 'directional', intensity: 2 },
32
+ transform: { position: [5, 8, 4] },
33
+ },
34
+ {
35
+ name: 'Ambient',
36
+ light: { type: 'hemisphere', color: '#ffffff', groundColor: '#444444', intensity: 0.5 },
37
+ },
38
+ {
39
+ name: 'Ground',
40
+ mesh: { type: 'plane' },
41
+ material: { type: 'standard', color: '#3a3f47' },
42
+ // -90° about X (lay the plane flat); -sqrt(1/2) / sqrt(1/2) quaternion.
43
+ transform: { rotation: [-Math.SQRT1_2, 0, 0, Math.SQRT1_2], scale: [20, 20, 20] },
44
+ },
45
+ {
46
+ name: 'Box',
47
+ mesh: { type: 'box' },
48
+ material: { type: 'standard', color: '#579eff' },
49
+ transform: { position: [0, 0.5, 0] },
50
+ },
51
+ ],
52
+ };
53
+
54
+ /** Minimal WASD + jump input map. */
55
+ const STARTER_INPUTMAP = {
56
+ version: 1,
57
+ actions: {
58
+ move_forward: { bindings: [{ type: 'key', code: 'KeyW' }] },
59
+ move_backward: { bindings: [{ type: 'key', code: 'KeyS' }] },
60
+ move_left: { bindings: [{ type: 'key', code: 'KeyA' }] },
61
+ move_right: { bindings: [{ type: 'key', code: 'KeyD' }] },
62
+ jump: { bindings: [{ type: 'key', code: 'Space' }] },
63
+ },
64
+ };
65
+
66
+ const PROJECT_MANIFEST = {
67
+ name: 'Browser Project',
68
+ defaultScene: STARTER_SCENE_PATH,
69
+ };
70
+
71
+ /**
72
+ * Seed the Starter project into `backend` if it has not been seeded yet.
73
+ * Idempotent — returns immediately once a VALID default scene exists.
74
+ *
75
+ * Self-healing: if a default scene already exists but no longer parses against
76
+ * the engine schema (e.g. an earlier build seeded an invalid scene), it is
77
+ * re-seeded. This repairs browsers whose OPFS holds a stale/broken Starter —
78
+ * `exists()` alone would skip them forever and the editor would fall back to a
79
+ * bare scene on every visit.
80
+ */
81
+ export async function seedIfEmpty(backend: StorageBackend): Promise<void> {
82
+ if (await backend.exists(STARTER_SCENE_PATH)) {
83
+ // Validate the existing scene; only re-seed the scene file if it's broken.
84
+ try {
85
+ parseSceneFile(JSON.parse(await backend.read(STARTER_SCENE_PATH)));
86
+ return; // valid — leave the user's scene (possibly edited) untouched
87
+ } catch {
88
+ await backend.write(STARTER_SCENE_PATH, JSON.stringify(STARTER_SCENE, null, 2));
89
+ return;
90
+ }
91
+ }
92
+ await backend.write(STARTER_SCENE_PATH, JSON.stringify(STARTER_SCENE, null, 2));
93
+ 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));
95
+ }
96
+
97
+ /** 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 }> {
101
+ try {
102
+ return JSON.parse(await backend.read(PROJECT_MANIFEST_PATH));
103
+ } catch {
104
+ return { ...PROJECT_MANIFEST };
105
+ }
106
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Backend-agnostic file interface for the editor.
3
+ *
4
+ * The editor and engine read/write project files through one `StorageBackend`
5
+ * and never learn whether the bytes live on a local filesystem (Node server) or
6
+ * in the browser (OPFS / File System Access). Swapping the backend is what turns
7
+ * the editor server-less: the same code paths (scene load/save, asset listing,
8
+ * in-browser HMR script reads) run unchanged against either implementation.
9
+ *
10
+ * Scope — this interface deliberately covers ONLY what reduces to file
11
+ * operations. Three editor concerns do not, and are handled elsewhere:
12
+ * - native folder dialogs / "reveal in OS file manager" → `HostCapabilities`
13
+ * - the `vgai` CLI control channel (SSE + command RPC) → server-only, dropped
14
+ * in a pure-browser editor
15
+ * - `export` / packaging (spawns a build) → a client-side bundler in-browser
16
+ *
17
+ * `watch` is part of the interface but its semantics are backend-specific: a
18
+ * server backend reports real filesystem events (chokidar); a browser backend
19
+ * has no OS file-change notifications, so it derives events from its OWN writes
20
+ * (in a server-less editor the editor is the only writer — which is exactly the
21
+ * signal HMR needs).
22
+ *
23
+ * Paths are POSIX-style, project-root-relative, '/'-separated, no leading slash
24
+ * (e.g. `src/scripts/components/spin.ts`).
25
+ */
26
+
27
+ export type NodeType = 'file' | 'dir';
28
+
29
+ export interface Stat {
30
+ /** Normalized, root-relative path. */
31
+ path: string;
32
+ type: NodeType;
33
+ /** Byte length for files; 0 for directories. */
34
+ size: number;
35
+ /** Last-modified time, epoch ms (0 if the backend cannot report it). */
36
+ mtime: number;
37
+ }
38
+
39
+ export interface DirEntry {
40
+ /** Base name (no slashes). */
41
+ name: string;
42
+ /** Normalized, root-relative path. */
43
+ path: string;
44
+ type: NodeType;
45
+ /** Byte length for files (omitted for directories). */
46
+ size?: number;
47
+ }
48
+
49
+ export type WatchEventType = 'create' | 'update' | 'remove';
50
+
51
+ export interface WatchEvent {
52
+ type: WatchEventType;
53
+ /** Normalized, root-relative path of the affected node. */
54
+ path: string;
55
+ }
56
+
57
+ export type WatchCallback = (event: WatchEvent) => void;
58
+
59
+ /**
60
+ * The one file interface every storage backend implements. All methods take and
61
+ * return normalized, root-relative POSIX paths.
62
+ */
63
+ export interface StorageBackend {
64
+ /** Stable identifier for the active backend (for diagnostics/feature flags). */
65
+ readonly id: string;
66
+
67
+ /** Read a UTF-8 text file. Rejects if the path is missing or is a directory. */
68
+ read(path: string): Promise<string>;
69
+ /** Read raw bytes. Rejects if the path is missing or is a directory. */
70
+ readBytes(path: string): Promise<Uint8Array>;
71
+
72
+ /** Write a file, creating parent directories as needed. Emits a watch event. */
73
+ write(path: string, data: string | Uint8Array): Promise<void>;
74
+
75
+ /** Shallow directory listing. Rejects if the path is missing or is a file. */
76
+ list(dir: string): Promise<DirEntry[]>;
77
+
78
+ /** Stat a node, or resolve to `null` if it does not exist. */
79
+ stat(path: string): Promise<Stat | null>;
80
+
81
+ /** Convenience existence check. */
82
+ exists(path: string): Promise<boolean>;
83
+
84
+ /** Recursively create a directory (no-op if it already exists). */
85
+ mkdir(path: string): Promise<void>;
86
+
87
+ /** Recursively remove a file or directory (no-op if missing). Emits an event. */
88
+ remove(path: string): Promise<void>;
89
+
90
+ /** Subscribe to change events. Returns an unsubscribe function. */
91
+ watch(callback: WatchCallback): () => void;
92
+ }
93
+
94
+ /**
95
+ * Non-file host capabilities, kept separate from `StorageBackend` because they
96
+ * do not reduce to path operations. A backend may implement none of these — a
97
+ * pure-browser host typically omits `reveal`, and exposes `pickProjectRoot`
98
+ * only where the File System Access API is available.
99
+ */
100
+ export interface HostCapabilities {
101
+ /**
102
+ * Prompt the user to choose a project root and return a backend rooted there.
103
+ * Browser: `showDirectoryPicker()` (Chromium). Resolves to `null` if the user
104
+ * cancels. Absent entirely where unsupported.
105
+ */
106
+ pickProjectRoot?(): Promise<StorageBackend | null>;
107
+
108
+ /** Reveal a path in the OS file manager. Absent in the browser. */
109
+ reveal?(path: string): Promise<void>;
110
+ }
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Project-tool loader — W4/W3 of `docs/DATA-TOOLS-DESIGN.md` (§3.1 contract,
3
+ * §3.5 discovery). The tool sibling of `script-loader.ts`: project
4
+ * `src/tools/*.tool.tsx` modules are dynamically imported via Vite's `/@fs/`
5
+ * path (the same loader pattern, cache-busted with `?t=` on every refresh so
6
+ * a re-discover always gets fresh code).
7
+ *
8
+ * The registry here is LIVE project state derived from the folder scan
9
+ * (`GET /__editor/tool-files`, editor-server.ts) — never a cached manifest
10
+ * (§3.5 rule 2). Dock tools (`placement: 'dock'`) are consumed by
11
+ * `BottomPanel` through the tiny subscribe/get store below
12
+ * (`useSyncExternalStore`-shaped, mirroring `editorConsole`'s contract).
13
+ * Inspector tools (`placement: 'inspector'`, W3) are threaded into the
14
+ * PERMANENT section seam instead: each one becomes an
15
+ * `inspector-section-registry` registration (the same contract the
16
+ * first-party adapters use), wrapped in `InspectorToolSection`'s
17
+ * editor-styled chrome + error boundary. The registry's own change
18
+ * notification re-renders the inspector shell when tools (re)load.
19
+ *
20
+ * HMR: `dev.ts`'s `vgai-script-hmr` plugin already broadcasts
21
+ * `vgai:script-update` for every project `.tsx` change (the same event
22
+ * `play-mode.ts` reloads scripts from). The module-level listener at the
23
+ * bottom re-runs the whole discover+import pass whenever the changed file
24
+ * lives under `src/tools/` — Vite has already invalidated the changed
25
+ * module(s) in its graph by then, so the cache-busted re-import picks up
26
+ * edits to a tool AND to any helper module it imports, no editor reload.
27
+ *
28
+ * Failure physics (§6.5 "errors teach", §3's "never crashes the editor"):
29
+ * a tool file that fails to import, or that lacks the `tool`/default-component
30
+ * export pair, gets ONE console teaching error naming the file and citing the
31
+ * template example, and is skipped — every other tool still loads. A throwing
32
+ * inspector `match` is caught per call (logged once per load) and treated as
33
+ * "no match". Render-time crashes are the error boundary's job
34
+ * (`ToolHost.tsx` / `InspectorToolSection.tsx`), not ours.
35
+ */
36
+
37
+ import type { AuthoringAdapter, EditorNode } from '@engine/adapter';
38
+ import type { InspectorToolProps, ToolConfig } from '@engine/tools/define-tool';
39
+ import { type ComponentType, createElement } from 'react';
40
+ import { InspectorToolSection } from './components/InspectorToolSection';
41
+ import { registerInspectorSections } from './inspector-section-registry';
42
+ import { getCurrentProject } from './project-manager';
43
+
44
+ /** Fields shared by every successfully loaded project tool. */
45
+ interface LoadedToolBase {
46
+ /** `ToolConfig.id` — the mount's `data-testid` suffix. */
47
+ id: string;
48
+ /** `ToolConfig.title` — the tab label / section header (after ⚙, §3.5). */
49
+ title: string;
50
+ /** Project-relative source file, e.g. `src/tools/example.tool.tsx`. */
51
+ file: string;
52
+ /** Bumped on every (re)load — keys the host so an HMR reload remounts the
53
+ * component fresh and clears any tripped error boundary. */
54
+ version: number;
55
+ }
56
+
57
+ /** One successfully loaded dock tool, ready for `BottomPanel` to render. */
58
+ export interface DockTool extends LoadedToolBase {
59
+ placement: 'dock';
60
+ /** The module's default export — plain React, rendered inside `ToolHost`. */
61
+ Component: ComponentType;
62
+ }
63
+
64
+ /** One successfully loaded inspector tool (W3), ready to register. */
65
+ export interface InspectorTool extends LoadedToolBase {
66
+ placement: 'inspector';
67
+ /** The module's default export, rendered inside `InspectorToolSection`. */
68
+ Component: ComponentType<InspectorToolProps>;
69
+ /** The tool's own selection predicate (§3.1 — registry contract). */
70
+ match: (node: EditorNode | null, adapter: AuthoringAdapter) => boolean;
71
+ }
72
+
73
+ export type ProjectTool = DockTool | InspectorTool;
74
+
75
+ const TEMPLATE_EXAMPLE = 'the template worked example: src/tools/example.tool.tsx';
76
+
77
+ /**
78
+ * Validate a dynamically imported tool module against the §3.1 contract:
79
+ * `export const tool = defineTool(…)` plus a default-exported React
80
+ * component (and, for `placement: 'inspector'`, a callable `match`). Pure
81
+ * and export-tested; returns either the loaded tool or the teaching error to
82
+ * print — the caller owns the console (`refreshProjectTools`) so tests don't
83
+ * have to spy on it.
84
+ */
85
+ export function extractProjectTool(
86
+ mod: unknown,
87
+ file: string,
88
+ version: number,
89
+ ): { tool: ProjectTool } | { error: string } {
90
+ const record = (mod ?? {}) as Record<string, unknown>;
91
+ const config = record['tool'] as ToolConfig | undefined;
92
+ if (
93
+ config === null ||
94
+ typeof config !== 'object' ||
95
+ typeof config.id !== 'string' ||
96
+ typeof config.title !== 'string'
97
+ ) {
98
+ return {
99
+ error:
100
+ `[tools] ${file} does not export \`tool\` — every project tool exports ` +
101
+ `\`export const tool = defineTool({ id, title, placement: 'dock' | 'inspector' })\` ` +
102
+ `(docs/DATA-TOOLS-DESIGN.md §3.1; see ${TEMPLATE_EXAMPLE}). Skipped.`,
103
+ };
104
+ }
105
+ if (config.placement !== 'dock' && config.placement !== 'inspector') {
106
+ return {
107
+ error:
108
+ `[tools] ${file} has placement ${JSON.stringify((config as { placement?: unknown }).placement)} — ` +
109
+ `the placements are 'dock' and 'inspector' (docs/DATA-TOOLS-DESIGN.md §3.2). Skipped.`,
110
+ };
111
+ }
112
+ const Component = record['default'];
113
+ if (typeof Component !== 'function') {
114
+ return {
115
+ error:
116
+ `[tools] ${file} has no default-exported React component — export default a plain ` +
117
+ `function component next to \`tool\` (docs/DATA-TOOLS-DESIGN.md §3.1; see ` +
118
+ `${TEMPLATE_EXAMPLE}). Skipped.`,
119
+ };
120
+ }
121
+ const base = { id: config.id, title: config.title, file, version };
122
+ if (config.placement === 'inspector') {
123
+ // Re-checked here (defineTool already validates) because a hand-rolled
124
+ // `tool` object can bypass defineTool entirely.
125
+ if (typeof config.match !== 'function') {
126
+ return {
127
+ error:
128
+ `[tools] ${file} has placement 'inspector' but no \`match\` function — pass ` +
129
+ `match: (node) => boolean in defineTool (docs/DATA-TOOLS-DESIGN.md §3.1; see ` +
130
+ `${TEMPLATE_EXAMPLE}). Skipped.`,
131
+ };
132
+ }
133
+ return {
134
+ tool: {
135
+ ...base,
136
+ placement: 'inspector',
137
+ Component: Component as ComponentType<InspectorToolProps>,
138
+ match: config.match,
139
+ },
140
+ };
141
+ }
142
+ return { tool: { ...base, placement: 'dock', Component: Component as ComponentType } };
143
+ }
144
+
145
+ // --- The live registry (subscribe/get store, `useSyncExternalStore`-shaped) ---
146
+
147
+ let _dockTools: DockTool[] = [];
148
+ const _listeners = new Set<() => void>();
149
+
150
+ /** Current dock tools (stable reference between refreshes). */
151
+ export function getDockTools(): DockTool[] {
152
+ return _dockTools;
153
+ }
154
+
155
+ /** Subscribe to dock-tool registry changes. Returns an unsubscribe function. */
156
+ export function subscribeDockTools(fn: () => void): () => void {
157
+ _listeners.add(fn);
158
+ return () => _listeners.delete(fn);
159
+ }
160
+
161
+ // Inspector tools live in the SECTION registry, not a parallel store: each
162
+ // loaded tool holds one `registerInspectorSections` registration, replaced
163
+ // wholesale on every refresh (fresh Section identity per load = remount =
164
+ // tripped error boundaries clear on HMR, same physics as the dock's
165
+ // version-keyed `ToolHost`).
166
+ let _unregisterInspectorTools: Array<() => void> = [];
167
+
168
+ function registrationForInspectorTool(tool: InspectorTool): () => void {
169
+ // A project `match` that throws must not take the inspector shell down —
170
+ // it runs inside `Inspector.tsx`'s render. Teach once per load, then
171
+ // treat as "no match" (the §6.5 failure physics, at match granularity).
172
+ let warned = false;
173
+ return registerInspectorSections({
174
+ match: (node, adapter) => {
175
+ try {
176
+ return tool.match(node, adapter);
177
+ } catch (err) {
178
+ if (!warned) {
179
+ warned = true;
180
+ teachingError(
181
+ `[tools] ${tool.file}'s match() threw — treating it as "no match" until the file ` +
182
+ `is fixed and saved (docs/DATA-TOOLS-DESIGN.md §3.1).\n${String(err)}`,
183
+ );
184
+ }
185
+ return false;
186
+ }
187
+ },
188
+ Section: ({ adapter, nodeId }) =>
189
+ createElement(InspectorToolSection, {
190
+ id: tool.id,
191
+ title: tool.title,
192
+ file: tool.file,
193
+ Component: tool.Component,
194
+ adapter,
195
+ nodeId,
196
+ }),
197
+ });
198
+ }
199
+
200
+ function publishInspectorTools(tools: InspectorTool[]): void {
201
+ for (const unregister of _unregisterInspectorTools) unregister();
202
+ _unregisterInspectorTools = tools.map(registrationForInspectorTool);
203
+ }
204
+
205
+ /**
206
+ * Re-run the whole discovery pass: scan (`/__editor/tool-files`) → import
207
+ * each module fresh (`/@fs/` + `?t=` cache-bust) → validate → publish (dock
208
+ * store + inspector-section registrations). Cheap by design (a project has
209
+ * a handful of tools), so it doubles as the v1 HMR path — see the module
210
+ * doc comment. Never throws: per-file failures are console teaching errors
211
+ * and the file is skipped.
212
+ */
213
+ export async function refreshProjectTools(): Promise<void> {
214
+ const project = getCurrentProject();
215
+ if (!project) {
216
+ publishDockTools([]);
217
+ publishInspectorTools([]);
218
+ return;
219
+ }
220
+
221
+ let paths: string[];
222
+ try {
223
+ const res = await fetch('/__editor/tool-files');
224
+ if (!res.ok) return; // no dev server route (e.g. browser mode) — keep whatever we had
225
+ const { files } = (await res.json()) as { files: Array<{ path: string }> };
226
+ paths = files.map((f) => f.path);
227
+ } catch {
228
+ return; // server unreachable — keep whatever we had
229
+ }
230
+
231
+ const version = Date.now();
232
+ const nextDock: DockTool[] = [];
233
+ const nextInspector: InspectorTool[] = [];
234
+ const seen = new Set<string>();
235
+ for (const file of paths) {
236
+ let mod: unknown;
237
+ try {
238
+ // Same `/@fs/` dynamic-import path as script-loader.ts — the module
239
+ // arrives through Vite (transpiled, aliases resolved), and the `?t=`
240
+ // bust guarantees a fresh evaluation on every refresh.
241
+ mod = await import(/* @vite-ignore */ `/@fs/${project.rootPath}/${file}?t=${version}`);
242
+ } catch (err) {
243
+ // Import-time failure: a syntax error, or defineTool itself throwing
244
+ // its teaching error — surface it verbatim, name the file, keep the
245
+ // editor alive.
246
+ teachingError(
247
+ `[tools] failed to load ${file} — fix the error below or delete the file ` +
248
+ `(see ${TEMPLATE_EXAMPLE}). Skipped.\n${String(err)}`,
249
+ );
250
+ continue;
251
+ }
252
+ const result = extractProjectTool(mod, file, version);
253
+ if ('error' in result) {
254
+ teachingError(result.error);
255
+ continue;
256
+ }
257
+ if (seen.has(result.tool.id)) {
258
+ teachingError(
259
+ `[tools] ${file} reuses tool id "${result.tool.id}" — ids must be unique per project ` +
260
+ `(they key the dock tabs and inspector sections). Skipped.`,
261
+ );
262
+ continue;
263
+ }
264
+ seen.add(result.tool.id);
265
+ if (result.tool.placement === 'inspector') nextInspector.push(result.tool);
266
+ else nextDock.push(result.tool);
267
+ }
268
+ publishDockTools(nextDock);
269
+ publishInspectorTools(nextInspector);
270
+ }
271
+
272
+ function publishDockTools(next: DockTool[]): void {
273
+ _dockTools = next;
274
+ for (const fn of _listeners) fn();
275
+ }
276
+
277
+ function teachingError(message: string): void {
278
+ // biome-ignore lint/suspicious/noConsole: deliberate, greppable — a broken project tool must be loud in the editor console without crashing the editor (§6.5 "errors teach"; mirrors data-asset.ts's hotSwap rejection)
279
+ console.error(message);
280
+ }
281
+
282
+ // --- HMR: re-discover on any src/tools/ change (see module doc comment) ---
283
+ if (import.meta.hot) {
284
+ import.meta.hot.on('vgai:script-update', (data: { file: string }) => {
285
+ // `data.file` is absolute and forward-slash normalized (dev.ts). Any
286
+ // change under src/tools/ — the tool itself OR a helper it imports —
287
+ // triggers a full cheap refresh; Vite already invalidated the module.
288
+ if (!data.file.includes('/src/tools/')) return;
289
+ void refreshProjectTools();
290
+ });
291
+ }