@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,246 @@
1
+ /**
2
+ * In-browser TypeScript transpile + module resolution.
3
+ *
4
+ * This is the client-side replacement for Vite's on-the-fly transpile of
5
+ * project scripts. It lets the editor hot-reload edited component source
6
+ * **without any server round-trip** — no Node, no Vite dev server, no file
7
+ * write. The flow is:
8
+ *
9
+ * TS source string
10
+ * → esbuild-wasm transform (TS/TSX → ESM JS, in a Web Worker)
11
+ * → rewrite bare imports (`three`, `@engine/...`) to blob-URL shims that
12
+ * re-export the **live, already-running** singletons
13
+ * → Blob → URL.createObjectURL → dynamic import()
14
+ *
15
+ * Sharing singletons is the load-bearing detail: a hot-swapped component must
16
+ * extend the SAME `GameComponent` class and use the SAME `THREE` instance as
17
+ * the running runtime, or `instanceof` checks and prototype chains break. We
18
+ * therefore never pull `three`/engine code from a CDN — we re-export the
19
+ * modules the editor already imported, via `registerRuntimeModules()`.
20
+ *
21
+ * Idiomatic: esbuild-wasm is the same engine Vite uses, just run client-side;
22
+ * we call its documented API directly (no wrapper abstraction).
23
+ */
24
+
25
+ import * as esbuild from 'esbuild-wasm';
26
+ // Vite serves the wasm binary as a static asset URL — fully local, no CDN.
27
+ import esbuildWasmUrl from 'esbuild-wasm/esbuild.wasm?url';
28
+ import { GAME_GLOBALS_PRELUDE } from './gated-globals';
29
+
30
+ /** Live runtime modules, keyed by the bare specifier used to import them. */
31
+ type ModuleNamespace = Record<string, unknown>;
32
+ const runtimeModules = new Map<string, ModuleNamespace>();
33
+
34
+ /** Per-specifier shim blob URLs, memoized (the live module identity is stable). */
35
+ const shimUrlCache = new Map<string, string>();
36
+
37
+ let initPromise: Promise<void> | null = null;
38
+
39
+ /**
40
+ * Initialize esbuild-wasm exactly once. Safe to call repeatedly — concurrent
41
+ * callers share the same promise.
42
+ */
43
+ export function initBrowserTranspile(): Promise<void> {
44
+ if (!initPromise) {
45
+ initPromise = esbuild.initialize({ wasmURL: esbuildWasmUrl }).catch((err) => {
46
+ // Allow a retry on failure rather than caching a rejected promise.
47
+ initPromise = null;
48
+ throw err;
49
+ });
50
+ }
51
+ return initPromise;
52
+ }
53
+
54
+ /**
55
+ * Register the live runtime module namespaces that transpiled user code is
56
+ * allowed to import. Call once at editor/runtime init with the modules the
57
+ * host already imported statically (so identities are shared).
58
+ *
59
+ * registerRuntimeModules({
60
+ * three: THREE,
61
+ * '@engine/ecs/game-component': gameComponentModule,
62
+ * });
63
+ */
64
+ export function registerRuntimeModules(modules: Record<string, ModuleNamespace>): void {
65
+ for (const [spec, ns] of Object.entries(modules)) {
66
+ runtimeModules.set(spec, ns);
67
+ shimUrlCache.delete(spec); // invalidate any stale shim
68
+ }
69
+ // Exposed on globalThis so shim modules (which run in their own module scope)
70
+ // can reach the live namespaces by specifier.
71
+ (globalThis as Record<string, unknown>)['__vgaiModules'] = Object.fromEntries(runtimeModules);
72
+ }
73
+
74
+ const IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
75
+
76
+ // Reserved words can't be `export const <name>` binding identifiers. Some real
77
+ // modules expose them as convenience named exports (e.g. zod exports `catch`,
78
+ // `enum`, `function`, `void`) — re-exporting those verbatim would make the shim
79
+ // itself a SyntaxError ("Unexpected token 'catch'"). They're never imported by
80
+ // those names, so we skip them.
81
+ const RESERVED_WORDS = new Set([
82
+ 'break',
83
+ 'case',
84
+ 'catch',
85
+ 'class',
86
+ 'const',
87
+ 'continue',
88
+ 'debugger',
89
+ 'default',
90
+ 'delete',
91
+ 'do',
92
+ 'else',
93
+ 'enum',
94
+ 'export',
95
+ 'extends',
96
+ 'false',
97
+ 'finally',
98
+ 'for',
99
+ 'function',
100
+ 'if',
101
+ 'import',
102
+ 'in',
103
+ 'instanceof',
104
+ 'new',
105
+ 'null',
106
+ 'return',
107
+ 'super',
108
+ 'switch',
109
+ 'this',
110
+ 'throw',
111
+ 'true',
112
+ 'try',
113
+ 'typeof',
114
+ 'var',
115
+ 'void',
116
+ 'while',
117
+ 'with',
118
+ 'yield',
119
+ 'let',
120
+ 'static',
121
+ 'await',
122
+ 'implements',
123
+ 'interface',
124
+ 'package',
125
+ 'private',
126
+ 'protected',
127
+ 'public',
128
+ ]);
129
+
130
+ /** Build (or reuse) a blob-URL ESM module that re-exports a live namespace. */
131
+ function shimUrlFor(spec: string): string {
132
+ const cached = shimUrlCache.get(spec);
133
+ if (cached) return cached;
134
+
135
+ const ns = runtimeModules.get(spec);
136
+ if (!ns) {
137
+ throw new Error(
138
+ `Cannot resolve import '${spec}' in browser HMR — not a registered runtime module. ` +
139
+ `Register it via registerRuntimeModules({ '${spec}': <module> }).`,
140
+ );
141
+ }
142
+
143
+ const lines = [`const __m = globalThis.__vgaiModules[${JSON.stringify(spec)}];`];
144
+ for (const key of Object.keys(ns)) {
145
+ if (key === 'default' || !IDENT_RE.test(key) || RESERVED_WORDS.has(key)) continue;
146
+ lines.push(`export const ${key} = __m[${JSON.stringify(key)}];`);
147
+ }
148
+ if ('default' in ns) lines.push(`export default __m[${JSON.stringify('default')}];`);
149
+
150
+ const url = URL.createObjectURL(new Blob([lines.join('\n')], { type: 'text/javascript' }));
151
+ shimUrlCache.set(spec, url);
152
+ return url;
153
+ }
154
+
155
+ // Matches `from '...'` / `from "..."` and bare `import '...'`, but NOT dynamic
156
+ // `import('...')` (a `(` follows `import`, not a quote) — those stay untouched.
157
+ const IMPORT_RE = /(\bfrom\s*|\bimport\s+)(['"])([^'"]+)\2/g;
158
+
159
+ /** True for specifiers that are already directly importable in the browser. */
160
+ function isResolvable(spec: string): boolean {
161
+ return (
162
+ spec.startsWith('.') ||
163
+ spec.startsWith('/') ||
164
+ spec.startsWith('blob:') ||
165
+ spec.startsWith('http:') ||
166
+ spec.startsWith('https:')
167
+ );
168
+ }
169
+
170
+ /** Rewrite bare imports in transpiled JS to blob-URL shims over live modules. */
171
+ function rewriteImports(code: string): string {
172
+ return code.replace(IMPORT_RE, (match, lead: string, quote: string, spec: string) => {
173
+ if (isResolvable(spec)) return match;
174
+ return `${lead}${quote}${shimUrlFor(spec)}${quote}`;
175
+ });
176
+ }
177
+
178
+ /**
179
+ * Transpile TS/TSX source to an importable ESM module URL (blob:). Bare imports
180
+ * are rewritten to live-singleton shims.
181
+ *
182
+ * Low-level primitive: it does NOT apply the game-globals shadow. Project /
183
+ * game code must go through {@link importFromSource} (which wraps this) so its
184
+ * `window`/`document` input listeners stay focus-gated; only shadow-exempt
185
+ * editor-internal source should call this directly.
186
+ */
187
+ export async function transpileToModuleUrl(
188
+ source: string,
189
+ filename = 'in-browser-edit.tsx',
190
+ ): Promise<string> {
191
+ await initBrowserTranspile();
192
+ const { code } = await esbuild.transform(source, {
193
+ loader: filename.endsWith('.tsx') ? 'tsx' : 'ts',
194
+ format: 'esm',
195
+ target: 'es2022',
196
+ sourcefile: filename,
197
+ // Match the editor's tsconfig: decorators + classic field semantics.
198
+ tsconfigRaw: {
199
+ compilerOptions: { experimentalDecorators: true, useDefineForClassFields: false },
200
+ },
201
+ });
202
+ const rewritten = rewriteImports(code);
203
+ return URL.createObjectURL(new Blob([rewritten], { type: 'text/javascript' }));
204
+ }
205
+
206
+ /**
207
+ * Prepend the game-globals lexical shadow to project source — the browser-side
208
+ * analogue of the dev server's `vgai-game-globals` Vite plugin
209
+ * (`server/dev.ts` + `shouldShadowGameGlobals`). The server shadows per-module
210
+ * because Vite transforms EVERY module indiscriminately and must be told which
211
+ * ones are game code; the browser has no such firehose — {@link importFromSource}
212
+ * is the single client-transpile chokepoint and only ever receives project
213
+ * code, so "scoping" here is by construction (route project code through this
214
+ * seam; never through the raw {@link transpileToModuleUrl}). Applying the shadow
215
+ * to EVERY module that reaches this seam is what keeps game `window`/`document`
216
+ * input focus-gated on the whole browser path — the bundled project scripts
217
+ * (`browser-project-scripts.ts`) AND every single-file hot-swap alike, not just
218
+ * hot-swaps. Kept as its own named, exported function so that invariant is
219
+ * unit-testable and can't be silently dropped (`browser-globals-shadow.test.ts`).
220
+ */
221
+ export function applyGameGlobalsShadow(source: string): string {
222
+ return GAME_GLOBALS_PRELUDE + source;
223
+ }
224
+
225
+ /**
226
+ * Transpile TS/TSX source in-browser and dynamically import it, returning the
227
+ * module namespace. The blob URL is revoked once the import settles.
228
+ *
229
+ * This is the ONLY path project code should take: it applies the game-globals
230
+ * shadow ({@link applyGameGlobalsShadow}) so a component's `window`/`document`
231
+ * resolve to the editor's gated proxies (input stays focus-gated on the HMR
232
+ * path too). The raw {@link transpileToModuleUrl} deliberately does NOT shadow —
233
+ * it is the low-level primitive; feeding project code to it directly would leak
234
+ * ungated input listeners.
235
+ */
236
+ export async function importFromSource(
237
+ source: string,
238
+ filename = 'in-browser-edit.tsx',
239
+ ): Promise<Record<string, unknown>> {
240
+ const url = await transpileToModuleUrl(applyGameGlobalsShadow(source), filename);
241
+ try {
242
+ return (await import(/* @vite-ignore */ url)) as Record<string, unknown>;
243
+ } finally {
244
+ URL.revokeObjectURL(url);
245
+ }
246
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Browser-mode live-reload (Phase A2).
3
+ *
4
+ * A server-backed editor learns about file changes two ways: chokidar → SSE
5
+ * (`scene-changed`, consumed by `connectSceneEvents`) and the Vite dev server's
6
+ * `vgai:script-update` HMR event (consumed in `play-mode.ts`). A server-less
7
+ * browser editor has neither. What it DOES have is `StorageBackend.watch()`:
8
+ * because the browser backend (OPFS / File System Access) is the sole writer,
9
+ * its self-derived write events ARE the change signal — exactly what HMR needs.
10
+ *
11
+ * This module is the browser counterpart of those two consumers. `asset-events`
12
+ * and `play-mode` keep the server (SSE / `import.meta.hot`) paths and route
13
+ * through here only when `getEditorMode() === 'browser'`.
14
+ *
15
+ * A `WatchEvent` carries only `{ type, path }` — no bytes — so each helper reads
16
+ * the file back from the backend before dispatching:
17
+ * - Scene changes: today this is the editor's own autosave echo, suppressed
18
+ * downstream by the store's `_lastSavedContent` guard (a self-write reads
19
+ * back byte-identical). It becomes a genuine external-change signal once A4
20
+ * adds File System Access out-of-band change detection — this wiring needs
21
+ * no further change for that.
22
+ * - Component sources: fires whenever anything writes a project `.ts`/`.tsx`
23
+ * through the backend (a future code panel, an agent apply-diff, the dev
24
+ * `storage-hmr-demo`), driving the same in-browser hot-swap the server's
25
+ * `vgai:script-update` handler performs.
26
+ */
27
+
28
+ import type { StorageBackend, WatchEvent } from './storage';
29
+ import { getStorageBackend } from './storage';
30
+
31
+ /** A scene document the editor can reload (`.vscn.json`). */
32
+ export function isSceneFile(path: string): boolean {
33
+ return path.endsWith('.vscn.json');
34
+ }
35
+
36
+ /**
37
+ * A project source file that hot-swaps in place (a GameComponent module).
38
+ *
39
+ * `.ts`/`.tsx` only, excluding the entry (`main.ts`) and component registry
40
+ * (`registry.ts`) — editing those needs a warm restart (server mode) or a
41
+ * client-side re-bundle (browser mode, not yet available), never a live
42
+ * prototype swap. Mirrors the server `vgai:script-update` handler's split.
43
+ */
44
+ export function isHotSwapScriptFile(path: string): boolean {
45
+ if (!path.endsWith('.ts') && !path.endsWith('.tsx')) return false;
46
+ const base = path.slice(path.lastIndexOf('/') + 1);
47
+ return base !== 'main.ts' && base !== 'registry.ts';
48
+ }
49
+
50
+ /** create/update carry new content; remove does not (nothing to reload). */
51
+ function isWriteEvent(ev: WatchEvent): boolean {
52
+ return ev.type === 'create' || ev.type === 'update';
53
+ }
54
+
55
+ /**
56
+ * Subscribe to backend writes matching `match`, read the new content, and hand
57
+ * it to `deliver`. Returns an unsubscribe function. Reads that fail (a file
58
+ * removed between the event and the read) are ignored.
59
+ */
60
+ function watchFileWrites(
61
+ backend: StorageBackend,
62
+ match: (path: string) => boolean,
63
+ deliver: (path: string, content: string) => void,
64
+ ): () => void {
65
+ return backend.watch((ev) => {
66
+ if (!isWriteEvent(ev) || !match(ev.path)) return;
67
+ void backend
68
+ .read(ev.path)
69
+ .then((content) => deliver(ev.path, content))
70
+ .catch(() => {
71
+ /* file vanished between the write event and the read — ignore */
72
+ });
73
+ });
74
+ }
75
+
76
+ /**
77
+ * Browser analogue of `connectSceneEvents`: fire `onChange` with the reloaded
78
+ * `.vscn.json` content whenever the active scene file is written through the
79
+ * backend. Returns an unsubscribe function.
80
+ */
81
+ export function watchSceneChanges(
82
+ onChange: (event: { path: string; content: string }) => void,
83
+ backend: StorageBackend = getStorageBackend(),
84
+ ): () => void {
85
+ return watchFileWrites(backend, isSceneFile, (path, content) => onChange({ path, content }));
86
+ }
87
+
88
+ /**
89
+ * Browser analogue of the `vgai:script-update` HMR handler: fire `onChange` with
90
+ * a hot-swappable component source whenever one is written through the backend.
91
+ * Returns an unsubscribe function.
92
+ */
93
+ export function watchScriptChanges(
94
+ onChange: (event: { path: string; source: string }) => void,
95
+ backend: StorageBackend = getStorageBackend(),
96
+ ): () => void {
97
+ return watchFileWrites(backend, isHotSwapScriptFile, (path, source) =>
98
+ onChange({ path, source }),
99
+ );
100
+ }
@@ -0,0 +1,326 @@
1
+ /**
2
+ * Command listener — receives commands from the editor server via SSE
3
+ * and dispatches them to the EditorStore and play-mode functions.
4
+ *
5
+ * The server broadcasts `editor-command` events sent by the SDK/CLI.
6
+ * This module translates them into store method calls.
7
+ */
8
+
9
+ import { parseSceneFile, SceneParseError } from '@engine/scene/parse';
10
+ import { connectEvents, reportCommandResult, reportEditorState } from './editor-api';
11
+ import { editorConsole } from './editor-console';
12
+ import { getEditorMode } from './editor-mode';
13
+ import type { AssetKind, EditorStore, HelperVisibility } from './editor-store';
14
+ import {
15
+ enterPlayMode,
16
+ exitPlayMode,
17
+ pausePlayMode,
18
+ resumePlayMode,
19
+ stepPlayMode,
20
+ } from './play-mode';
21
+
22
+ export interface EditorCommand {
23
+ type: string;
24
+ _requestId?: string;
25
+ [key: string]: unknown;
26
+ }
27
+
28
+ export interface CommandResult {
29
+ ok: boolean;
30
+ error?: string;
31
+ /** Optional payload for commands that answer with data (e.g. `capture-viewport`'s base64 PNG). */
32
+ data?: Record<string, unknown>;
33
+ }
34
+
35
+ /** Live viewport camera pose — undefined when nothing has bound a camera yet
36
+ * (see `EditorStore.cameraPose`'s doc comment). */
37
+ function collectCamera(store: EditorStore):
38
+ | {
39
+ position: { x: number; y: number; z: number };
40
+ target: { x: number; y: number; z: number };
41
+ fov: number;
42
+ }
43
+ | undefined {
44
+ const pose = store.cameraPose;
45
+ if (!pose) return undefined;
46
+ return {
47
+ position: { x: pose.position.x, y: pose.position.y, z: pose.position.z },
48
+ target: { x: pose.target.x, y: pose.target.y, z: pose.target.z },
49
+ fov: pose.fov,
50
+ };
51
+ }
52
+
53
+ export function collectState(store: EditorStore): Record<string, unknown> {
54
+ return {
55
+ playState: store.playState,
56
+ selectedEntityId: store.selectedEntityId,
57
+ selectedEntityIds: [...store.selectedEntityIds],
58
+ activeViewportTab: store.activeViewportTab,
59
+ activeTabKey: store.activeTabKey,
60
+ showGrid: store.showGrid,
61
+ showHelpers: store.showHelpers,
62
+ showStats: store.showStats,
63
+ shadingMode: store.shadingMode,
64
+ helperVisibility: { ...store.helperVisibility },
65
+ transformMode: store.transformMode,
66
+ transformSpace: store.transformSpace,
67
+ snapEnabled: store.snapEnabled,
68
+ entityCount: store.allEntities().length,
69
+ savePath: store.savePath,
70
+ // Camera facet (B3-followup): real pose when a viewport is bound, absent
71
+ // otherwise — never a fabricated value (see EditorStore.cameraPose).
72
+ camera: collectCamera(store),
73
+ // Hierarchy facet (B3-followup): the real entity tree, flattened to
74
+ // id/name/childIds rows (same shape editor.hierarchy.inspect declares).
75
+ // `hierarchyRows()` lives on EditorStore (inside the SceneEntity
76
+ // allowlist — see authoring-inversion-guard.test.ts) so this file never
77
+ // needs to import SceneEntity itself.
78
+ entities: store.hierarchyRows(),
79
+ };
80
+ }
81
+
82
+ /** Validate the current scene document against the Zod schema. */
83
+ function validateCurrentScene(store: EditorStore): CommandResult {
84
+ const doc = store.getDocument();
85
+ try {
86
+ parseSceneFile(doc);
87
+ return { ok: true };
88
+ } catch (err) {
89
+ if (err instanceof SceneParseError) {
90
+ return { ok: false, error: err.message };
91
+ }
92
+ return { ok: false, error: `Scene validation failed: ${err}` };
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Exported (alongside `collectState` above) so unit tests can dispatch
98
+ * commands directly against a headless `EditorStore`, with no SSE/server
99
+ * round-trip — see `packages/editor/test/command-listener.test.ts`, notably
100
+ * the false-ack regression test: an unrecognized command must return
101
+ * `{ok:false}`, never fall through to a fabricated `{ok:true}`.
102
+ */
103
+ export async function handleCommand(
104
+ store: EditorStore,
105
+ cmd: EditorCommand,
106
+ ): Promise<CommandResult> {
107
+ switch (cmd['type']) {
108
+ // Play control
109
+ case 'play': {
110
+ // Validate before play — enterPlayMode() also validates, but we need
111
+ // the error message to flow back to the SDK/CLI.
112
+ const validation = validateCurrentScene(store);
113
+ if (!validation.ok) {
114
+ return validation;
115
+ }
116
+ try {
117
+ await enterPlayMode();
118
+ } catch (err) {
119
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
120
+ }
121
+ break;
122
+ }
123
+ case 'stop':
124
+ exitPlayMode();
125
+ break;
126
+ case 'pause':
127
+ pausePlayMode();
128
+ break;
129
+ case 'resume':
130
+ resumePlayMode();
131
+ break;
132
+ case 'step':
133
+ stepPlayMode();
134
+ break;
135
+
136
+ // Selection
137
+ case 'select':
138
+ store.select((cmd['id'] as string | null) ?? null);
139
+ break;
140
+ case 'select-multiple':
141
+ store.selectMultiple(cmd['ids'] as string[]);
142
+ break;
143
+ case 'select-all':
144
+ store.selectAll();
145
+ break;
146
+
147
+ // Viewport
148
+ case 'focus-entity':
149
+ store.focusOnEntity(cmd['id'] as string);
150
+ break;
151
+ case 'focus-selection':
152
+ store.focusOnSelection();
153
+ break;
154
+ case 'view-preset':
155
+ store.setViewPreset(cmd['preset'] as 'top' | 'front' | 'right' | 'perspective');
156
+ break;
157
+ case 'set-camera': {
158
+ const position = cmd['position'] as { x: number; y: number; z: number } | undefined;
159
+ const target = cmd['target'] as { x: number; y: number; z: number } | undefined;
160
+ const fov = cmd['fov'] as number | undefined;
161
+ if (
162
+ !position ||
163
+ !target ||
164
+ typeof position.x !== 'number' ||
165
+ typeof position.y !== 'number' ||
166
+ typeof position.z !== 'number' ||
167
+ typeof target.x !== 'number' ||
168
+ typeof target.y !== 'number' ||
169
+ typeof target.z !== 'number'
170
+ ) {
171
+ return { ok: false, error: 'set-camera requires numeric {x,y,z} position and target.' };
172
+ }
173
+ store.setCameraPose(position, target, fov);
174
+ break;
175
+ }
176
+
177
+ // Panels
178
+ case 'viewport-tab':
179
+ store.setActiveViewportTab(cmd['tab'] as 'scene' | 'game');
180
+ break;
181
+ case 'active-tab':
182
+ store.setActiveTab(cmd['key'] as string);
183
+ break;
184
+ case 'open-asset-tab':
185
+ store.openAssetTab(cmd['path'] as string, cmd['kind'] as AssetKind);
186
+ break;
187
+ case 'close-asset-tab':
188
+ store.closeAssetTab(cmd['key'] as string);
189
+ break;
190
+ case 'toggle-command-palette':
191
+ store.toggleCommandPalette();
192
+ break;
193
+ case 'toggle-console':
194
+ window.dispatchEvent(new CustomEvent('editor:toggle-console'));
195
+ break;
196
+ case 'show-build':
197
+ window.dispatchEvent(new CustomEvent('editor:show-build-tab'));
198
+ break;
199
+ case 'capture-viewport': {
200
+ // Fresh, unthrottled on-demand capture (see EditorStore.captureViewportImage's
201
+ // doc comment) — distinct from the periodic autosave thumbnail the
202
+ // project-thumbnail snapshot path serves. `size` is optional; defaults
203
+ // to the store's standard thumbnail size.
204
+ const size = cmd['size'] as number | undefined;
205
+ const dataUrl = store.captureViewportImage(size);
206
+ if (!dataUrl) {
207
+ return {
208
+ ok: false,
209
+ error: 'Viewport is not bound yet (no renderer/scene/camera) — cannot capture.',
210
+ };
211
+ }
212
+ const comma = dataUrl.indexOf(',');
213
+ const base64 = comma >= 0 ? dataUrl.slice(comma + 1) : dataUrl;
214
+ return { ok: true, data: { base64, mimeType: 'image/png' } };
215
+ }
216
+
217
+ // Scene loading
218
+ case 'open-scene': {
219
+ const scenePath = cmd['path'] as string;
220
+ const url = scenePath.startsWith('/') ? scenePath : `/${scenePath}`;
221
+ const res = await fetch(url);
222
+ if (!res.ok) {
223
+ return { ok: false, error: `Failed to fetch scene: ${res.status} ${res.statusText}` };
224
+ }
225
+ try {
226
+ const doc = parseSceneFile(await res.json());
227
+ const savePath = scenePath.replace(/^\//, '');
228
+ store.loadDocument(doc, savePath);
229
+ } catch (err) {
230
+ const msg = err instanceof Error ? err.message : String(err);
231
+ editorConsole.error(`Failed to open scene: ${msg}`, 'scene');
232
+ return { ok: false, error: msg };
233
+ }
234
+ break;
235
+ }
236
+
237
+ // Display — set semantics (only toggle when value differs)
238
+ case 'set-grid':
239
+ if (store.showGrid !== (cmd['enabled'] as boolean)) store.toggleGrid();
240
+ break;
241
+ case 'set-helpers':
242
+ if (store.showHelpers !== (cmd['enabled'] as boolean)) store.toggleHelpers();
243
+ break;
244
+ case 'set-stats':
245
+ if (store.showStats !== (cmd['enabled'] as boolean)) store.toggleStats();
246
+ break;
247
+ case 'set-shading-mode':
248
+ store.setShadingMode(cmd['mode'] as 'solid' | 'wireframe' | 'unlit');
249
+ break;
250
+ case 'set-helper-type': {
251
+ const helperType = cmd['helperType'] as keyof HelperVisibility;
252
+ if (store.helperVisibility[helperType] !== (cmd['enabled'] as boolean)) {
253
+ store.toggleHelperType(helperType);
254
+ }
255
+ break;
256
+ }
257
+
258
+ // Transform tools — set semantics
259
+ case 'set-transform-mode':
260
+ store.setTransformMode(cmd['mode'] as 'translate' | 'rotate' | 'scale');
261
+ break;
262
+ case 'set-transform-space':
263
+ store.setTransformSpace(cmd['space'] as 'world' | 'local');
264
+ break;
265
+ case 'set-snap':
266
+ if (store.snapEnabled !== (cmd['enabled'] as boolean)) store.toggleSnap();
267
+ break;
268
+
269
+ default:
270
+ // Every unrecognized command type used to fall through to `return { ok:
271
+ // true }` below — a silent, false ack: the browser did NOTHING, but the
272
+ // SDK/CLI caller was told it succeeded. Every "honest gap" op in
273
+ // packages/vgai-sdk/src/editor/*-operations.ts (camera.get,
274
+ // hierarchy.inspect pre-wiring, story.open) exists specifically to avoid
275
+ // ever sending a command that would land here. Reporting a structured
276
+ // failure — never a fabricated success — is the fix, and it must hold for
277
+ // ANY unrecognized `type`, not just the ones known about today.
278
+ return { ok: false, error: `Unknown editor command: ${String(cmd['type'])}` };
279
+ }
280
+
281
+ return { ok: true };
282
+ }
283
+
284
+ /**
285
+ * Connect the command listener to the editor server's SSE stream.
286
+ * Dispatches incoming commands to the store and play-mode functions.
287
+ * Reports state after each command and on initial connect.
288
+ */
289
+ export function connectCommandListener(store: EditorStore): () => void {
290
+ // Browser mode (Phase A2): the `vgai` CLI control channel is server-only (it
291
+ // is an SSE command stream + state POSTs to `/__editor/*`, which do not exist
292
+ // without a Node server). Skip it — otherwise the EventSource retry-loops
293
+ // against a 404 and every `reportEditorState` POSTs into the void.
294
+ if (getEditorMode() === 'browser') {
295
+ void store;
296
+ return () => {};
297
+ }
298
+ const source = connectEvents();
299
+
300
+ // Report initial state
301
+ reportEditorState(collectState(store));
302
+
303
+ source.addEventListener('editor-command', (e: MessageEvent) => {
304
+ try {
305
+ const cmd = JSON.parse(e.data as string) as EditorCommand;
306
+ const requestId = cmd._requestId;
307
+ handleCommand(store, cmd).then((result) => {
308
+ // Report command result back to server (so SDK/CLI gets the response)
309
+ if (requestId) {
310
+ reportCommandResult(requestId, result.ok, result.error, result.data);
311
+ }
312
+ // Report updated state after command execution
313
+ reportEditorState(collectState(store));
314
+ });
315
+ } catch {
316
+ /* ignore malformed events */
317
+ }
318
+ });
319
+
320
+ // When the server switches projects, reload to pick up the new project's files
321
+ source.addEventListener('project-changed', () => {
322
+ window.location.reload();
323
+ });
324
+
325
+ return () => source.close();
326
+ }