@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,132 @@
1
+ /**
2
+ * In-tree native-React ingest-game discovery (Track N, N2 —
3
+ * docs/REACT-INGEST-LANDING-DESIGN.md D-N2). The react sibling of
4
+ * `discovery.ts`/`discovery2d.ts`: every vendored native-React game gets its
5
+ * own `vgai.game.json` under `./games-react/<id>/` (one `react` world, an
6
+ * `{ ingest: { strategy: 'deduped' } }` adapter, `entry` pointing at a
7
+ * host-shim module in the SAME folder — D-N2). This module discovers them
8
+ * via `import.meta.glob`, mirroring `discovery2d.ts`'s mechanics.
9
+ *
10
+ * UNLIKE `discovery.ts`/`discovery2d.ts`, this module does NOT also glob the
11
+ * entry module itself: an `ingest-react` world mounts through
12
+ * `resolveWorldAdapter` (`adapter-resolver.ts`'s `resolveIngestReactAdapter`,
13
+ * N1) — the SAME `/@fs/`-dynamic-import + `createRoot` host stack
14
+ * `default-react` uses — rather than a capture-based bridge, so there is no
15
+ * "build an `IngestGame`/`IngestGame2D` descriptor with its own `load`
16
+ * thunk" step to replicate here. This module's ONLY job is: find every
17
+ * `games-react/*\/vgai.game.json`, validate its single world is a real
18
+ * `ingest-react` `'deduped'` world, and hand back the parsed
19
+ * `ResolvedGameManifest` + that world's absolute on-disk folder (the
20
+ * `projectRoot` `resolveWorldAdapter` needs for its `entry`'s `/@fs/`
21
+ * dynamic import and its `loadProjectGameProvider` lookup) — see
22
+ * `ingest-mode.ts`'s `enterIngestModeReact` for the mount step.
23
+ *
24
+ * `projectRoot` (an ABSOLUTE filesystem path) can't come from
25
+ * `import.meta.glob`/`import.meta.url` alone (those resolve to HTTP URLs in
26
+ * the browser, not filesystem paths) — it is built from
27
+ * `__VGAI_ENGINE_ROOT__` (a dev-server-only Vite `define`, `server/dev.ts`,
28
+ * mirroring the existing `__VGAI_PROJECT_PATH__` define) plus this folder's
29
+ * FIXED, known-at-build-time relative offset. `browser` editor-mode builds
30
+ * (no dev server) never define `__VGAI_ENGINE_ROOT__` — `getIngestGameReact`
31
+ * callers must check `getEditorMode() !== 'browser'` first (mirrors every
32
+ * other `/@fs/`-dependent ingest route's `getEditorMode() === 'browser'`
33
+ * guard, e.g. `ingest-mode.ts`'s `tryManifestIngestRoute`).
34
+ */
35
+
36
+ import { loadGameManifest, type ResolvedGameManifest } from '@engine/manifest/load';
37
+
38
+ declare const __VGAI_ENGINE_ROOT__: string;
39
+
40
+ const manifestModules = import.meta.glob('./games-react/*/vgai.game.json', {
41
+ eager: true,
42
+ import: 'default',
43
+ }) as Record<string, unknown>;
44
+
45
+ const MANIFEST_KEY_RE = /^\.\/games-react\/([^/]+)\/vgai\.game\.json$/;
46
+
47
+ function folderIdFromManifestKey(key: string): string {
48
+ const match = MANIFEST_KEY_RE.exec(key);
49
+ if (!match?.[1]) {
50
+ throw new Error(`react ingest discovery: unexpected manifest glob key "${key}"`);
51
+ }
52
+ return match[1];
53
+ }
54
+
55
+ /** A discovered native-React ingest fixture: its parsed manifest (exactly one
56
+ * `ingest-react` `'deduped'` world) plus that world for convenience. */
57
+ export interface IngestGameReact {
58
+ readonly folderId: string;
59
+ readonly manifest: ResolvedGameManifest;
60
+ readonly worldId: string;
61
+ }
62
+
63
+ function buildIngestGameReact(folderId: string, raw: unknown): IngestGameReact {
64
+ const manifest = loadGameManifest(raw);
65
+ if (manifest.worlds.length !== 1) {
66
+ throw new Error(
67
+ `react ingest discovery: "${folderId}/vgai.game.json" must declare exactly one world ` +
68
+ `(found ${manifest.worlds.length}).`,
69
+ );
70
+ }
71
+ const world = manifest.worlds[0]!;
72
+ if (
73
+ world.kind !== 'react' ||
74
+ world.adapter.type !== 'ingest' ||
75
+ world.adapter.identity !== 'ingest-react'
76
+ ) {
77
+ throw new Error(
78
+ `react ingest discovery: "${folderId}/vgai.game.json"'s world must be a react { ingest } ` +
79
+ `world (found kind "${world.kind}", adapter identity "${world.adapter.identity}").`,
80
+ );
81
+ }
82
+ if (world.adapter.strategy !== 'deduped') {
83
+ throw new Error(
84
+ `react ingest discovery: "${folderId}" declares strategy "${world.adapter.strategy}" — ` +
85
+ "react ingestion v1 supports ONLY 'deduped' (D-N3).",
86
+ );
87
+ }
88
+ if (!world.entry) {
89
+ throw new Error(
90
+ `react ingest discovery: "${folderId}" has no \`entry\` — an ingest-react world's \`entry\` ` +
91
+ 'must point at the host-shim module in the same folder (D-N2).',
92
+ );
93
+ }
94
+ return { folderId, manifest, worldId: world.id };
95
+ }
96
+
97
+ let _cache: IngestGameReact[] | null = null;
98
+
99
+ /** Every in-tree native-React ingest fixture, discovered from
100
+ * `./games-react/*\/vgai.game.json`. */
101
+ export function discoverIngestGamesReact(): IngestGameReact[] {
102
+ if (!_cache) {
103
+ _cache = Object.entries(manifestModules)
104
+ .map(([key, raw]) => buildIngestGameReact(folderIdFromManifestKey(key), raw))
105
+ .sort((a, b) => a.folderId.localeCompare(b.folderId));
106
+ }
107
+ return _cache;
108
+ }
109
+
110
+ /** Look up one discovered react ingest game by its folder id (`?ingest=<id>`
111
+ * resolution — the folder id IS the world id by convention, one world per
112
+ * manifest, but callers should key off `folderId` since that's what the URL
113
+ * param names). */
114
+ export function getIngestGameReact(folderId: string): IngestGameReact | undefined {
115
+ return discoverIngestGamesReact().find((g) => g.folderId === folderId);
116
+ }
117
+
118
+ /**
119
+ * The absolute on-disk `projectRoot` for a discovered react ingest game's
120
+ * folder — what `resolveWorldAdapter`/`resolveAllWorlds` need to resolve its
121
+ * `entry` (and, transitively, `loadProjectGameProvider`'s
122
+ * `src/ui/game-state.tsx` lookup). `undefined` when
123
+ * `__VGAI_ENGINE_ROOT__` isn't defined (browser-mode static build — no dev
124
+ * server, no `/@fs/` route to resolve through anyway).
125
+ */
126
+ export function ingestGameReactProjectRoot(folderId: string): string | undefined {
127
+ if (typeof __VGAI_ENGINE_ROOT__ !== 'string' || !__VGAI_ENGINE_ROOT__) return undefined;
128
+ const root = __VGAI_ENGINE_ROOT__.endsWith('/')
129
+ ? __VGAI_ENGINE_ROOT__.slice(0, -1)
130
+ : __VGAI_ENGINE_ROOT__;
131
+ return `${root}/packages/editor/src/ingest/games-react/${folderId}`;
132
+ }
@@ -0,0 +1,205 @@
1
+ /**
2
+ * In-tree ingest-game discovery — T3.3 slice 3 (docs/CLI-ON-FOLDER-DESIGN.md
3
+ * §1E). Replaces the deleted `ingest/games/registry.ts` (`INGEST_GAMES`
4
+ * array): every fixture game folder under `ingest/games/<id>/` now carries
5
+ * its own `vgai.game.json` (one `threejs` world, `{ ingest }` adapter); this
6
+ * module discovers them via `import.meta.glob` and builds the SAME
7
+ * `IngestGame[]` shape the registry used to hand-maintain — adding a new
8
+ * fixture becomes "drop a folder with a manifest", zero code edits here.
9
+ *
10
+ * Module-loading choice (the slice's CAVEAT): `resolveIngestDescriptor`'s
11
+ * `/@fs/${projectRoot}/...` dynamic-import route (adapter-resolver.ts) is
12
+ * built for an EXTERNAL project folder outside this package's own source
13
+ * tree (the CLI-on-folder route, §1E's other half) — not for these IN-TREE
14
+ * fixtures, which Vite already knows about statically. So this module does
15
+ * NOT call `resolveIngestDescriptor` at all; it uses separate
16
+ * `import.meta.glob`s, all statically analyzable by Vite (and by Vitest,
17
+ * which shares Vite's transform pipeline) in both dev AND unit tests, with
18
+ * no runtime fetch/`/@fs/` involved:
19
+ * - manifests (`vgai.game.json`, eager, parsed JSON) — the source of truth
20
+ * for id/description/strategy/assets/domStubs/captureTimeoutMs (`name`
21
+ * comes from the manifest's top-level game name);
22
+ * - entry modules (`*.js`, LAZY thunks) for the `shared`/`deduped`
23
+ * strategies — same shape as the registry's `load: () => import(...)`,
24
+ * side-effecting only when actually launched (a bare `import.meta.glob`
25
+ * with no `eager`/`query` option yields exactly `() => Promise<Module>`
26
+ * thunks, matching `IngestGame.load` byte-for-byte);
27
+ * - raw text (`?raw` query) for `entryHtml` files (iframe-reachable's `.js`
28
+ * module source / opaque-embed's `.html` document) — never
29
+ * imported/executed by the host, just read as a string;
30
+ * - `?url` asset globs for the manifest's `assets` map (relative paths ->
31
+ * Vite's built/served asset URL, same as the registry's per-game `?url`
32
+ * imports, just data-driven instead of one hand-written import per game).
33
+ */
34
+
35
+ import { loadGameManifest } from '@engine/manifest/load';
36
+ import type { IngestGame } from './types';
37
+
38
+ const manifestModules = import.meta.glob('./games/*/vgai.game.json', {
39
+ eager: true,
40
+ import: 'default',
41
+ }) as Record<string, unknown>;
42
+
43
+ const entryModules = import.meta.glob('./games/*/*.js') as Record<string, () => Promise<unknown>>;
44
+
45
+ const entryHtmlText = import.meta.glob('./games/*/*.{html,js}', {
46
+ eager: true,
47
+ query: '?raw',
48
+ import: 'default',
49
+ }) as Record<string, string>;
50
+
51
+ const assetUrls = import.meta.glob('./games/*/assets/**', {
52
+ eager: true,
53
+ query: '?url',
54
+ import: 'default',
55
+ }) as Record<string, string>;
56
+
57
+ const MANIFEST_KEY_RE = /^\.\/games\/([^/]+)\/vgai\.game\.json$/;
58
+
59
+ function folderIdFromManifestKey(key: string): string {
60
+ const match = MANIFEST_KEY_RE.exec(key);
61
+ if (!match?.[1]) {
62
+ throw new Error(`ingest discovery: unexpected manifest glob key "${key}"`);
63
+ }
64
+ return match[1];
65
+ }
66
+
67
+ function resolveAssetUrls(
68
+ folderId: string,
69
+ assets: Record<string, string>,
70
+ ): Record<string, string> {
71
+ const resolved: Record<string, string> = {};
72
+ for (const [pathSubstring, relPath] of Object.entries(assets)) {
73
+ const key = `./games/${folderId}/${relPath}`;
74
+ const url = assetUrls[key];
75
+ if (!url) {
76
+ throw new Error(
77
+ `ingest discovery: "${folderId}"'s asset "${relPath}" did not match any served asset ` +
78
+ `(looked for glob key "${key}" — available: ${Object.keys(assetUrls).join(', ')})`,
79
+ );
80
+ }
81
+ resolved[pathSubstring] = url;
82
+ }
83
+ return resolved;
84
+ }
85
+
86
+ type BaseFields = Pick<
87
+ IngestGame,
88
+ 'id' | 'name' | 'description' | 'assets' | 'domStubs' | 'captureTimeoutMs' | 'gameVersion'
89
+ >;
90
+
91
+ /** `shared`/`deduped`: a `load` thunk off the entry-module glob. */
92
+ function buildModuleLoadedGame(
93
+ folderId: string,
94
+ world: { entry: string | undefined },
95
+ strategy: 'shared' | 'deduped',
96
+ base: BaseFields,
97
+ ): IngestGame {
98
+ if (!world.entry) {
99
+ throw new Error(
100
+ `ingest discovery: "${folderId}" (strategy "${strategy}") has no \`entry\` — ` +
101
+ "shared/deduped ingest worlds need the game module's folder-relative path there.",
102
+ );
103
+ }
104
+ const key = `./games/${folderId}/${world.entry}`;
105
+ const load = entryModules[key];
106
+ if (!load) {
107
+ throw new Error(
108
+ `ingest discovery: "${folderId}"'s entry "${world.entry}" did not match any game ` +
109
+ `module (looked for glob key "${key}").`,
110
+ );
111
+ }
112
+ return { ...base, load };
113
+ }
114
+
115
+ /** `iframe-reachable`/`opaque-embed`: raw text off the entryHtml file glob. */
116
+ function buildFileBackedGame(
117
+ folderId: string,
118
+ entryHtml: string | undefined,
119
+ strategy: 'iframe-reachable' | 'opaque-embed',
120
+ base: BaseFields,
121
+ ): IngestGame {
122
+ if (!entryHtml) {
123
+ throw new Error(
124
+ `ingest discovery: "${folderId}" (strategy "${strategy}") has no \`entryHtml\`.`,
125
+ );
126
+ }
127
+ const key = `./games/${folderId}/${entryHtml}`;
128
+ const text = entryHtmlText[key];
129
+ if (text === undefined) {
130
+ throw new Error(
131
+ `ingest discovery: "${folderId}"'s entryHtml "${entryHtml}" did not match any file ` +
132
+ `(looked for glob key "${key}").`,
133
+ );
134
+ }
135
+ return strategy === 'iframe-reachable'
136
+ ? { ...base, iframeReachableSrc: text }
137
+ : { ...base, embedHtml: text };
138
+ }
139
+
140
+ function buildIngestGame(folderId: string, raw: unknown): IngestGame {
141
+ const manifest = loadGameManifest(raw);
142
+ if (manifest.worlds.length !== 1) {
143
+ throw new Error(
144
+ `ingest discovery: "${folderId}/vgai.game.json" must declare exactly one world ` +
145
+ `(found ${manifest.worlds.length}).`,
146
+ );
147
+ }
148
+ const world = manifest.worlds[0]!;
149
+ if (world.adapter.type !== 'ingest' || world.adapter.identity !== 'ingest-three') {
150
+ throw new Error(
151
+ `ingest discovery: "${folderId}/vgai.game.json"'s world must be a threejs { ingest } ` +
152
+ `world (found adapter identity "${world.adapter.identity}").`,
153
+ );
154
+ }
155
+ const { adapter } = world;
156
+
157
+ const base: BaseFields = {
158
+ id: world.id,
159
+ name: manifest.name,
160
+ description: world.description ?? '',
161
+ // D9 (docs/DECISIONS-PENDING.md §D9): the manifest's own `version`, so a
162
+ // later mount can compare a saved overlay's `authoredAgainst.gameVersion`
163
+ // against what's currently running (`overlay-report.ts`).
164
+ gameVersion: manifest.version,
165
+ ...(adapter.assets !== undefined ? { assets: resolveAssetUrls(folderId, adapter.assets) } : {}),
166
+ ...(adapter.domStubs !== undefined ? { domStubs: adapter.domStubs } : {}),
167
+ ...(adapter.captureTimeoutMs !== undefined
168
+ ? { captureTimeoutMs: adapter.captureTimeoutMs }
169
+ : {}),
170
+ };
171
+
172
+ switch (adapter.strategy) {
173
+ case 'shared':
174
+ case 'deduped':
175
+ return buildModuleLoadedGame(folderId, world, adapter.strategy, base);
176
+ case 'iframe-reachable':
177
+ case 'opaque-embed':
178
+ return buildFileBackedGame(folderId, adapter.entryHtml, adapter.strategy, base);
179
+ default: {
180
+ // Exhaustive per IngestStrategySchema's 4 members — a new strategy
181
+ // value added to the schema without a branch here is a compile error.
182
+ const exhaustive: never = adapter.strategy;
183
+ throw new Error(
184
+ `ingest discovery: "${folderId}" has an unhandled ingest strategy ${JSON.stringify(exhaustive)}.`,
185
+ );
186
+ }
187
+ }
188
+ }
189
+
190
+ let _cache: IngestGame[] | null = null;
191
+
192
+ /** Every in-tree ingest fixture game, discovered from `./games/*\/vgai.game.json`. */
193
+ export function discoverIngestGames(): IngestGame[] {
194
+ if (!_cache) {
195
+ _cache = Object.entries(manifestModules)
196
+ .map(([key, raw]) => buildIngestGame(folderIdFromManifestKey(key), raw))
197
+ .sort((a, b) => a.id.localeCompare(b.id));
198
+ }
199
+ return _cache;
200
+ }
201
+
202
+ /** Look up one discovered ingest game by id (`?ingest=<id>` resolution). */
203
+ export function getIngestGame(id: string): IngestGame | undefined {
204
+ return discoverIngestGames().find((g) => g.id === id);
205
+ }
@@ -0,0 +1,204 @@
1
+ /**
2
+ * In-tree world2d (PixiJS) ingest-game discovery — T6.1 slice 4
3
+ * (docs/MULTI-WORLD-DESIGN.md §1.E/§3 slice 4, world2d silo retirement). The
4
+ * pixi sibling of `discovery.ts`: replaces the deleted
5
+ * `world2d-demo/games/registry.ts` (`WORLD2D_INGEST_GAMES` array) — the three
6
+ * vgai-authored mini-games (bunnymark, flappy, tilemap-camera) each now carry
7
+ * their own `vgai.game.json` under `./games-2d/<id>/` (one `pixijs` world, a
8
+ * `{ ingest }` adapter); this module discovers them via `import.meta.glob`,
9
+ * mirroring `discovery.ts`'s mechanics exactly (see that file's doc comment
10
+ * for the full "why glob, not resolveIngest2DDescriptor" rationale — same
11
+ * reasoning applies here: these are IN-TREE fixtures Vite already knows about
12
+ * statically, not an external project folder).
13
+ *
14
+ * OVERLAY-KEY CONTINUITY (§1.E/§1.G): each fixture's world id is UNCHANGED
15
+ * from its old registry-era id (`bunnymark`/`flappy`/`tilemap-camera`, no
16
+ * `2d-` prefixing) — a pre-existing silo-era overlay would have lived at
17
+ * `.vgai/overlays/2d-<id>.json` (the retired `overlayPath2D`); this slice's
18
+ * unified overlay path is `.vgai/overlays/<id>.json` (`authoring/
19
+ * world-overlay.ts`, §1.G) — a DIFFERENT file, not a rename. Old `2d-*.json`
20
+ * overlays are dev-local artifacts, left in place, per §1.H's "no migration"
21
+ * note. `id` reachability (the id string itself) survives; the overlay FILE
22
+ * does not carry forward automatically.
23
+ *
24
+ * Track P (docs/PIXI-INGEST-LANDING-DESIGN.md §2, P1/P2) added a real
25
+ * `iframe-reachable` branch alongside `shared`/`deduped`: an externalized
26
+ * built bundle descriptor built straight from the manifest's six new
27
+ * `ingest.{bundleUrl,baseHref,assetBaseUrl,extraDeps,pixiModuleUrl,bodyHtml}`
28
+ * fields — see `buildIframeReachableGame2D` below. `opaque-embed` still
29
+ * throws HERE (no in-tree world2d fixture uses it, and this synchronous
30
+ * glob-discovery route has no runtime fetch to build a lazy `embedHtml`
31
+ * thunk from) — D-W2 (docs/WAVE4-FTUE-HARDENING-DESIGN.md) wires it for the
32
+ * manifest-driven external-folder route instead
33
+ * (`resolveIngest2DDescriptor`/`adapter-resolver.ts`), where `IngestGame2D`
34
+ * now DOES have an `embedHtml` field.
35
+ */
36
+
37
+ import { loadGameManifest } from '@engine/manifest/load';
38
+ import type { IngestGame2D } from '@engine/world2d/ingest2d';
39
+ import { resolveExtraDeps } from './host-namespace-registry';
40
+
41
+ const manifestModules = import.meta.glob('./games-2d/*/vgai.game.json', {
42
+ eager: true,
43
+ import: 'default',
44
+ }) as Record<string, unknown>;
45
+
46
+ const entryModules = import.meta.glob('./games-2d/*/*.ts') as Record<
47
+ string,
48
+ () => Promise<unknown>
49
+ >;
50
+
51
+ const MANIFEST_KEY_RE = /^\.\/games-2d\/([^/]+)\/vgai\.game\.json$/;
52
+
53
+ function folderIdFromManifestKey(key: string): string {
54
+ const match = MANIFEST_KEY_RE.exec(key);
55
+ if (!match?.[1]) {
56
+ throw new Error(`world2d ingest discovery: unexpected manifest glob key "${key}"`);
57
+ }
58
+ return match[1];
59
+ }
60
+
61
+ type BaseFields2D = Pick<IngestGame2D, 'id' | 'name' | 'description' | 'captureTimeoutMs'>;
62
+
63
+ function buildModuleLoadedGame2D(
64
+ folderId: string,
65
+ world: { entry: string | undefined },
66
+ strategy: 'shared' | 'deduped',
67
+ base: BaseFields2D,
68
+ ): IngestGame2D {
69
+ if (!world.entry) {
70
+ throw new Error(
71
+ `world2d ingest discovery: "${folderId}" (strategy "${strategy}") has no \`entry\` — ` +
72
+ "shared/deduped ingest-pixi worlds need the game module's folder-relative path there.",
73
+ );
74
+ }
75
+ const key = `./games-2d/${folderId}/${world.entry}`;
76
+ const load = entryModules[key];
77
+ if (!load) {
78
+ throw new Error(
79
+ `world2d ingest discovery: "${folderId}"'s entry "${world.entry}" did not match any game ` +
80
+ `module (looked for glob key "${key}").`,
81
+ );
82
+ }
83
+ return { ...base, tier: 'shared', load };
84
+ }
85
+
86
+ /**
87
+ * Build an `IngestGame2D` for the `iframe-reachable` strategy straight from
88
+ * the manifest's Track P fields (docs/PIXI-INGEST-LANDING-DESIGN.md §2) —
89
+ * `bundleUrl` is required by the schema's cross-field check for this
90
+ * strategy (exactly one of `bundleUrl`/`entryHtml`); `entryHtml`-only
91
+ * iframe-reachable worlds still have no builder here (mirrors the
92
+ * `resolveIngestDescriptor`/three-side gap: reading a raw HTML/inline-source
93
+ * file's contents needs a runtime fetch this synchronous builder doesn't do).
94
+ * `extraDeps`/`pixiModuleUrl` become LAZY thunks (`IngestGame2D.extraDeps`/
95
+ * `.pixiNamespace`) closing over the host-namespace registry / a dynamic
96
+ * `import()` — never resolved just to build this descriptor.
97
+ */
98
+ export function buildIframeReachableGame2D(
99
+ folderId: string,
100
+ adapter: {
101
+ bundleUrl: string | undefined;
102
+ baseHref: string | undefined;
103
+ assetBaseUrl: string | undefined;
104
+ extraDeps: string[] | undefined;
105
+ pixiModuleUrl: string | undefined;
106
+ bodyHtml: string | undefined;
107
+ },
108
+ base: BaseFields2D,
109
+ ): IngestGame2D {
110
+ if (!adapter.bundleUrl) {
111
+ throw new Error(
112
+ `world2d ingest discovery: "${folderId}" (strategy "iframe-reachable") has no \`bundleUrl\` ` +
113
+ '— only the externalized-bundle route is buildable here (an `entryHtml`-only iframe-' +
114
+ "reachable world needs a runtime file fetch this synchronous glob discovery doesn't do).",
115
+ );
116
+ }
117
+ const { bundleUrl, baseHref, assetBaseUrl, extraDeps, pixiModuleUrl, bodyHtml } = adapter;
118
+ return {
119
+ ...base,
120
+ tier: 'iframe-reachable',
121
+ bundleUrl,
122
+ ...(baseHref !== undefined ? { baseHref } : {}),
123
+ ...(assetBaseUrl !== undefined ? { assetBaseUrl } : {}),
124
+ ...(extraDeps !== undefined ? { extraDeps: () => resolveExtraDeps(extraDeps) } : {}),
125
+ ...(pixiModuleUrl !== undefined
126
+ ? { pixiNamespace: () => import(/* @vite-ignore */ pixiModuleUrl) }
127
+ : {}),
128
+ ...(bodyHtml !== undefined ? { bodyHtml } : {}),
129
+ };
130
+ }
131
+
132
+ function buildIngestGame2D(folderId: string, raw: unknown): IngestGame2D {
133
+ const manifest = loadGameManifest(raw);
134
+ if (manifest.worlds.length !== 1) {
135
+ throw new Error(
136
+ `world2d ingest discovery: "${folderId}/vgai.game.json" must declare exactly one world ` +
137
+ `(found ${manifest.worlds.length}).`,
138
+ );
139
+ }
140
+ const world = manifest.worlds[0]!;
141
+ if (world.adapter.type !== 'ingest' || world.adapter.identity !== 'ingest-pixi') {
142
+ throw new Error(
143
+ `world2d ingest discovery: "${folderId}/vgai.game.json"'s world must be a pixijs ` +
144
+ `{ ingest } world (found adapter identity "${world.adapter.identity}").`,
145
+ );
146
+ }
147
+ const { adapter } = world;
148
+
149
+ const base: BaseFields2D = {
150
+ id: world.id,
151
+ name: manifest.name,
152
+ description: world.description ?? '',
153
+ ...(adapter.captureTimeoutMs !== undefined
154
+ ? { captureTimeoutMs: adapter.captureTimeoutMs }
155
+ : {}),
156
+ };
157
+
158
+ switch (adapter.strategy) {
159
+ case 'shared':
160
+ case 'deduped':
161
+ return buildModuleLoadedGame2D(folderId, world, adapter.strategy, base);
162
+ case 'iframe-reachable':
163
+ return buildIframeReachableGame2D(folderId, adapter, base);
164
+ case 'opaque-embed':
165
+ // D-W2 (docs/WAVE4-FTUE-HARDENING-DESIGN.md) wired `IngestGame2D.embedHtml`
166
+ // for the manifest-driven external-folder route (`resolveIngest2DDescriptor`,
167
+ // reading `entryHtml`'s bytes through the project-static route as a lazy
168
+ // thunk) — but no in-tree world2d fixture uses opaque-embed, and this
169
+ // SYNCHRONOUS glob-discovery route has no runtime fetch to build that
170
+ // thunk from (mirrors the three side's identical `entryHtml`-only gap,
171
+ // `discovery.ts`'s own file-glob route DOES have one via `?raw`, since
172
+ // Vite already knows these files statically — porting that mechanism
173
+ // here is unclaimed, not attempted, since nothing needs it yet).
174
+ throw new Error(
175
+ `world2d ingest discovery: "${folderId}" declares strategy "${adapter.strategy}" — not ` +
176
+ 'buildable from this synchronous glob-discovery route (no in-tree world2d opaque-embed ' +
177
+ 'fixture exists yet; see resolveIngest2DDescriptor for the manifest-driven route).',
178
+ );
179
+ default: {
180
+ // Exhaustive per IngestStrategySchema's 4 members.
181
+ const exhaustive: never = adapter.strategy;
182
+ throw new Error(
183
+ `world2d ingest discovery: "${folderId}" has an unhandled ingest strategy ${JSON.stringify(exhaustive)}.`,
184
+ );
185
+ }
186
+ }
187
+ }
188
+
189
+ let _cache: IngestGame2D[] | null = null;
190
+
191
+ /** Every in-tree world2d ingest fixture game, discovered from `./games-2d/*\/vgai.game.json`. */
192
+ export function discoverIngestGames2D(): IngestGame2D[] {
193
+ if (!_cache) {
194
+ _cache = Object.entries(manifestModules)
195
+ .map(([key, raw]) => buildIngestGame2D(folderIdFromManifestKey(key), raw))
196
+ .sort((a, b) => a.id.localeCompare(b.id));
197
+ }
198
+ return _cache;
199
+ }
200
+
201
+ /** Look up one discovered world2d ingest game by id. */
202
+ export function getIngestGame2D(id: string): IngestGame2D | undefined {
203
+ return discoverIngestGames2D().find((g) => g.id === id);
204
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * ESM-entry resolution from a parsed `package.json` — the ONE pure-logic
3
+ * home for the minimal `exports`/`module`-field subset D-Z3
4
+ * (docs/EXTERNAL-FOLDER-INGEST-DESIGN.md's wave-3 addendum) and D-Z4
5
+ * (docs/WAVE3-ADAPTER-PLUMBING-DESIGN.md §1) both need.
6
+ *
7
+ * Two callers share this logic on purpose, per D-Z4's own design note ("put
8
+ * shared pure logic in one place if feasible rather than duplicating"):
9
+ * - `host-namespace-registry.ts`'s `probeProjectNodeModules` — editor-side,
10
+ * FETCHES a project's `node_modules/<spec>/package.json` through the
11
+ * `/project-game-static` dev-server route (browser-realm, no `node:fs`).
12
+ * - `packages/editor/scripts/run-bundle.ts`'s extraDeps pre-build
13
+ * validation — a plain Node CLI script, READS the same file straight off
14
+ * disk via `node:fs`.
15
+ * Both need byte-identical SEMANTICS (same exports-subset, same "found"
16
+ * dump for the CJS-only case) even though the I/O differs — this module is
17
+ * the fs-free, fetch-free core both wrap.
18
+ */
19
+
20
+ /** The one exports-map shape this fallback honors: a "." entry (string, or an
21
+ * { import | module | default } conditions object — the real shapes
22
+ * `@pixi/sound` and `gsap` ship, D-Z3's reference packages). Reads a single
23
+ * string condition off `conditions`, preferring `import`, then `module`,
24
+ * then `default` — the order D-Z3 specifies. */
25
+ function esmEntryFromConditions(conditions: Record<string, unknown>): string | null {
26
+ for (const key of ['import', 'module', 'default']) {
27
+ const v = conditions[key];
28
+ if (typeof v === 'string') return v;
29
+ }
30
+ return null;
31
+ }
32
+
33
+ /**
34
+ * Resolve the ESM entry PATH (package-folder-relative, e.g. "./lib/index.mjs")
35
+ * from a parsed `package.json`'s `exports` field, honoring the minimal subset
36
+ * this fallback supports (see module doc comment). Returns `null` (never
37
+ * throws) when the shape doesn't fit this subset or no ESM entry is declared
38
+ * at all — the CALLER decides how to report that (trap: exports-map parsing
39
+ * is a rabbit hole, so this stays an honest minimal subset, not a full
40
+ * resolver).
41
+ */
42
+ function esmEntryFromExportsField(exportsField: unknown): string | null {
43
+ if (typeof exportsField === 'string') return exportsField;
44
+ if (!exportsField || typeof exportsField !== 'object' || Array.isArray(exportsField)) {
45
+ return null;
46
+ }
47
+ const exportsObj = exportsField as Record<string, unknown>;
48
+ const dot = exportsObj['.'];
49
+ if (typeof dot === 'string') return dot;
50
+ if (dot && typeof dot === 'object' && !Array.isArray(dot)) {
51
+ return esmEntryFromConditions(dot as Record<string, unknown>);
52
+ }
53
+ if (dot === undefined) {
54
+ // No "." subpath key at all — a package with a SINGLE entry point may
55
+ // declare `exports` as a bare conditions map with no subpaths (no key
56
+ // starts with '.'). Anything else (real subpaths like "./foo") is
57
+ // outside this minimal subset — leave it to the caller's "found" dump.
58
+ const hasSubpathKeys = Object.keys(exportsObj).some((k) => k.startsWith('.'));
59
+ if (!hasSubpathKeys) return esmEntryFromConditions(exportsObj);
60
+ }
61
+ return null;
62
+ }
63
+
64
+ /**
65
+ * Resolve the ESM entry PATH from a parsed `package.json`: `exports["."]`
66
+ * (string or conditions object) first, then the top-level `module` field.
67
+ * `null` when neither is present/parseable (CJS-only or an unrecognized
68
+ * shape) — never throws.
69
+ */
70
+ export function resolveEsmEntryPath(pkg: Record<string, unknown>): string | null {
71
+ const fromExports = esmEntryFromExportsField(pkg['exports']);
72
+ if (fromExports) return fromExports;
73
+
74
+ const moduleField = pkg['module'];
75
+ if (typeof moduleField === 'string') return moduleField;
76
+
77
+ return null;
78
+ }
79
+
80
+ /** Outcome of probing one parsed `package.json` for a usable ESM entry. */
81
+ export type EsmEntryProbeResult =
82
+ | { readonly kind: 'cjs-only'; readonly found: Record<string, unknown> }
83
+ | { readonly kind: 'esm-entry'; readonly entryPath: string };
84
+
85
+ /**
86
+ * Probe an already-parsed `package.json` for a usable ESM entry, per the
87
+ * minimal subset this module supports. Package-folder-relative `entryPath`
88
+ * has any leading `./` stripped (callers join it onto their own base path/URL
89
+ * however they need). `found` on the CJS-only branch is the same
90
+ * introspection dump both callers surface in their distinct named error
91
+ * ("installed but CJS-only").
92
+ */
93
+ export function probePackageJsonForEsmEntry(pkg: Record<string, unknown>): EsmEntryProbeResult {
94
+ const esmEntryPath = resolveEsmEntryPath(pkg);
95
+ if (!esmEntryPath) {
96
+ return {
97
+ kind: 'cjs-only',
98
+ found: { main: pkg['main'], module: pkg['module'], exports: pkg['exports'] },
99
+ };
100
+ }
101
+ return { kind: 'esm-entry', entryPath: esmEntryPath.replace(/^\.\//, '') };
102
+ }