@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,149 @@
1
+ // C3 (phase-b/c3-r3f-packaged) — the adaptive `optimizeDeps.entries` source
2
+ // for `packaged.ts`'s project-rooted Vite instance. See that file's header
3
+ // doc comment, "optimizeDeps" section, for the full crawl-vs-crash story;
4
+ // this module only computes WHICH files to scope the crawl to.
5
+
6
+ import { existsSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { loadGameManifestFile } from '../../engine/src/manifest/load-file';
9
+
10
+ /**
11
+ * SERVER-ONLY packages that must never be crawled INTO during the client
12
+ * dep-prebundle — passed to `optimizeDeps.exclude` in `packaged.ts` (C3
13
+ * hardening follow-on). Belt to the entries-scoping suspenders.
14
+ *
15
+ * ## The pathological case this closes
16
+ *
17
+ * The entries-scoping fix (`computeProjectOptimizeDepsEntries` below) makes
18
+ * the crawl start ONLY from a project's declared world entries — which
19
+ * NORMALLY never reach `server/` code, so the colyseus server stack
20
+ * (`colyseus` -> `@colyseus/core` -> `@pm2/io`) is never crawled and the
21
+ * esbuild-scanner crash that `noDiscovery: true` used to guard against never
22
+ * fires. But if a project VIOLATES the repo's D2 rule (CLAUDE.md
23
+ * "Networking": client code calls Colyseus via the CLIENT SDK
24
+ * `@colyseus/sdk`, dynamic-imported, and NEVER value-imports the `server/`
25
+ * room classes) by having a WORLD ENTRY statically `import` from `server/`,
26
+ * the crawl reaches `@colyseus/core`'s `Stats.mjs`, whose `import("@pm2/io")`
27
+ * has no resolvable browser entry, and esbuild's prebundle step THROWS from
28
+ * inside the async optimizer — which, unhandled, HARD-KILLS the whole
29
+ * packaged node process (the only authoring surface). That is a strictly
30
+ * harsher degrade than the old `noDiscovery: true`, which at least still
31
+ * booted an editor.
32
+ *
33
+ * `exclude`-ing these three names keeps esbuild from crawling into them
34
+ * during prebundle: even a world entry that (wrongly) reaches them no longer
35
+ * crashes the scanner — the offending world degrades to a benign browser-
36
+ * side runtime import failure for that ONE world, and the editor process
37
+ * survives and serves everything else. Verified live (a real, checkout-
38
+ * absent packaged boot against a project whose world entry value-imports
39
+ * `server/rooms`): without this list the server process dies at boot; with
40
+ * it, the server boots and serves.
41
+ *
42
+ * CRITICAL: this list must NOT contain `@colyseus/sdk` — that is the
43
+ * legitimate CLIENT SDK a real multiplayer project's own game code imports
44
+ * (dynamic-imported per D2; e.g. `examples/third-person-arena/src/
45
+ * network.ts`). It is a SEPARATE package from `@colyseus/core` — its own
46
+ * published `build/index.mjs` is a self-contained client bundle — and MUST
47
+ * still prebundle so those projects work. Excluding only the server-side
48
+ * names leaves the client SDK's prebundle path untouched (proven: a world
49
+ * entry importing `@colyseus/sdk` still prebundles it cleanly with this
50
+ * exclude list active).
51
+ */
52
+ export const SERVER_ONLY_PREBUNDLE_EXCLUDE: readonly string[] = [
53
+ 'colyseus',
54
+ '@colyseus/core',
55
+ '@pm2/io',
56
+ ];
57
+
58
+ /**
59
+ * The project-relative CLIENT entry files `optimizeDeps.entries` should scope
60
+ * Vite's dependency-DISCOVERY crawl to, for the project rooted at
61
+ * `projectRoot` — every `worlds[].entry` its OWN `vgai.game.json` declares
62
+ * (default-adapter, code-authored worlds; scene-only worlds have no `entry`
63
+ * and contribute nothing here — their gameplay code is registered
64
+ * elsewhere, reached transitively from whichever world DOES declare an
65
+ * `entry`, e.g. the template's single world), deduped, and filtered to paths
66
+ * that exist on disk.
67
+ *
68
+ * ## Why THESE files, not `index.html` or the project's own bootstrap `main.ts`
69
+ *
70
+ * A project's `index.html` -> `src/main.ts` bootstrap ties every declared
71
+ * world together for the STANDALONE (`npm run game`) build, and — critically
72
+ * — the scaffolded template's own `main.ts` also carries the P2P-Colyseus
73
+ * URL-param branch that dynamically imports the project's OWN `server/rooms`
74
+ * (`await import('../server/rooms')`, gated behind `?net=p2p-host`, but
75
+ * esbuild's dependency scanner follows a string-literal dynamic `import()`
76
+ * as an unconditional graph edge regardless of the runtime guard around it).
77
+ * `server/rooms.ts` re-exports real Colyseus `Room` classes, whose `colyseus`
78
+ * (SERVER package) import chain reaches `@colyseus/core` -> `@pm2/io`, a
79
+ * package esbuild's prebundle step cannot resolve an entry for (confirmed
80
+ * empirically, not theoretically: a direct `vite.optimizeDeps` run scoped to
81
+ * `index.html`-style discovery against `packages/editor/template` throws
82
+ * `Failed to resolve entry for package "@pm2/io"` from inside
83
+ * `@colyseus/core/build/Stats.mjs`'s own `import("@pm2/io")` — this is
84
+ * `packaged.ts`'s ORIGINAL reason for `noDiscovery: true`, verified still
85
+ * true on this Vite version at the time of the C3 fix).
86
+ *
87
+ * A manifest's `worlds[].entry` files never reach that chain: they are the
88
+ * PER-WORLD mount code the editor's own adapter-resolver dynamically imports
89
+ * directly for Play mode / world mounting (never `src/main.ts` — that
90
+ * bootstrap is the STANDALONE build's concern, unrelated to how the editor
91
+ * itself mounts a world; see `packaged.ts`'s header doc comment). Scoping the
92
+ * crawl to exactly these files means: (a) it never touches `server/`, so the
93
+ * `@pm2/io` class of crash cannot occur no matter what a project's server
94
+ * room code imports, and (b) any CJS-needing-prebundle client dependency a
95
+ * world's own code reaches — `react`, `@react-three/fiber`, `@react-three/
96
+ * drei`, or some future ecosystem this repo has never heard of — gets
97
+ * auto-discovered and prebundled for free, with no per-package allowlist to
98
+ * maintain (this is what actually fixes the R3F class of bug this file's own
99
+ * feature exists for: `@react-three/fiber`'s CJS transitive deps like
100
+ * `use-sync-external-store` are reached from `examples/r3f-probe`'s
101
+ * `src/net-entry.tsx`, never from anything server-shaped).
102
+ *
103
+ * The client SDK case (`@colyseus/sdk`, the package a world's own game code
104
+ * legitimately imports for real multiplayer — e.g.
105
+ * `examples/third-person-arena/src/network.ts`) is DIFFERENT from the
106
+ * server-package case above and does NOT crash: `@colyseus/sdk`'s own
107
+ * published build (`build/index.mjs`) is a self-contained client bundle that
108
+ * does not pull in `@colyseus/core`'s server-only `Stats.mjs` (confirmed
109
+ * empirically the same way: a full prebundle scoped to
110
+ * `examples/third-person-arena`'s own `src/index.tsx` world entry succeeds
111
+ * and prebundles `@colyseus/sdk` cleanly).
112
+ *
113
+ * ## Known scope trim
114
+ *
115
+ * Computed ONCE, from the boot-time `VGAI_PROJECT`, exactly like this file's
116
+ * sibling `engineSrc`/`resolve.alias` resolution in `packaged.ts` — NOT
117
+ * re-computed when `onProjectOpened` switches to a different project mid-
118
+ * session (a pre-existing limitation of that callback: it only extends
119
+ * `fs.allow`/the watcher/the game-globals shadow, never anything that was
120
+ * baked into the Vite instance's `createServer` call, like `resolve.alias`
121
+ * already wasn't either). A switched-to project keeps whatever entries the
122
+ * ORIGINALLY opened project computed.
123
+ *
124
+ * Never throws: an absent/unparseable manifest (no project open yet, a
125
+ * pre-Phase-B project, a broken `vgai.game.json`) degrades to `[]` — the
126
+ * SAFEST fallback, not a guess. Per Vite's own `optimizeDeps.entries`
127
+ * resolution (`computeEntries` in vite's dep-scanner), a truthy-but-empty
128
+ * array short-circuits the `**\/*.html` crawl fallback WITHOUT crawling
129
+ * anything itself, so an unreadable manifest degrades to "no auto-discovery
130
+ * beyond the static `include` list" — the same conservative posture
131
+ * `noDiscovery: true` had, rather than falling back to a full crawl that
132
+ * could reach server code we know nothing about.
133
+ */
134
+ export function computeProjectOptimizeDepsEntries(projectRoot: string): string[] {
135
+ let manifest: ReturnType<typeof loadGameManifestFile>;
136
+ try {
137
+ manifest = loadGameManifestFile(join(projectRoot, 'vgai.game.json'));
138
+ } catch {
139
+ return [];
140
+ }
141
+
142
+ const entries = new Set<string>();
143
+ for (const world of manifest.worlds) {
144
+ if (!world.entry) continue;
145
+ if (!existsSync(join(projectRoot, world.entry))) continue;
146
+ entries.add(world.entry);
147
+ }
148
+ return [...entries];
149
+ }
@@ -0,0 +1,65 @@
1
+ // T3.3 slice 1 — server-side compat-view synthesis for `project.json` reads
2
+ // (docs/CLI-ON-FOLDER-DESIGN.md §1C).
3
+ //
4
+ // A project folder's config view can come from two places: `project.json`
5
+ // itself (legacy, still authoritative when present — including fields like
6
+ // `appId` that have no manifest home) or, when that file is ABSENT, a
7
+ // synthesized `{ name, defaultScene }` view derived from `vgai.game.json`
8
+ // (`name` from the manifest; `defaultScene` from the first world's `scene`,
9
+ // explicitly `null` if that world has none — e.g. an `entry`-based or ingest
10
+ // world — NEVER omitted: a consumer that saw the key simply missing collapsed
11
+ // "not declared" into "guess a path" via `?? 'scenes/default.vscn.json'`,
12
+ // which is what caused `initial-scene.ts` to silently WRITE a placeholder
13
+ // scene for a legal entry-only first world — §7.1-12 / R2b fix 1. `null` is
14
+ // the explicit, typed "no scene declared" the consumer is forced to branch
15
+ // on instead of defaulting through.
16
+ //
17
+ // This is the ONE place the project.json/manifest fallback rule lives —
18
+ // every server route that reads project config goes through `readProjectView`
19
+ // rather than re-implementing the fallback (editor-server.ts's static
20
+ // `/project.json` serve, `GET /__editor/project`, `POST /__editor/open-project`'s
21
+ // existence check, and the recent-projects existence check all call this).
22
+
23
+ import { readFile } from 'node:fs/promises';
24
+ import { join } from 'node:path';
25
+ import { loadGameManifestFile } from '../../engine/src/manifest/load-file';
26
+
27
+ export type ProjectView = Record<string, unknown>;
28
+
29
+ /**
30
+ * Read the compat project view for `root`.
31
+ *
32
+ * - `project.json` present -> its parsed contents verbatim (whatever fields
33
+ * it has, including `appId`).
34
+ * - `project.json` absent, `vgai.game.json` present -> synthesized
35
+ * `{ name, defaultScene }`, where `defaultScene` is the first world's
36
+ * `scene` if declared, else explicit `null` (never omitted — appId is
37
+ * omitted, though: it has no manifest home and nothing reads it per
38
+ * docs/CLI-ON-FOLDER-DESIGN.md §1C).
39
+ * - Neither present, or the manifest exists but fails to parse -> `null`.
40
+ * (The hard fail-loud-on-a-broken-manifest behavior lives at the CLI
41
+ * discovery gate, `packages/vgai-cli/src/discovery.ts` — a server route
42
+ * reading a broken manifest degrades to "no project" rather than crashing
43
+ * the request.)
44
+ */
45
+ export async function readProjectView(root: string): Promise<ProjectView | null> {
46
+ try {
47
+ const raw = await readFile(join(root, 'project.json'), 'utf-8');
48
+ return JSON.parse(raw) as ProjectView;
49
+ } catch {
50
+ // project.json absent/unreadable — fall through to the manifest.
51
+ }
52
+
53
+ try {
54
+ const manifest = loadGameManifestFile(join(root, 'vgai.game.json'));
55
+ const firstWorld = manifest.worlds[0];
56
+ // Always set the key — explicit `null`, not an omitted property, when the
57
+ // first world declares no scene (e.g. `entry`-only). See the module doc
58
+ // comment above: an omitted key is what let a naive `?? 'scenes/
59
+ // default.vscn.json'` consumer silently guess a path.
60
+ const view: ProjectView = { name: manifest.name, defaultScene: firstWorld?.scene ?? null };
61
+ return view;
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
@@ -0,0 +1,430 @@
1
+ /**
2
+ * Pure, dependency-light helpers for the editor server.
3
+ *
4
+ * Everything here is side-effect free and unit-testable (see
5
+ * packages/editor/test/server-security.test.ts). The route handlers in
6
+ * editor-server.ts / asset-library-routes.ts delegate their security and
7
+ * correctness checks to these functions so the logic can be verified in
8
+ * isolation.
9
+ */
10
+
11
+ import { existsSync } from 'node:fs';
12
+ import { createRequire } from 'node:module';
13
+ import { dirname, join, resolve, sep } from 'node:path';
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Path containment (S1 / S5 / S6)
17
+ // ---------------------------------------------------------------------------
18
+
19
+ /**
20
+ * True if `child` resolves to the same path as `parent`, or to a path strictly
21
+ * inside it. Defends against `..` traversal and sibling-prefix attacks where a
22
+ * naive `startsWith` would accept `/a/public-secrets` as inside `/a/public`.
23
+ */
24
+ export function isPathInside(parent: string, child: string): boolean {
25
+ const resolvedParent = resolve(parent);
26
+ const resolvedChild = resolve(child);
27
+ if (resolvedChild === resolvedParent) return true;
28
+ const parentWithSep = resolvedParent.endsWith(sep) ? resolvedParent : resolvedParent + sep;
29
+ return resolvedChild.startsWith(parentWithSep);
30
+ }
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Asset source + host allowlists (S1 / S2)
34
+ // ---------------------------------------------------------------------------
35
+
36
+ export const ALLOWED_ASSET_SOURCES = ['polyhaven', 'ambientcg', 'local'] as const;
37
+ export type AllowedAssetSource = (typeof ALLOWED_ASSET_SOURCES)[number];
38
+
39
+ /** Allowlist the `source` segment so it can never escape the library directory. */
40
+ export function isAllowedAssetSource(source: unknown): source is AllowedAssetSource {
41
+ return (
42
+ typeof source === 'string' && (ALLOWED_ASSET_SOURCES as readonly string[]).includes(source)
43
+ );
44
+ }
45
+
46
+ /** Known CDN / API domains the server is permitted to fetch from. */
47
+ export const ASSET_HOST_ALLOWLIST = [
48
+ 'polyhaven.com',
49
+ 'polyhaven.org',
50
+ 'ambientcg.com',
51
+ 'struffelproduction.com', // ambientCG's download CDN
52
+ ];
53
+
54
+ /**
55
+ * True if `host` is an IPv4/IPv6 literal inside a private, loopback,
56
+ * link-local, or otherwise non-routable range. Used to block SSRF to metadata
57
+ * endpoints (e.g. 169.254.169.254) and internal services.
58
+ */
59
+ export function isPrivateOrLinkLocalHost(host: string): boolean {
60
+ const h = host.replace(/^\[|\]$/g, '').toLowerCase();
61
+
62
+ if (h === 'localhost' || h.endsWith('.localhost')) return true;
63
+
64
+ // IPv6 loopback / link-local / unique-local.
65
+ if (h === '::1' || h === '::') return true;
66
+ if (h.startsWith('fe80:') || h.startsWith('fc') || h.startsWith('fd')) return true;
67
+ // IPv4-mapped IPv6, e.g. ::ffff:169.254.169.254
68
+ const mapped = h.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
69
+ if (mapped) return isPrivateOrLinkLocalHost(mapped[1]!);
70
+
71
+ const m = h.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
72
+ if (!m) return false;
73
+ const a = Number(m[1]);
74
+ const b = Number(m[2]);
75
+ if (a === 0 || a === 10 || a === 127) return true; // 0.x, private, loopback
76
+ if (a === 169 && b === 254) return true; // link-local (cloud metadata)
77
+ if (a === 172 && b >= 16 && b <= 31) return true; // private
78
+ if (a === 192 && b === 168) return true; // private
79
+ if (a === 100 && b >= 64 && b <= 127) return true; // CGNAT
80
+ if (a >= 224) return true; // multicast / reserved
81
+ return false;
82
+ }
83
+
84
+ /**
85
+ * Validate a body-supplied download URL: must be HTTPS, point at an allowlisted
86
+ * CDN host, and not resolve to a private / link-local literal IP. (S2)
87
+ */
88
+ export function isAllowedAssetHost(urlString: string): boolean {
89
+ let u: URL;
90
+ try {
91
+ u = new URL(urlString);
92
+ } catch {
93
+ return false;
94
+ }
95
+ if (u.protocol !== 'https:') return false;
96
+ const host = u.hostname.toLowerCase();
97
+ if (isPrivateOrLinkLocalHost(host)) return false;
98
+ return ASSET_HOST_ALLOWLIST.some((d) => host === d || host.endsWith('.' + d));
99
+ }
100
+
101
+ // ---------------------------------------------------------------------------
102
+ // AppleScript escaping (S4)
103
+ // ---------------------------------------------------------------------------
104
+
105
+ // Matches ASCII control characters (U+0000–U+001F).
106
+ const CONTROL_CHARS = /[\u0000-\u001f]/g;
107
+
108
+ /**
109
+ * Escape a string for safe inclusion inside a double-quoted AppleScript literal.
110
+ * Backslashes must be doubled BEFORE quotes are escaped, otherwise an injected
111
+ * trailing backslash neutralises the closing quote. Control characters are
112
+ * stripped so they can't break out of the `-e` argument.
113
+ */
114
+ export function escapeAppleScriptString(s: string): string {
115
+ return s.replace(CONTROL_CHARS, ' ').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
116
+ }
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // Content-Disposition filename sanitization (S7)
120
+ // ---------------------------------------------------------------------------
121
+
122
+ // Control chars, double-quote, and backslash are unsafe in a quoted filename.
123
+ const UNSAFE_FILENAME_CHARS = /[\u0000-\u001f"\\]/g;
124
+
125
+ /**
126
+ * Reduce an arbitrary name to a safe `filename="..."` value: basename only, no
127
+ * path separators, quotes, backslashes, or control characters.
128
+ */
129
+ export function sanitizeContentDispositionFilename(name: string): string {
130
+ const base = name.split(/[\\/]/).pop() ?? '';
131
+ const cleaned = base.replace(UNSAFE_FILENAME_CHARS, '').trim();
132
+ return cleaned || 'download';
133
+ }
134
+
135
+ // ---------------------------------------------------------------------------
136
+ // /@fs served-extension allowlist (S5)
137
+ // ---------------------------------------------------------------------------
138
+
139
+ const SERVABLE_FS_EXTENSIONS = new Set([
140
+ 'ts',
141
+ 'tsx',
142
+ 'js',
143
+ 'jsx',
144
+ 'mjs',
145
+ 'cjs',
146
+ 'json',
147
+ 'wasm',
148
+ 'css',
149
+ 'glsl',
150
+ 'vert',
151
+ 'frag',
152
+ ]);
153
+
154
+ /** Restrict /@fs serving to source/asset file types (never .env, keys, etc.). */
155
+ export function isServableFsExtension(ext: string): boolean {
156
+ return SERVABLE_FS_EXTENSIONS.has(ext.toLowerCase());
157
+ }
158
+
159
+ // ---------------------------------------------------------------------------
160
+ // Origin allowlist for mutating editor routes (S3)
161
+ // ---------------------------------------------------------------------------
162
+
163
+ /** True for loopback hostnames (localhost / 127.0.0.0/8 / ::1). */
164
+ export function isLoopbackHostname(host: string): boolean {
165
+ const h = host.replace(/^\[|\]$/g, '').toLowerCase();
166
+ if (h === 'localhost' || h === '::1') return true;
167
+ return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h);
168
+ }
169
+
170
+ /**
171
+ * CSRF / drive-by defense for mutating `/__editor/*` routes. A browser always
172
+ * sends an `Origin` on cross-site POSTs; if present it must be a loopback origin
173
+ * (or an explicitly-allowed host). Non-browser clients (the CLI/SDK using Node
174
+ * `fetch`) send no `Origin` and are allowed.
175
+ */
176
+ export function isAllowedEditorOrigin(
177
+ origin: string | undefined,
178
+ extraAllowedHosts: string[] = [],
179
+ ): boolean {
180
+ if (!origin) return true;
181
+ let u: URL;
182
+ try {
183
+ u = new URL(origin);
184
+ } catch {
185
+ return false;
186
+ }
187
+ const host = u.hostname.toLowerCase();
188
+ if (isLoopbackHostname(host)) return true;
189
+ return extraAllowedHosts.some((h) => h.toLowerCase() === host);
190
+ }
191
+
192
+ // ---------------------------------------------------------------------------
193
+ // Installed-package source resolution (B1b, packaging plan §3(b)1)
194
+ // ---------------------------------------------------------------------------
195
+
196
+ /**
197
+ * Resolve `packageName`'s own `src/` directory as INSTALLED in `fromDir`'s
198
+ * `node_modules` — Node's own resolution algorithm, rooted at `fromDir` (a
199
+ * project directory), not at wherever this server process's own code lives.
200
+ *
201
+ * Why: a project that pins `@vgai/engine@0.3.0` must be served ITS 0.3.0
202
+ * source, never a copy baked into whatever package happens to be running the
203
+ * editor server — otherwise the exact version-skew class Phase B exists to
204
+ * kill (an editor silently serving a different engine version than the one
205
+ * the project declares) reappears one layer down, inside script-serving.
206
+ * `packageName`'s `exports` map must publish a `./package.json` entry (both
207
+ * `@vgai/engine` and `@vgai/scene-ui` do, as of B1a) for `require.resolve` to
208
+ * find it.
209
+ *
210
+ * Returns `null` (never throws) when `packageName` isn't resolvable from
211
+ * `fromDir` — no project open, a pre-Phase-B project with no such
212
+ * dependency, or a project that hasn't `npm install`ed yet. Callers fall back
213
+ * to a checkout-relative default in that case.
214
+ */
215
+ export function resolveInstalledPackageSrcDir(fromDir: string, packageName: string): string | null {
216
+ try {
217
+ const req = createRequire(join(fromDir, 'package.json'));
218
+ const pkgJsonPath = req.resolve(`${packageName}/package.json`);
219
+ return join(dirname(pkgJsonPath), 'src');
220
+ } catch {
221
+ return null;
222
+ }
223
+ }
224
+
225
+ // ---------------------------------------------------------------------------
226
+ // Bind host + listen-error helpers (S3 / SC5)
227
+ // ---------------------------------------------------------------------------
228
+
229
+ /** Resolve the interface to bind to. Defaults to loopback; opt-in via env. */
230
+ export function resolveBindHost(env: NodeJS.ProcessEnv = process.env): string {
231
+ return env['VGAI_EDITOR_HOST'] || env['EDITOR_HOST'] || '127.0.0.1';
232
+ }
233
+
234
+ /** Format a friendly message for a server `listen` error (notably EADDRINUSE). */
235
+ export function friendlyListenError(
236
+ err: NodeJS.ErrnoException,
237
+ port: number,
238
+ host: string,
239
+ ): string {
240
+ if (err.code === 'EADDRINUSE') {
241
+ return (
242
+ `Port ${port} is already in use on ${host}. ` +
243
+ `Another editor instance may already be running — stop it, or set PORT / VGAI_EDITOR_HOST ` +
244
+ `to a free address and try again.`
245
+ );
246
+ }
247
+ if (err.code === 'EACCES') {
248
+ return `Permission denied binding to ${host}:${port}. Try a port above 1024.`;
249
+ }
250
+ return `Failed to start server on ${host}:${port}: ${err.message}`;
251
+ }
252
+
253
+ // ---------------------------------------------------------------------------
254
+ // File-move disambiguation (SC2)
255
+ // ---------------------------------------------------------------------------
256
+
257
+ export interface MoveCandidate {
258
+ hash: string;
259
+ size: number;
260
+ timestamp: number;
261
+ }
262
+
263
+ /**
264
+ * A delete+create pair is treated as a move (and triggers scene-ref rewriting)
265
+ * only when it is unambiguous: the added file must match a pending unlink by
266
+ * BOTH content hash AND byte size, that match must be unique, and it must fall
267
+ * within the move window. Hash-only matching risks rewriting scene files on an
268
+ * unrelated delete+create that happens to collide. (SC2)
269
+ */
270
+ export function findUniqueMoveMatch<T extends MoveCandidate>(
271
+ candidates: readonly T[],
272
+ addedHash: string,
273
+ addedSize: number,
274
+ now: number,
275
+ windowMs: number,
276
+ ): T | null {
277
+ const matches = candidates.filter(
278
+ (c) => c.hash === addedHash && c.size === addedSize && now - c.timestamp < windowMs,
279
+ );
280
+ return matches.length === 1 ? matches[0]! : null;
281
+ }
282
+
283
+ // ---------------------------------------------------------------------------
284
+ // Manifest write validation (A4, D8) — POST /__editor/manifest
285
+ // ---------------------------------------------------------------------------
286
+
287
+ export interface ManifestWriteBody {
288
+ path?: unknown;
289
+ content?: unknown;
290
+ }
291
+
292
+ export type ManifestWriteValidation =
293
+ | { ok: true; content: string }
294
+ | { ok: false; status: number; error: string };
295
+
296
+ /**
297
+ * Validate a `POST /__editor/manifest` request body. The route HARD-CODES the
298
+ * write destination (`join(projectRoot, 'vgai.game.json')`) — this function
299
+ * never returns a path, only a green light + the (already-string, already-
300
+ * valid-JSON) content to write, so there is no way for a caller to derive the
301
+ * destination from user input even by accident. Rejects: no project open,
302
+ * any `path` other than the literal `'vgai.game.json'` (traversal, a
303
+ * different filename, an absolute path — all rejected identically), a
304
+ * non-string `content`, and content that fails to `JSON.parse`.
305
+ */
306
+ export function validateManifestWrite(
307
+ projectRoot: string,
308
+ engineRoot: string,
309
+ body: ManifestWriteBody,
310
+ ): ManifestWriteValidation {
311
+ if (projectRoot === engineRoot) {
312
+ return { ok: false, status: 400, error: 'No project open.' };
313
+ }
314
+ if (body.path !== undefined && body.path !== 'vgai.game.json') {
315
+ return {
316
+ ok: false,
317
+ status: 400,
318
+ error: "Invalid path — only 'vgai.game.json' may be written.",
319
+ };
320
+ }
321
+ if (typeof body.content !== 'string') {
322
+ return { ok: false, status: 400, error: 'Invalid content — expected a JSON string.' };
323
+ }
324
+ try {
325
+ JSON.parse(body.content);
326
+ } catch {
327
+ return { ok: false, status: 400, error: 'Content is not valid JSON.' };
328
+ }
329
+ return { ok: true, content: body.content };
330
+ }
331
+
332
+ // ---------------------------------------------------------------------------
333
+ // Project src/ watcher classification (W6a, docs/DATA-TOOLS-DESIGN.md §7)
334
+ // ---------------------------------------------------------------------------
335
+
336
+ /**
337
+ * Which editor list a `src/**` add/unlink invalidates, or `null` if it's
338
+ * irrelevant. Feeds the editor-server's second `src/`-scoped chokidar
339
+ * watcher (`startWatcher()` in editor-server.ts) so a NEW
340
+ * `src/tools/*.tool.tsx` or `src/data/*.data.json`/`*.schema.json` shows up
341
+ * in the dock/DATA tab without a full editor reload (spec §7 W4 field note
342
+ * c). `.schema.json` is included on the `data` side: its add/unlink flips the
343
+ * DATA tab between the no-emitted-schema teaching message and real widgets.
344
+ *
345
+ * `relPath` is project-root-relative with forward slashes (same convention
346
+ * `/__editor/data-files` and `/__editor/tool-files` already use).
347
+ */
348
+ export function classifyProjectSrcPath(relPath: string): 'tools' | 'data' | null {
349
+ if (relPath.startsWith('src/tools/') && relPath.endsWith('.tool.tsx')) return 'tools';
350
+ if (
351
+ relPath.startsWith('src/data/') &&
352
+ (relPath.endsWith('.data.json') || relPath.endsWith('.schema.json'))
353
+ ) {
354
+ return 'data';
355
+ }
356
+ return null;
357
+ }
358
+
359
+ // ---------------------------------------------------------------------------
360
+ // Command-relay response shaping (SC1)
361
+ // ---------------------------------------------------------------------------
362
+
363
+ export interface CommandResult {
364
+ ok?: boolean;
365
+ timedOut?: boolean;
366
+ error?: string;
367
+ /** Optional payload for commands that answer with data (e.g. `capture-viewport`'s base64 PNG). */
368
+ data?: Record<string, unknown>;
369
+ }
370
+
371
+ /** Map a relayed-command result to an HTTP status + body. A timeout (no editor
372
+ * connected) is a distinct 504 error rather than a fake success. (SC1)
373
+ * `data`, when present on a success, is spread into the response body
374
+ * alongside `ok: true` so callers (`HttpEditorTransport.sendCommand`) see it
375
+ * at the top level rather than needing a second field of indirection. */
376
+ export function commandResponseFor(result: CommandResult): {
377
+ status: number;
378
+ body: Record<string, unknown>;
379
+ } {
380
+ if (result.ok) return { status: 200, body: { ok: true, ...(result.data ?? {}) } };
381
+ if (result.timedOut) {
382
+ return {
383
+ status: 504,
384
+ body: { ok: false, error: result.error ?? 'Command timed out — no editor connected.' },
385
+ };
386
+ }
387
+ return { status: 400, body: { ok: false, error: result.error } };
388
+ }
389
+
390
+ // ---------------------------------------------------------------------------
391
+ // "New Project" packaged-mode guard (Phase B cleanup, ITEM 2)
392
+ // ---------------------------------------------------------------------------
393
+
394
+ /**
395
+ * True iff `engineRoot` looks like a real monorepo checkout `scaffoldProject`
396
+ * can read a starter/example template from — i.e.
397
+ * `<engineRoot>/packages/editor/template` exists, the EXACT path
398
+ * `create-vgai-project`'s `scaffoldProject` reads for the starter template
399
+ * (`packages/create-vgai-project/src/scaffold.ts`'s `templateDir`; the
400
+ * `--example <id>` path similarly reads `<engineRoot>/examples/<id>`, which
401
+ * only exists under the same monorepo layout).
402
+ *
403
+ * A packaged (`node_modules`-installed) `@vgai/editor`'s `engineRoot` is the
404
+ * EDITOR PACKAGE's own root instead of a monorepo checkout root (see
405
+ * `packaged.ts`'s header doc comment, "Known scope trims" — `createEditorServer`
406
+ * is constructed with `engineRoot: editorPackageRoot` there, since there is no
407
+ * real monorepo on disk to point at). That directory has no
408
+ * `packages/editor/template` subtree — only a bare `template/` at its own
409
+ * root — so this correctly reports `false` there, and `true` for both `dev.ts`
410
+ * (real `engineRoot`) and `prod.ts` (Electron packaging, also a real checkout
411
+ * at build time).
412
+ */
413
+ export function isMonorepoScaffoldRoot(engineRoot: string): boolean {
414
+ return existsSync(join(engineRoot, 'packages', 'editor', 'template'));
415
+ }
416
+
417
+ /**
418
+ * The actionable error `POST /__editor/create-project` returns when
419
+ * `!isMonorepoScaffoldRoot(engineRoot)` (packaged mode). `scaffoldProject`'s
420
+ * `monoRoot` assumes a monorepo checkout layout the packaged runtime doesn't
421
+ * have (`isMonorepoScaffoldRoot`'s doc comment) — rather than let it crash
422
+ * partway through with a confusing `ENOENT` reading a nonexistent template
423
+ * dir, the route fails loudly, up front, with the supported alternative.
424
+ * Relocating `template/` into `create-vgai-project` (so a packaged editor's
425
+ * "New Project" could scaffold without a monorepo at all) is the real fix,
426
+ * tracked as a follow-up (phase plan §6 landmine 5) — not attempted here.
427
+ */
428
+ export const PACKAGED_NO_MONOREPO_TEMPLATE_ERROR =
429
+ "Creating a new project from inside the packaged editor isn't supported yet. " +
430
+ 'Run `vgai create <name>` in a terminal, then open the result with `vgai edit <name>`.';