@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,206 @@
1
+ /**
2
+ * vgai Code Playground — a stranger can WRITE + RUN + SAVE a game entirely in
3
+ * the browser, with no backend.
4
+ *
5
+ * CodeMirror (TS source) → src/browser-transpile.ts (esbuild-wasm) → live
6
+ * createGameRuntime (the REAL engine) → render. Source autosaves to
7
+ * localStorage. Run rebuilds the runtime from the current source.
8
+ *
9
+ * This is the hands-on surface behind the tutorials' "Open the editor" links —
10
+ * what RunHuman testers use to actually build a game while following a lesson.
11
+ */
12
+
13
+ import { javascript } from '@codemirror/lang-javascript';
14
+ import { EditorState } from '@codemirror/state';
15
+ import { oneDark } from '@codemirror/theme-one-dark';
16
+ import { keymap } from '@codemirror/view';
17
+ import type { VgaiMountedGame } from '@engine/adapter/vgai-scene-game-adapter';
18
+ import * as gameComponentModule from '@engine/ecs/game-component';
19
+ import { createGameRuntime, type GameSession } from '@engine/runtime/create-runtime';
20
+ import { basicSetup, EditorView } from 'codemirror';
21
+ import * as THREE from 'three';
22
+
23
+ // Disable auto-close-brackets: beginner testers typing a full component (incl.
24
+ // their own closing braces) would otherwise get DOUBLED braces → a syntax error.
25
+ // What-you-type-is-what-you-get is the right model for a learn-by-coding surface.
26
+ const noAutoCloseBrackets = EditorState.languageData.of(() => [
27
+ { closeBrackets: { brackets: [] } },
28
+ ]);
29
+
30
+ import { importFromSource, registerRuntimeModules } from '../browser-transpile';
31
+
32
+ // Let in-browser-transpiled user code share the running engine's singletons
33
+ // (so `extends GameComponent` and `instanceof THREE.*` work across the boundary).
34
+ registerRuntimeModules({
35
+ three: THREE as unknown as Record<string, unknown>,
36
+ '@engine/ecs/game-component': gameComponentModule as unknown as Record<string, unknown>,
37
+ });
38
+
39
+ const STORAGE_KEY = 'vgai-playground-source-v1';
40
+
41
+ const STARTER = `import { GameComponent } from '@engine/ecs/game-component';
42
+ import * as THREE from 'three';
43
+
44
+ // A GameComponent is gameplay attached to a Three.js object.
45
+ // init() runs once; update(dt) runs every frame. Edit and press Run!
46
+ export class Spinner extends GameComponent {
47
+ t = 0;
48
+
49
+ update(dt) {
50
+ this.t += dt;
51
+ // spin
52
+ this.object3D.rotation.y += dt * 1.5;
53
+ this.object3D.rotation.x += dt * 0.6;
54
+ // bob up and down
55
+ this.object3D.position.y = Math.sin(this.t * 2) * 0.4;
56
+ // shift color over time
57
+ const hue = (this.t * 0.1) % 1;
58
+ this.object3D.material.color.setHSL(hue, 0.6, 0.55);
59
+ }
60
+ }
61
+ `;
62
+
63
+ const codeEl = document.getElementById('code') as HTMLElement;
64
+ const statusEl = document.getElementById('status') as HTMLElement;
65
+ const runBtn = document.getElementById('run') as HTMLButtonElement;
66
+ const resetBtn = document.getElementById('reset') as HTMLButtonElement;
67
+ const canvas = document.getElementById('c') as HTMLCanvasElement;
68
+
69
+ function setStatus(msg: string, kind: 'ok' | 'err' | '' = ''): void {
70
+ statusEl.textContent = msg;
71
+ statusEl.className = kind;
72
+ }
73
+
74
+ // --- CodeMirror editor ---------------------------------------------------
75
+ const saved = localStorage.getItem(STORAGE_KEY);
76
+ const runKeymap = keymap.of([
77
+ {
78
+ key: 'Mod-s',
79
+ run: () => {
80
+ void run();
81
+ return true;
82
+ },
83
+ preventDefault: true,
84
+ },
85
+ {
86
+ key: 'Mod-Enter',
87
+ run: () => {
88
+ void run();
89
+ return true;
90
+ },
91
+ preventDefault: true,
92
+ },
93
+ ]);
94
+ const view = new EditorView({
95
+ doc: saved ?? STARTER,
96
+ parent: codeEl,
97
+ extensions: [
98
+ basicSetup,
99
+ noAutoCloseBrackets,
100
+ javascript({ typescript: true }),
101
+ oneDark,
102
+ runKeymap,
103
+ EditorView.updateListener.of((u) => {
104
+ if (u.docChanged) localStorage.setItem(STORAGE_KEY, view.state.doc.toString());
105
+ }),
106
+ ],
107
+ });
108
+
109
+ // --- Runtime -------------------------------------------------------------
110
+ let session: GameSession | null = null;
111
+ let cube: THREE.Mesh | null = null;
112
+
113
+ function findComponentClass(mod: Record<string, unknown>): (new () => object) | null {
114
+ const Base = gameComponentModule.GameComponent;
115
+ // Prefer an exported class whose prototype extends GameComponent.
116
+ for (const v of Object.values(mod)) {
117
+ if (typeof v === 'function' && v.prototype instanceof Base) return v as new () => object;
118
+ }
119
+ // Fall back to the first exported function (class).
120
+ for (const v of Object.values(mod)) {
121
+ if (typeof v === 'function') return v as new () => object;
122
+ }
123
+ return null;
124
+ }
125
+
126
+ async function run(): Promise<void> {
127
+ const source = view.state.doc.toString();
128
+ localStorage.setItem(STORAGE_KEY, source);
129
+ setStatus('transpiling + running…');
130
+ let mod: Record<string, unknown>;
131
+ try {
132
+ mod = await importFromSource(source, 'playground.tsx');
133
+ } catch (err) {
134
+ setStatus(`Transpile error:\n${err}`, 'err');
135
+ return;
136
+ }
137
+ const Cls = findComponentClass(mod) as
138
+ | (new () => InstanceType<typeof gameComponentModule.GameComponent>)
139
+ | null;
140
+ if (!Cls) {
141
+ setStatus(
142
+ 'No exported GameComponent class found. Export a class that `extends GameComponent`.',
143
+ 'err',
144
+ );
145
+ return;
146
+ }
147
+ const OK = '✓ running — edit the code and press Run (or ⌘/Ctrl+S) to update.';
148
+ try {
149
+ if (session && cube) {
150
+ // Re-run: swap the component on the LIVE runtime — never re-create the
151
+ // renderer (a 2nd createGameRuntime on the same canvas loses the WebGL
152
+ // context). detach+attach also handles a renamed class cleanly.
153
+ const ctx = (session.mounted as VgaiMountedGame).ctx;
154
+ ctx.components.detach(cube);
155
+ // No manual initAll() here (T1.11): the ComponentManager now flushes
156
+ // pending init() automatically at the frame boundary, before the new
157
+ // instance's first update() — nothing after this depends on init()
158
+ // having completed synchronously.
159
+ ctx.components.attach(cube, new Cls());
160
+ setStatus(OK, 'ok');
161
+ return;
162
+ }
163
+ // First run: build the runtime once.
164
+ session = await createGameRuntime({
165
+ canvas,
166
+ width: canvas.clientWidth,
167
+ height: canvas.clientHeight,
168
+ setup: async (ctx) => {
169
+ ctx.camera.position.set(3, 2.5, 3);
170
+ ctx.camera.lookAt(0, 0, 0);
171
+ ctx.scene.background = new THREE.Color('#14161a');
172
+ ctx.scene.add(new THREE.AmbientLight(0xffffff, 0.75));
173
+ const dir = new THREE.DirectionalLight(0xffffff, 1.2);
174
+ dir.position.set(5, 8, 4);
175
+ ctx.scene.add(dir);
176
+ const grid = new THREE.GridHelper(10, 10, 0x444444, 0x222222);
177
+ grid.position.y = -1;
178
+ ctx.scene.add(grid);
179
+
180
+ cube = new THREE.Mesh(
181
+ new THREE.BoxGeometry(1.4, 1.4, 1.4),
182
+ new THREE.MeshStandardMaterial({ color: '#888', roughness: 0.4, metalness: 0.1 }),
183
+ );
184
+ ctx.scene.add(cube);
185
+ // No manual initAll() (T1.11) — see the comment in the re-run branch
186
+ // above; the automatic frame-boundary flush covers this.
187
+ ctx.components.attach(cube, new Cls());
188
+ return { dispose() {} };
189
+ },
190
+ });
191
+ setStatus(OK, 'ok');
192
+ } catch (err) {
193
+ setStatus(`Runtime error:\n${err}`, 'err');
194
+ }
195
+ }
196
+
197
+ runBtn.addEventListener('click', () => void run());
198
+ resetBtn.addEventListener('click', () => {
199
+ view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: STARTER } });
200
+ localStorage.setItem(STORAGE_KEY, STARTER);
201
+ void run();
202
+ });
203
+ window.addEventListener('resize', () => session?.resize(canvas.clientWidth, canvas.clientHeight));
204
+
205
+ // Boot: run whatever's in the editor (saved or starter).
206
+ void run();
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Project manager — manages the active game project.
3
+ *
4
+ * The editor receives the project path via a `?project=` URL parameter
5
+ * or the `__VGAI_PROJECT_PATH__` define injected by the dev server.
6
+ * Projects can also be switched at runtime via `switchProject()`.
7
+ */
8
+
9
+ import { getCurrentServerProject, openProjectOnServer } from './editor-api';
10
+ import { getEditorMode } from './editor-mode';
11
+ import { getStorageBackend, restorePersistedProjectRoot } from './storage';
12
+ import { readProjectManifest, seedIfEmpty } from './storage/seed';
13
+
14
+ export interface ProjectConfig {
15
+ name: string;
16
+ resolution?: { width: number; height: number } | undefined;
17
+ appId?: string | undefined;
18
+ /**
19
+ * The project's default scene path, or `null` when the manifest's first
20
+ * world declares no scene (e.g. an `entry`-only world — §7.1-12). Always
21
+ * present when synthesized from a manifest (`readProjectView`, server-side);
22
+ * a hand-authored legacy `project.json` may still omit the key entirely
23
+ * (verbatim pass-through — §1C), which `loadInitialScene` treats the same
24
+ * as `null`. Consumers must NOT default a missing/null value to a guessed
25
+ * path — that was the bug (initial-scene.ts guessing
26
+ * 'scenes/default.vscn.json' and silently writing a placeholder scene).
27
+ */
28
+ defaultScene?: string | null | undefined;
29
+ }
30
+
31
+ export interface ActiveProject {
32
+ /** Absolute path to the project root (as passed via URL param / env). */
33
+ rootPath: string;
34
+ /** Parsed project.json contents. */
35
+ config: ProjectConfig;
36
+ /**
37
+ * Set when this project is a BUNDLED EXAMPLE opened in the hosted (browser)
38
+ * build via `?project=<id>` — the `examples/<id>/` folder name, which is also
39
+ * the `SETUP_BY_EXAMPLE_ID` dispatch key and the `/examples/<id>/` staging
40
+ * prefix (docs/EXAMPLES-AS-PROJECTS-DESIGN.md §H). Example projects are
41
+ * read-only: their scenes load with no save path, so autosave is inert.
42
+ */
43
+ exampleId?: string;
44
+ }
45
+
46
+ let _activeProject: ActiveProject | null = null;
47
+ const _listeners = new Set<() => void>();
48
+
49
+ function _notify(): void {
50
+ for (const fn of _listeners) fn();
51
+ }
52
+
53
+ /** Open a project by its root path. Fetches project.json from the server. */
54
+ export async function openProject(rootPath: string): Promise<ActiveProject> {
55
+ const res = await fetch('/project.json');
56
+ if (!res.ok) {
57
+ throw new Error(`Failed to load project.json: ${res.status} ${res.statusText}`);
58
+ }
59
+ const config = (await res.json()) as ProjectConfig;
60
+ _activeProject = { rootPath, config };
61
+ _notify();
62
+ return _activeProject;
63
+ }
64
+
65
+ /**
66
+ * Switch to a different project at runtime.
67
+ * Tells the server to update its project root, then refreshes local state.
68
+ */
69
+ export async function switchProject(projectPath: string): Promise<ActiveProject> {
70
+ const result = await openProjectOnServer(projectPath);
71
+ if (!result.ok || !result.config) {
72
+ throw new Error(`Failed to open project at ${projectPath}`);
73
+ }
74
+ _activeProject = { rootPath: projectPath, config: result.config as ProjectConfig };
75
+ _notify();
76
+ return _activeProject;
77
+ }
78
+
79
+ /** Set the active project directly (e.g. from the project screen). */
80
+ export function setActiveProject(project: ActiveProject): void {
81
+ _activeProject = project;
82
+ _notify();
83
+ }
84
+
85
+ /** Get the currently open project, or null if none. */
86
+ export function getCurrentProject(): ActiveProject | null {
87
+ return _activeProject;
88
+ }
89
+
90
+ /** Subscribe to project changes. Returns unsubscribe function. */
91
+ export function onProjectChange(fn: () => void): () => void {
92
+ _listeners.add(fn);
93
+ return () => _listeners.delete(fn);
94
+ }
95
+
96
+ declare const __VGAI_PROJECT_PATH__: string;
97
+
98
+ /** Read the project path from URL param or Vite define. */
99
+ export function getProjectPathFromURL(): string | null {
100
+ const params = new URLSearchParams(window.location.search);
101
+ const fromURL = params.get('project');
102
+ if (fromURL) return fromURL;
103
+
104
+ // Fallback: injected by dev server via define
105
+ if (typeof __VGAI_PROJECT_PATH__ === 'string' && __VGAI_PROJECT_PATH__) {
106
+ return __VGAI_PROJECT_PATH__;
107
+ }
108
+
109
+ return null;
110
+ }
111
+
112
+ /**
113
+ * Detect and open whatever project the server currently has active.
114
+ * Used in production where __VGAI_PROJECT_PATH__ isn't available at build time.
115
+ */
116
+ export async function detectServerProject(): Promise<ActiveProject | null> {
117
+ const info = await getCurrentServerProject();
118
+ if (!info) return null;
119
+ _activeProject = { rootPath: info.path, config: info.config as ProjectConfig };
120
+ _notify();
121
+ return _activeProject;
122
+ }
123
+
124
+ /**
125
+ * Detect if a project is already specified (via URL param, Vite define, or server).
126
+ * Returns the project if found, null if the project screen should be shown.
127
+ */
128
+ export async function detectProject(): Promise<ActiveProject | null> {
129
+ // Browser-first build: no server. `?project=<exampleId>` opens a bundled
130
+ // example staged at `/examples/<id>/` (Track H — legacy flat-pool `?scene=`
131
+ // links are rewritten onto that scheme first); otherwise seed a Starter into
132
+ // the in-browser VFS and open it directly (skips the server-oriented
133
+ // project screen).
134
+ if (getEditorMode() === 'browser') {
135
+ const { applyLegacyPoolRedirect, openExampleProject } = await import('./hosted-example');
136
+ applyLegacyPoolRedirect();
137
+ const exampleId = new URLSearchParams(window.location.search).get('project');
138
+ if (exampleId) {
139
+ try {
140
+ _activeProject = await openExampleProject(exampleId);
141
+ _notify();
142
+ return _activeProject;
143
+ } catch (err) {
144
+ // Unknown/broken example link — fall through to the seeded Starter so
145
+ // the editor still opens, but say why loudly.
146
+ console.error(`[vgai] Failed to open example project "${exampleId}":`, err);
147
+ }
148
+ }
149
+ // Reopen the last-picked on-disk folder (File System Access) when its
150
+ // permission survived the reload — so disk authoring persists across
151
+ // reloads instead of dropping back into the OPFS sandbox. A folder that
152
+ // needs a fresh permission gesture can't be reopened without a user click,
153
+ // so we bail to the project screen (returns null) where a one-click reopen
154
+ // is offered; nothing remembered → seed/open the OPFS Starter as before.
155
+ const restored = await restorePersistedProjectRoot();
156
+ if (restored?.status === 'prompt') return null;
157
+ const backend = getStorageBackend();
158
+ await seedIfEmpty(backend);
159
+ const config = await readProjectManifest(backend);
160
+ _activeProject = { rootPath: restored?.status === 'granted' ? 'fsa' : 'browser', config };
161
+ _notify();
162
+ return _activeProject;
163
+ }
164
+
165
+ // 1. Check URL param or Vite define
166
+ const projectPath = getProjectPathFromURL();
167
+ if (projectPath) {
168
+ return openProject(projectPath);
169
+ }
170
+
171
+ // 2. Check if the server already has a project active
172
+ return detectServerProject();
173
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Scene diff — computes minimal operations to transform one SceneFile into another.
3
+ *
4
+ * Compares entities by ID. Produces add/remove/update/move operations that can be
5
+ * applied incrementally to the editor store without a full scene rebuild.
6
+ *
7
+ * The inverse of this — `applyDiff` — lives in the engine
8
+ * (`@engine/scene/scene-apply`), not here, so that `packages/vgai-cli`
9
+ * (`vgai apply-diff`, T4.5) can reach it without a new cli→editor dependency
10
+ * edge; see that module's jsdoc for the full rationale. `DiffOp`/`SceneDiff`
11
+ * are accordingly DEFINED in the engine (`@engine/scene/scene-diff-types`)
12
+ * and just re-exported here so this stays the only file that COMPUTES a
13
+ * diff, and no existing import of these two type names (elsewhere in the
14
+ * editor) needs to change.
15
+ */
16
+
17
+ import type { DiffOp, SceneDiff } from '@engine/scene/scene-diff-types';
18
+ import type { SceneEntity, SceneFile } from '@engine/scene/scene-types';
19
+
20
+ export type { DiffOp, SceneDiff };
21
+
22
+ interface FlatEntry {
23
+ entity: SceneEntity;
24
+ parentId: string | null;
25
+ index: number;
26
+ }
27
+
28
+ /** Flatten an entity tree into a map of id → { entity (without children), parentId, index }. */
29
+ function flattenEntities(entities: SceneEntity[], parentId: string | null): Map<string, FlatEntry> {
30
+ const map = new Map<string, FlatEntry>();
31
+ for (let i = 0; i < entities.length; i++) {
32
+ const e = entities[i]!;
33
+ if (!e.id) continue;
34
+ // Store entity without children for comparison
35
+ const { children: _, ...rest } = e;
36
+ map.set(e.id, { entity: rest as SceneEntity, parentId, index: i });
37
+ if (e.children) {
38
+ for (const [k, v] of flattenEntities(e.children, e.id)) {
39
+ map.set(k, v);
40
+ }
41
+ }
42
+ }
43
+ return map;
44
+ }
45
+
46
+ /** Deep equality check via JSON serialization (sufficient for scene entity data). */
47
+ function deepEqual(a: unknown, b: unknown): boolean {
48
+ return JSON.stringify(a) === JSON.stringify(b);
49
+ }
50
+
51
+ /** Compare entity fields (excluding children and id) to detect property changes. */
52
+ function entityFieldsChanged(a: SceneEntity, b: SceneEntity): boolean {
53
+ // Compare all fields except id and children
54
+ const { id: _aId, children: _aC, ...aRest } = a;
55
+ const { id: _bId, children: _bC, ...bRest } = b;
56
+ return !deepEqual(aRest, bRest);
57
+ }
58
+
59
+ export function diffScene(oldDoc: SceneFile, newDoc: SceneFile): SceneDiff {
60
+ const ops: DiffOp[] = [];
61
+
62
+ const oldMap = flattenEntities(oldDoc.entities, null);
63
+ const newMap = flattenEntities(newDoc.entities, null);
64
+
65
+ // Removed entities (in old but not in new) — process deepest first to avoid parent issues
66
+ const removedIds: string[] = [];
67
+ for (const id of oldMap.keys()) {
68
+ if (!newMap.has(id)) removedIds.push(id);
69
+ }
70
+ // Reverse so children are removed before parents
71
+ removedIds.reverse();
72
+ for (const id of removedIds) {
73
+ ops.push({ type: 'remove', id });
74
+ }
75
+
76
+ // Added entities (in new but not in old) — process shallowest first
77
+ for (const [id, entry] of newMap) {
78
+ if (!oldMap.has(id)) {
79
+ ops.push({ type: 'add', entity: entry.entity, parentId: entry.parentId, index: entry.index });
80
+ }
81
+ }
82
+
83
+ // Updated and moved entities (in both)
84
+ for (const [id, newEntry] of newMap) {
85
+ const oldEntry = oldMap.get(id);
86
+ if (!oldEntry) continue;
87
+
88
+ // Check for property changes
89
+ if (entityFieldsChanged(oldEntry.entity, newEntry.entity)) {
90
+ ops.push({ type: 'update', id, entity: newEntry.entity });
91
+ }
92
+
93
+ // Check for reparent/reorder
94
+ if (oldEntry.parentId !== newEntry.parentId || oldEntry.index !== newEntry.index) {
95
+ ops.push({ type: 'move', id, parentId: newEntry.parentId, index: newEntry.index });
96
+ }
97
+ }
98
+
99
+ // Environment
100
+ const environmentChanged = !deepEqual(oldDoc.environment, newDoc.environment);
101
+
102
+ // Metadata
103
+ const metaChanged = oldDoc.name !== newDoc.name;
104
+
105
+ return {
106
+ ops,
107
+ environmentChanged,
108
+ newEnvironment: environmentChanged ? newDoc.environment : undefined,
109
+ metaChanged,
110
+ newName: metaChanged ? newDoc.name : undefined,
111
+ };
112
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Scene serializer — extracts a SceneFile from the live Three.js scene graph.
3
+ *
4
+ * Each entity object in the scene carries its SceneEntity descriptor in
5
+ * `userData['entity']`. The serializer walks the graph, reads those descriptors,
6
+ * overwrites transforms with the live object transforms, and reconstructs
7
+ * the tree from parent-child relationships.
8
+ *
9
+ * Used by: save, undo push, play-mode snapshot.
10
+ */
11
+
12
+ import type { SceneEntity, SceneEnvironment, SceneFile } from '@engine/scene/scene-types';
13
+ import type { UIRoot } from '@engine/scene/schema';
14
+ import { getUserData } from '@engine/scene/user-data';
15
+ import type * as THREE from 'three';
16
+
17
+ export interface SceneMetadata {
18
+ version: number;
19
+ name: string;
20
+ environment?: SceneEnvironment;
21
+ /** Scene-graph UI (the `ui` field) — preserved through load/serialize (A1/C9). */
22
+ ui?: UIRoot[];
23
+ }
24
+
25
+ type Transform = NonNullable<SceneEntity['transform']>;
26
+
27
+ const EPS = 1e-6;
28
+
29
+ /**
30
+ * True if a transform is the identity (position 0, rotation identity quat,
31
+ * scale 1) — i.e. the default applied by the entity factory. Such transforms
32
+ * are omitted from serialized output to avoid spurious diff churn (ED9).
33
+ */
34
+ export function isDefaultTransform(t: Transform | undefined): boolean {
35
+ if (!t) return true;
36
+ const p = t.position ?? [0, 0, 0];
37
+ const r = t.rotation ?? [0, 0, 0, 1];
38
+ const s = t.scale ?? [1, 1, 1];
39
+ return (
40
+ Math.abs(p[0]) < EPS &&
41
+ Math.abs(p[1]) < EPS &&
42
+ Math.abs(p[2]) < EPS &&
43
+ Math.abs(r[0]) < EPS &&
44
+ Math.abs(r[1]) < EPS &&
45
+ Math.abs(r[2]) < EPS &&
46
+ Math.abs(r[3] - 1) < EPS &&
47
+ Math.abs(s[0] - 1) < EPS &&
48
+ Math.abs(s[1] - 1) < EPS &&
49
+ Math.abs(s[2] - 1) < EPS
50
+ );
51
+ }
52
+
53
+ /**
54
+ * Serialize the live Three.js scene into a SceneFile.
55
+ *
56
+ * Walks `scene.children` recursively, collecting `userData['entity']` from
57
+ * each entity object and overwriting its transform with the live values.
58
+ */
59
+ export function serializeScene(scene: THREE.Scene, meta: SceneMetadata): SceneFile {
60
+ const entities = serializeChildren(scene);
61
+
62
+ const doc: SceneFile = {
63
+ version: meta.version,
64
+ name: meta.name,
65
+ entities,
66
+ };
67
+
68
+ if (meta.environment) {
69
+ doc.environment = meta.environment;
70
+ }
71
+ if (meta.ui && meta.ui.length > 0) {
72
+ doc.ui = meta.ui;
73
+ }
74
+
75
+ return doc;
76
+ }
77
+
78
+ /** Recursively collect entity descriptors from an object's children. */
79
+ function serializeChildren(parent: THREE.Object3D): SceneEntity[] {
80
+ const result: SceneEntity[] = [];
81
+
82
+ for (const child of parent.children) {
83
+ // Only process objects that are entities (have entityId), not helpers
84
+ if (getUserData(child, 'entityId') === undefined) continue;
85
+ if (getUserData(child, 'editorHelper')) continue;
86
+
87
+ const stored = getUserData(child, 'entity') as SceneEntity | undefined;
88
+ if (!stored) continue;
89
+
90
+ // Deep clone the stored descriptor so we don't mutate it
91
+ const entity: SceneEntity = JSON.parse(JSON.stringify(stored)) as SceneEntity;
92
+
93
+ // Overwrite transform from live Three.js values. Omit a default/identity
94
+ // transform to avoid spurious diff churn (ED9).
95
+ if (!entity.boneAttachment) {
96
+ const transform: Transform = {
97
+ position: [child.position.x, child.position.y, child.position.z],
98
+ rotation: [child.quaternion.x, child.quaternion.y, child.quaternion.z, child.quaternion.w],
99
+ scale: [child.scale.x, child.scale.y, child.scale.z],
100
+ };
101
+ if (isDefaultTransform(transform)) {
102
+ delete entity.transform;
103
+ } else {
104
+ entity.transform = transform;
105
+ }
106
+ }
107
+
108
+ // Recurse into children (filter out helpers, only entity children)
109
+ const childEntities = serializeChildren(child);
110
+ if (childEntities.length > 0) {
111
+ entity.children = childEntities;
112
+ } else {
113
+ delete entity.children;
114
+ }
115
+
116
+ result.push(entity);
117
+ }
118
+
119
+ return result;
120
+ }
121
+
122
+ /**
123
+ * Strip empty/default values from a serialized SceneFile for clean output.
124
+ * Matches the cleanup logic in the old `EditorStore.getDocument()`.
125
+ */
126
+ export function cleanDocument(doc: SceneFile): SceneFile {
127
+ const cleaned = JSON.parse(JSON.stringify(doc)) as SceneFile;
128
+ walkEntities(cleaned.entities, (e) => {
129
+ // Strip empty children arrays
130
+ if (e.children && e.children.length === 0) delete e.children;
131
+ // Strip zero pivots
132
+ if (e.pivot && e.pivot[0] === 0 && e.pivot[1] === 0 && e.pivot[2] === 0) delete e.pivot;
133
+ // Strip default/identity transforms (ED9)
134
+ if (e.transform && isDefaultTransform(e.transform)) delete e.transform;
135
+ });
136
+ return cleaned;
137
+ }
138
+
139
+ function walkEntities(entities: SceneEntity[], fn: (e: SceneEntity) => void): void {
140
+ for (const e of entities) {
141
+ fn(e);
142
+ if (e.children) walkEntities(e.children, fn);
143
+ }
144
+ }