@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,50 @@
1
+ /**
2
+ * Canonicalize a project root path (T6.2 slice 3 finding — see
3
+ * docs/REACT-WORLD-DESIGN.md; reported in the slice-3 "Report back").
4
+ *
5
+ * Vite's own module resolver calls `fs.realpathSync` when resolving a
6
+ * STATIC relative import — e.g. a react world's entry module's own
7
+ * `import { useGameState } from './src/ui/game-state'` — which
8
+ * collapses a symlinked path segment (macOS's `/var` -> `/private/var`,
9
+ * hit by every project scaffolded under `os.tmpdir()`, as every e2e spec
10
+ * that calls `scaffoldProject()` does) to its real form.
11
+ * `adapter-resolver.ts`'s hand-built `/@fs/${projectRoot}/...` dynamic-import
12
+ * strings (which must bypass Vite's static analysis via `@vite-ignore` — the
13
+ * path is only known at runtime) do NOT go through that same resolution. So
14
+ * if `projectRoot` itself isn't already canonical, two DIFFERENT `/@fs/`
15
+ * URLs get produced for the exact same file: Vite's realpath'd rewrite of
16
+ * the entry module's own relative import vs. our raw, non-realpath'd string
17
+ * used to import `GameProvider` for the `<GameProvider>` wrap. Browser ES
18
+ * module identity is per-URL, so the SAME `game-state.tsx` loads as TWO
19
+ * separate module instances with TWO separate top-level `createContext()`
20
+ * calls — `useGameState`'s `useGame()` then throws "no Game in context"
21
+ * even though `<GameProvider>` genuinely wraps the entry (this is exactly
22
+ * what the T6.2 slice-3 AC e2e's first real-browser run of a react world
23
+ * caught).
24
+ *
25
+ * The fix is upstream of any one `/@fs/` string: canonicalize the project
26
+ * root ONCE, at the points a project path enters server state (dev/prod
27
+ * boot from `VGAI_PROJECT`, and the `/__editor/open-project` route) — every
28
+ * `/@fs/${projectRoot}` string built anywhere afterwards (browser-side,
29
+ * repo-wide, not just the react-world resolver) then already matches
30
+ * whatever Vite's own resolver would independently arrive at for that same
31
+ * project.
32
+ */
33
+ import { realpathSync } from 'node:fs';
34
+ import { resolve } from 'node:path';
35
+
36
+ /**
37
+ * Resolve `rawPath` to an absolute, symlink-canonicalized path. Falls back to
38
+ * a plain `path.resolve` if the path doesn't exist yet or `realpathSync`
39
+ * otherwise fails (e.g. a permissions error) — never throws, so a bad/odd
40
+ * path degrades to today's pre-canonicalization behavior rather than
41
+ * blocking project open.
42
+ */
43
+ export function canonicalProjectRoot(rawPath: string): string {
44
+ const absolute = resolve(rawPath);
45
+ try {
46
+ return realpathSync(absolute);
47
+ } catch {
48
+ return absolute;
49
+ }
50
+ }
@@ -0,0 +1,101 @@
1
+ export const DEFAULT_CLOUD_ASSET_BASE_URL = 'https://vgai-asset-library.aaron-0ed.workers.dev';
2
+
3
+ export interface CloudAssetObject {
4
+ key: string;
5
+ sha256: string;
6
+ sizeBytes: number;
7
+ contentType: string;
8
+ relativePath: string;
9
+ }
10
+
11
+ export interface CloudAssetFile {
12
+ label: string;
13
+ format: string;
14
+ main: CloudAssetObject;
15
+ dependencies: CloudAssetObject[];
16
+ }
17
+
18
+ export interface CloudAssetRecord {
19
+ id: string;
20
+ status: 'validated';
21
+ source: string;
22
+ name: string;
23
+ type: string;
24
+ license: string;
25
+ author: string;
26
+ sourceUrl: string;
27
+ attribution: string;
28
+ files: CloudAssetFile[];
29
+ }
30
+
31
+ interface CloudAssetManifest {
32
+ version: 1;
33
+ generatedAt: string;
34
+ assets: CloudAssetRecord[];
35
+ }
36
+
37
+ let cachedManifest: { value: CloudAssetManifest; url: string; loadedAt: number } | undefined;
38
+ const CACHE_MS = 5 * 60 * 1000;
39
+
40
+ export function getCloudAssetBaseUrl(): string {
41
+ return (process.env['VGAI_CLOUD_ASSET_URL'] ?? DEFAULT_CLOUD_ASSET_BASE_URL).replace(/\/+$/, '');
42
+ }
43
+
44
+ export async function loadCloudAssetManifest(): Promise<CloudAssetManifest | null> {
45
+ const baseUrl = getCloudAssetBaseUrl();
46
+ if (
47
+ cachedManifest &&
48
+ cachedManifest.url === baseUrl &&
49
+ Date.now() - cachedManifest.loadedAt < CACHE_MS
50
+ ) {
51
+ return cachedManifest.value;
52
+ }
53
+ try {
54
+ const response = await fetch(`${baseUrl}/v1/manifest`, { signal: AbortSignal.timeout(5_000) });
55
+ if (!response.ok) return null;
56
+ const value = (await response.json()) as CloudAssetManifest;
57
+ if (value.version !== 1 || !Array.isArray(value.assets)) return null;
58
+ cachedManifest = { value, url: baseUrl, loadedAt: Date.now() };
59
+ return value;
60
+ } catch {
61
+ return null;
62
+ }
63
+ }
64
+
65
+ export async function findCloudAsset(id: string): Promise<CloudAssetRecord | null> {
66
+ const manifest = await loadCloudAssetManifest();
67
+ return manifest?.assets.find((asset) => asset.id === id) ?? null;
68
+ }
69
+
70
+ export function cloudAssetObjectUrl(
71
+ assetId: string,
72
+ fileIndex: number,
73
+ dependencyIndex?: number,
74
+ ): string {
75
+ const base = `${getCloudAssetBaseUrl()}/v1/assets/${encodeURIComponent(assetId)}/files/${fileIndex}`;
76
+ return dependencyIndex === undefined ? base : `${base}/dependencies/${dependencyIndex}`;
77
+ }
78
+
79
+ export function parseCloudAssetObjectUrl(
80
+ value: string,
81
+ expectedAssetId: string,
82
+ ): { fileIndex: number; dependencyIndex?: number } | null {
83
+ let url: URL;
84
+ try {
85
+ url = new URL(value);
86
+ } catch {
87
+ return null;
88
+ }
89
+ const expectedBase = new URL(getCloudAssetBaseUrl());
90
+ if (url.protocol !== 'https:' || url.origin !== expectedBase.origin || url.search || url.hash)
91
+ return null;
92
+ const prefix = expectedBase.pathname.replace(/\/$/, '');
93
+ const relativePath = url.pathname.slice(prefix.length);
94
+ const match = /^\/v1\/assets\/([a-f0-9]{24})\/files\/(\d+)(?:\/dependencies\/(\d+))?$/.exec(
95
+ relativePath,
96
+ );
97
+ if (!match || match[1] !== expectedAssetId) return null;
98
+ const parsed: { fileIndex: number; dependencyIndex?: number } = { fileIndex: Number(match[2]) };
99
+ if (match[3] !== undefined) parsed.dependencyIndex = Number(match[3]);
100
+ return parsed;
101
+ }
package/server/dev.ts ADDED
@@ -0,0 +1,350 @@
1
+ /**
2
+ * Editor dev server — Express + Vite middleware mode on a single port.
3
+ *
4
+ * Run with:
5
+ * npx tsx packages/editor/server/dev.ts
6
+ *
7
+ * Supports VGAI_PROJECT env var for initial project path.
8
+ */
9
+
10
+ import { type Dirent, readdirSync, realpathSync, statSync } from 'node:fs';
11
+ import path from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
13
+ import express from 'express';
14
+ import { createServer as createViteServer } from 'vite';
15
+ import { projectGameStaticPlugin } from '../vite-plugin-project-game-static';
16
+ import { canonicalProjectRoot } from './canonical-path';
17
+ import { createEditorServer } from './editor-server';
18
+ import { gameGlobalsShadowPlugin } from '../vite-plugin-game-globals';
19
+ import { ingestGameShadowRoots } from './game-globals-shadow';
20
+ import { friendlyListenError, resolveBindHost } from './server-utils';
21
+ import { registerSession, unregisterSession } from './session-registry';
22
+
23
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
+ // Port is env-overridable so multiple editor servers (e.g. parallel e2e/capture
25
+ // runs across git worktrees) don't collide on 5173. Defaults preserve behavior.
26
+ const PORT = Number(process.env['VGAI_EDITOR_PORT']) || 5173;
27
+ const HMR_PORT = Number(process.env['VGAI_HMR_PORT']) || (PORT === 5173 ? 24678 : PORT + 100);
28
+ // S3: bind to loopback by default; opt-in to a wider interface via env.
29
+ const HOST = resolveBindHost();
30
+ // Engine root is three levels up from packages/editor/server/
31
+ const engineRoot = path.resolve(__dirname, '../../..');
32
+ // Worktree / hoisted-monorepo support: `node_modules` is very often a SYMLINK
33
+ // (git worktrees share ONE install; npm workspaces hoist to a parent), and its
34
+ // realpath then lands OUTSIDE `engineRoot`. Vite's `server.fs.allow` matches on
35
+ // the symlink-RESOLVED path, so without allowing the resolved node_modules a
36
+ // game's own dependency — e.g. a vendored react game importing a font package —
37
+ // 403s ("outside of Vite serving allow list") in a worktree checkout even
38
+ // though it serves fine in a normal one. Allowing the project's own resolved
39
+ // deps is safe, and this is a no-op when node_modules is a real directory
40
+ // already under engineRoot.
41
+ const nodeModulesRealPath = (() => {
42
+ try {
43
+ const real = realpathSync(path.join(engineRoot, 'node_modules'));
44
+ return real.startsWith(engineRoot) ? null : real;
45
+ } catch {
46
+ return null;
47
+ }
48
+ })();
49
+ // Canonicalized (symlink-resolved), not just `path.resolve` — see
50
+ // canonical-path.ts's doc comment (T6.2 slice 3 finding: a react world's
51
+ // `GameProvider` colocation breaks if this path and Vite's own resolver
52
+ // disagree on a symlinked segment, e.g. macOS's `/var` -> `/private/var`).
53
+ const projectPath = process.env['VGAI_PROJECT']
54
+ ? canonicalProjectRoot(process.env['VGAI_PROJECT'])
55
+ : undefined;
56
+
57
+ // D-X2 (docs/EXTERNAL-FOLDER-INGEST-DESIGN.md): the project-static route
58
+ // (below) must follow the CURRENTLY open project, not a boot-time snapshot —
59
+ // `open-project` can switch it live (`onProjectOpened` below). A mutable box
60
+ // read by the plugin's thunk, updated in the SAME callback that already
61
+ // extends Vite's fs.allow / watcher / game-globals shadow for a newly opened
62
+ // project.
63
+ let currentProjectRoot: string | undefined = projectPath;
64
+
65
+ // Roots whose `/src/` modules are treated as game code and get the gated-globals
66
+ // prelude (see below). Seeded from the env project (extended when the editor
67
+ // opens/switches a project at runtime, onProjectOpened below) PLUS the fixed
68
+ // in-tree ingest-fixture roots (R2c — see game-globals-shadow.ts's doc comment:
69
+ // those same-realm `shared`/`deduped` fixtures are part of THIS package's own
70
+ // `/src/` tree, not a project a user opens, so they need their own entry here).
71
+ const projectRoots = new Set<string>(ingestGameShadowRoots(path.resolve(__dirname, '../src')));
72
+ if (projectPath) projectRoots.add(projectPath);
73
+
74
+ async function main(): Promise<void> {
75
+ const app = express();
76
+
77
+ // 1. Create Vite in middleware mode (before editor routes so we can pass the callback)
78
+ const vite = await createViteServer({
79
+ configFile: path.resolve(engineRoot, 'vite.config.ts'),
80
+ plugins: [
81
+ // Pipe gated `window`/`document` into game code: prepend a lexical shadow
82
+ // to every project `/src/` module so its globals resolve to the editor's
83
+ // gated proxies. Scoped to `projectRoots` — an opened project's root PLUS
84
+ // the fixed in-tree ingest-fixture roots (R2c) — so engine/editor code is
85
+ // untouched (it keeps the real window; its input is gated separately via
86
+ // InputManager.setEnabled). The repo-root config registers this same
87
+ // plugin over the ingest roots for the hosted build; the shared factory
88
+ // no-ops on already-shadowed code, so the overlap in dev is harmless.
89
+ gameGlobalsShadowPlugin(() => projectRoots),
90
+ // Project-rooted verbatim static serving (D-X2) — the external-folder
91
+ // sibling of `gameStaticPlugin`'s vendored-dist route (`vite.config.ts`,
92
+ // repo-root config). Always registered (not gated on `projectPath`
93
+ // being set at boot): a project can open LATER via `onProjectOpened`,
94
+ // and the route must still come alive then — the thunk reads
95
+ // `currentProjectRoot` live on every request.
96
+ projectGameStaticPlugin(() => currentProjectRoot),
97
+ // Intercept project script changes and send a custom HMR event
98
+ // instead of triggering a full page reload.
99
+ ...(projectPath
100
+ ? [
101
+ {
102
+ name: 'vgai-script-hmr',
103
+ configureServer(server: { watcher: { add: (p: string) => void } }) {
104
+ // Watch the project directory so changes outside the Vite root are detected
105
+ server.watcher.add(projectPath);
106
+ },
107
+ handleHotUpdate({
108
+ file,
109
+ server,
110
+ }: {
111
+ file: string;
112
+ server: { ws: { send: (msg: unknown) => void } };
113
+ }) {
114
+ // Vite's `file` hook argument is ALWAYS forward-slash normalized
115
+ // (its cross-platform module-id convention), but `projectPath`
116
+ // comes from `canonicalProjectRoot` -> `realpathSync`, which on
117
+ // Windows returns a native backslash path. A naive
118
+ // `file.startsWith(projectPath)` therefore NEVER matches on
119
+ // Windows ("C:/Users/...".startsWith("C:\\Users\\...") is
120
+ // false) — the custom `vgai:script-update` event silently never
121
+ // fires and every project edit falls through to Vite's default
122
+ // full-module-reload HMR instead. Compare forward-slash forms
123
+ // on both sides so this works identically on every OS.
124
+ const projectPathPosix = projectPath!.split(path.sep).join('/');
125
+ if (file.startsWith(projectPathPosix)) {
126
+ // Data assets (docs/DATA-TOOLS-DESIGN.md §2.3) ride Vite's
127
+ // STOCK HMR: fall through so the imported JSON module
128
+ // updates and the owning handle module's
129
+ // `import.meta.hot.accept('./x.data.json', …)` fires in the
130
+ // running game (live tuning). Safe from the full-page-reload
131
+ // hazard the swallow below exists for: the template's
132
+ // handle module self-accepts the dep, and an unimported
133
+ // .data.json maps to zero modules (no-op). Scene/asset
134
+ // files stay swallowed — the editor owns those via SSE.
135
+ if (/\.data\.json$/.test(file)) return;
136
+ if (/\.(ts|tsx|js|jsx)$/.test(file)) {
137
+ server.ws.send({ type: 'custom', event: 'vgai:script-update', data: { file } });
138
+ }
139
+ // Swallow all project file changes — the editor handles
140
+ // scene/asset updates via its own SSE channel.
141
+ return [];
142
+ }
143
+ },
144
+ },
145
+ ]
146
+ : []),
147
+ ],
148
+ server: {
149
+ middlewareMode: true,
150
+ hmr: { port: HMR_PORT },
151
+ fs: {
152
+ allow: [
153
+ engineRoot,
154
+ ...(nodeModulesRealPath ? [nodeModulesRealPath] : []),
155
+ ...(projectPath ? [projectPath] : []),
156
+ ],
157
+ },
158
+ },
159
+ // Pass project path to editor client (server-first mode is implied by the
160
+ // dev server's Vite mode 'development' — see editor-mode.ts).
161
+ define: {
162
+ __VGAI_PROJECT_PATH__: JSON.stringify(projectPath ?? ''),
163
+ // Track N, N2 (docs/REACT-INGEST-LANDING-DESIGN.md D-N2): the absolute
164
+ // repo root, so `ingest/discovery-react.ts` can build the `projectRoot`
165
+ // an `ingest-react` fixture's `resolveWorldAdapter` call needs (its
166
+ // `entry`'s `/@fs/` dynamic import + `loadProjectGameProvider` lookup)
167
+ // WITHOUT a "current project" — these in-tree fixtures live in THIS
168
+ // package's own source tree, not a project a user opens (mirrors
169
+ // `ingestGameShadowRoots`'s identical reasoning for the game-globals
170
+ // shadow). Undefined in the `web` (browser-mode, no dev server) build —
171
+ // `ingestGameReactProjectRoot` degrades to `undefined` there, and every
172
+ // caller already gates on `getEditorMode() !== 'browser'` first.
173
+ __VGAI_ENGINE_ROOT__: JSON.stringify(engineRoot),
174
+ },
175
+ });
176
+
177
+ // 2. Mount editor API routes (/__editor/*) with Vite fs.allow update callback
178
+ const editorRouter = createEditorServer({
179
+ engineRoot,
180
+ projectPath,
181
+ onProjectOpened(newProjectPath: string) {
182
+ // Dynamically allow Vite to serve /@fs/ paths from the new project directory
183
+ const allowed = vite.config.server.fs.allow;
184
+ if (!allowed.includes(newProjectPath)) {
185
+ allowed.push(newProjectPath);
186
+ }
187
+ // Also start watching the project for HMR
188
+ vite.watcher.add(newProjectPath);
189
+ // And gate its modules with the game-globals prelude (see vgai-game-globals above).
190
+ projectRoots.add(newProjectPath);
191
+ // And point the project-static route (D-X2) at the newly opened project.
192
+ currentProjectRoot = newProjectPath;
193
+ // Keep the session registry (D12) pointing at the CURRENT project so
194
+ // `vgai edit` reuse/targeting decisions see switches, not boot state.
195
+ registerSession({
196
+ project: newProjectPath,
197
+ port: PORT,
198
+ pid: process.pid,
199
+ startedAt: sessionStartedAt,
200
+ });
201
+ },
202
+ });
203
+ app.use(editorRouter);
204
+ app.use(vite.middlewares);
205
+
206
+ // Opt-in PROJECT-SCOPED poll watching (VGAI_WATCH_POLL=1, or =<ms> for the
207
+ // interval). Load-bearing on WSL when the project lives on a Windows drive
208
+ // (/mnt/c, drvfs): inotify never fires there, so without polling the editor
209
+ // silently misses every external file edit — most visibly the data-asset
210
+ // live-tuning loop (docs/DATA-TOOLS-DESIGN.md §2.3: edit
211
+ // `src/data/*.data.json` in VS Code/agent while playing → HMR into the
212
+ // running game). The project's own vite.config.ts can NOT provide this —
213
+ // the editor boots Vite from the ENGINE's config and only `watcher.add()`s
214
+ // the project — so this env var is the single knob.
215
+ //
216
+ // Deliberately NOT Vite's `server.watch.usePolling`: that polls the whole
217
+ // watched set (the entire engine tree), and over drvfs the stat flood
218
+ // starves the event loop badly enough that play-mode's game mount never
219
+ // completes (field-tested 2026-07-10: editor shell loads, ▶ mounts nothing
220
+ // even after 45s; scoped polling mounts fine). Instead: a tiny mtime poller
221
+ // over the CURRENT project only, forwarding synthetic add/change/unlink
222
+ // events into Vite's watcher, which runs the normal invalidate+HMR pipeline.
223
+ if (process.env['VGAI_WATCH_POLL']) {
224
+ startProjectPollWatcher(vite.watcher, () => currentProjectRoot);
225
+ }
226
+
227
+ // 3. Start Express
228
+ const sessionStartedAt = new Date().toISOString();
229
+ const server = app.listen(PORT, HOST, () => {
230
+ console.log(
231
+ `\n \x1b[32mEditor server\x1b[0m running at \x1b[36mhttp://${HOST}:${PORT}\x1b[0m`,
232
+ );
233
+ if (projectPath) {
234
+ console.log(` \x1b[33mProject\x1b[0m: ${projectPath}`);
235
+ }
236
+ console.log(
237
+ ` \x1b[33mEditor\x1b[0m: \x1b[36mhttp://${HOST}:${PORT}/\x1b[0m\n`,
238
+ );
239
+ // D12: announce this session so `vgai edit` can reuse instead of hijack.
240
+ registerSession({
241
+ project: projectPath ?? null,
242
+ port: PORT,
243
+ pid: process.pid,
244
+ startedAt: sessionStartedAt,
245
+ });
246
+ });
247
+
248
+ // SC5: friendly message instead of an opaque crash on port conflict.
249
+ server.on('error', (err: NodeJS.ErrnoException) => {
250
+ console.error(`\n \x1b[31m${friendlyListenError(err, PORT, HOST)}\x1b[0m\n`);
251
+ process.exit(1);
252
+ });
253
+
254
+ // Cleanup on shutdown
255
+ const shutdown = () => {
256
+ unregisterSession(process.pid);
257
+ vite.close();
258
+ server.close();
259
+ };
260
+ process.once('SIGINT', shutdown);
261
+ process.once('SIGTERM', shutdown);
262
+ process.once('exit', () => unregisterSession(process.pid));
263
+ }
264
+
265
+ /**
266
+ * VGAI_WATCH_POLL's engine: rescan the current project's tree every
267
+ * `interval` ms (default 1000; `VGAI_WATCH_POLL=<ms>` overrides), diff mtimes
268
+ * against the previous scan, and forward add/change/unlink as synthetic
269
+ * events on Vite's own chokidar watcher — from there the stock
270
+ * invalidate + handleHotUpdate pipeline (including the vgai-script-hmr
271
+ * plugin above) runs exactly as if inotify had fired. See the call site's
272
+ * comment for why this exists and why it is scoped to the project.
273
+ *
274
+ * Cost model: a project tree is small (tens to a few hundred files once
275
+ * node_modules/.git/dist are skipped), so even drvfs-priced stats are cheap.
276
+ * Switching projects re-baselines silently (no spurious event storm).
277
+ */
278
+ function startProjectPollWatcher(
279
+ watcher: { emit: (event: string, file: string) => void },
280
+ getRoot: () => string | undefined,
281
+ ): void {
282
+ const raw = Number(process.env['VGAI_WATCH_POLL']);
283
+ const interval = raw > 1 ? raw : 1000;
284
+ const SKIP = new Set(['node_modules', '.git', 'dist', '.vgai', 'logs']);
285
+ let known: Map<string, number> | undefined;
286
+ let lastRoot: string | undefined;
287
+
288
+ const listDir = (dir: string): Dirent[] => {
289
+ try {
290
+ return readdirSync(dir, { withFileTypes: true });
291
+ } catch {
292
+ return []; // directory vanished mid-scan
293
+ }
294
+ };
295
+
296
+ const statMtime = (file: string): number | undefined => {
297
+ try {
298
+ return statSync(file).mtimeMs;
299
+ } catch {
300
+ return undefined; // file vanished between readdir and stat
301
+ }
302
+ };
303
+
304
+ const scan = (dir: string, out: Map<string, number>): void => {
305
+ for (const entry of listDir(dir)) {
306
+ if (SKIP.has(entry.name)) continue;
307
+ const full = path.join(dir, entry.name);
308
+ if (entry.isDirectory()) {
309
+ scan(full, out);
310
+ continue;
311
+ }
312
+ if (!entry.isFile()) continue;
313
+ const mtime = statMtime(full);
314
+ if (mtime !== undefined) out.set(full, mtime);
315
+ }
316
+ };
317
+
318
+ const diffAndEmit = (prev: Map<string, number>, next: Map<string, number>): void => {
319
+ for (const [file, mtime] of next) {
320
+ const was = prev.get(file);
321
+ if (was === undefined) watcher.emit('add', file);
322
+ else if (was !== mtime) watcher.emit('change', file);
323
+ }
324
+ for (const file of prev.keys()) {
325
+ if (!next.has(file)) watcher.emit('unlink', file);
326
+ }
327
+ };
328
+
329
+ const tick = (): void => {
330
+ const root = getRoot();
331
+ if (root !== lastRoot) {
332
+ lastRoot = root;
333
+ known = undefined; // new/first project: baseline scan, emit nothing
334
+ }
335
+ if (root) {
336
+ const next = new Map<string, number>();
337
+ scan(root, next);
338
+ if (known) diffAndEmit(known, next);
339
+ known = next;
340
+ }
341
+ setTimeout(tick, interval);
342
+ };
343
+ setTimeout(tick, interval);
344
+ console.log(` Project poll-watcher on (VGAI_WATCH_POLL, every ${interval}ms)`);
345
+ }
346
+
347
+ main().catch((err) => {
348
+ console.error('Failed to start dev server:', err);
349
+ process.exit(1);
350
+ });