@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,401 @@
1
+ /**
2
+ * Reachable-three iframe host — the tier-2/3 middle case the founding docs flagged
3
+ * as the "named remaining edge" of bundled-game coverage (`cov-iframe-reachable`).
4
+ *
5
+ * A prebuilt bundle runs in its OWN iframe document (a separate realm), but — unlike
6
+ * the opaque embed floor — its `three` is REACHABLE: the host wires the iframe's
7
+ * `import 'three'` to a tiny blob module that re-exports `window.parent`'s host
8
+ * `three` namespace. Because the bundle therefore constructs HOST-realm `three`
9
+ * objects, the cross-realm canvas/document hazard that blocked naive sharing is
10
+ * sidestepped two ways:
11
+ * - every `Scene`/`Mesh`/`Camera` the bundle makes is a host-realm instance, so the
12
+ * editor's `instanceof THREE.*`, gizmo, raycast and inspector all work on them; and
13
+ * - the bundle creates + passes its OWN iframe canvas to `new WebGLRenderer({canvas})`,
14
+ * so the WebGL context lives in the iframe realm — no parent-document canvas adoption.
15
+ *
16
+ * The host's scene-capture trap (installed on the shared host `three`) then captures
17
+ * the bundle's renderer on its first frame exactly as in the shared-ESM path — the
18
+ * difference is purely WHERE the bundle's code runs (an iframe document) and HOW its
19
+ * `three` is resolved (importmap → blob re-export). The result is a real
20
+ * `IngestAuthoringAdapter` over the live `Object3D` tree: full in-realm editing.
21
+ *
22
+ * See `docs/ADAPTER-ARCHITECTURE.md` §2.3 (coverage table, reachable-three row).
23
+ */
24
+
25
+ import type { MountedGame } from '@engine/adapter';
26
+ import { installSceneCapture, type SceneCaptureHandle } from '@engine/adapter/ingest/scene-capture';
27
+ import * as THREE from 'three';
28
+ import type { EditorStore } from '../editor-store';
29
+ import { IngestAuthoringAdapter } from './ingest-authoring-adapter';
30
+ import type { IngestMount, MountIngestOptions } from './ingest-game-adapter';
31
+
32
+ /** Global key under which the host exposes its `three` namespace to the iframe realm. */
33
+ const HOST_THREE_KEY = '__VGAI_HOST_THREE';
34
+
35
+ /**
36
+ * Build a blob-module URL that re-exports the host `three` namespace from
37
+ * `window.parent[HOST_THREE_KEY]`. The iframe's importmap maps `three` to this URL,
38
+ * so the bundle's `import * as THREE from 'three'` yields the HOST instance.
39
+ */
40
+ function makeHostThreeReexportUrl(): string {
41
+ // Only valid JS identifier exports can be re-bound by name; `default` is handled
42
+ // separately. (three's public API is all identifier-named named exports.)
43
+ const ident = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
44
+ const keys = Object.keys(THREE).filter((k) => k !== 'default' && ident.test(k));
45
+ const body =
46
+ `const T = window.parent[${JSON.stringify(HOST_THREE_KEY)}];\n` +
47
+ `export default T;\n` +
48
+ keys.map((k) => `export const ${k} = T[${JSON.stringify(k)}];`).join('\n');
49
+ return URL.createObjectURL(new Blob([body], { type: 'text/javascript' }));
50
+ }
51
+
52
+ let _hostNsReexportSeq = 0;
53
+
54
+ /**
55
+ * D-W1 (docs/WAVE4-FTUE-HARDENING-DESIGN.md) — the three-side twin of
56
+ * `ingest-iframe-2d.ts`'s `makeHostNamespaceReexportUrl`: build a blob module
57
+ * re-exporting an ARBITRARY host namespace (a manifest's `extraDeps` beyond
58
+ * `three` itself — e.g. `gsap`), for the iframe's importmap. Duplicated
59
+ * (rather than imported from the world2d module) to keep this file's own
60
+ * module boundary independent of the pixi-flavored ingest-iframe-2d stack —
61
+ * both are ~10-line, dependency-free functions over `window`/`Blob`/`URL`.
62
+ */
63
+ function makeHostNamespaceReexportUrl(ns: Record<string, unknown>): string {
64
+ const key = `__vgaiHostThreeExtraDep_${_hostNsReexportSeq++}`;
65
+ (window as unknown as Record<string, unknown>)[key] = ns;
66
+ const ident = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
67
+ const keys = Object.keys(ns).filter((k) => k !== 'default' && ident.test(k));
68
+ const body =
69
+ `const T = window.parent[${JSON.stringify(key)}];\n` +
70
+ `export default (T && T.default !== undefined ? T.default : T);\n` +
71
+ keys.map((k) => `export const ${k} = T[${JSON.stringify(k)}];`).join('\n');
72
+ return URL.createObjectURL(new Blob([body], { type: 'text/javascript' }));
73
+ }
74
+
75
+ /**
76
+ * D-W1 — the three analog of pixi's `Assets.init` basePath wrap: a
77
+ * document.write iframe keeps the PARENT's `window.location`, so a relative
78
+ * asset request the bundle makes (through a `three` loader, which routes
79
+ * every URL through `DefaultLoadingManager`) would otherwise resolve against
80
+ * the parent editor page, not the served bundle directory. Absolute
81
+ * (has-a-scheme), root-relative (`/...`), `data:`, and `blob:` URLs already
82
+ * resolve correctly on their own and pass through unchanged; only bundle-
83
+ * relative requests get joined against `assetBaseUrl`. `assetBaseUrl` itself
84
+ * MUST already be absolute here (`new URL(url, assetBaseUrl)` requires an
85
+ * origin) — the ingest-mode.ts caller resolves a root-relative manifest
86
+ * value against the editor page's own URL BEFORE calling this (mirrors the
87
+ * pixi sibling's identical `assetBaseUrl` absolutization). Exported for
88
+ * direct unit testing (pure, no DOM).
89
+ */
90
+ export function makeAssetUrlModifier(assetBaseUrl: string): (url: string) => string {
91
+ const base = assetBaseUrl.endsWith('/') ? assetBaseUrl : `${assetBaseUrl}/`;
92
+ const isAlreadyResolvable = /^([a-z][a-z0-9+.-]*:)|^\//i;
93
+ return (url: string) => {
94
+ if (isAlreadyResolvable.test(url)) return url;
95
+ return new URL(url, base).toString();
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Assemble the reachable-three iframe document (D-W1): factored into a pure,
101
+ * directly-unit-testable function so both mount paths below — the original
102
+ * inline-source mount and the new prebuilt-bundle mount — build their HTML
103
+ * through ONE seam, and a unit test can pin the exact assembled string
104
+ * (importmap/base/bodyHtml/script tag) without a real iframe/DOM. Byte-
105
+ * compatible with the original inline-source document by construction: that
106
+ * mount passes the SAME `style`/`imports`/inline-script shape it always
107
+ * built inline, just through this shared assembler now.
108
+ */
109
+ export interface ReachableThreeDocumentOptions {
110
+ /** Bare-specifier -> URL importmap entries (MUST include `three`). */
111
+ imports: Record<string, string>;
112
+ /** `<base href>` for the iframe, when given. */
113
+ baseHref?: string | undefined;
114
+ /** Inline `<style>` block body (kept for the original inline-source mount's exact byte output). */
115
+ style?: string | undefined;
116
+ /** DOM injected into `<body>` before the script tag. */
117
+ bodyHtml?: string | undefined;
118
+ /** The game's own script — either an inline module body or a `<script type="module" src=...>`. */
119
+ script: { kind: 'inline'; source: string } | { kind: 'src'; url: string };
120
+ }
121
+
122
+ export function assembleReachableThreeDocument(opts: ReachableThreeDocumentOptions): string {
123
+ // Split the closing script token so this module's own bundling never sees a
124
+ // literal `</script>` (defensive; matches the world2d iframe mounts' convention).
125
+ const close = `</${'scr'}ipt>`;
126
+ const baseTag = opts.baseHref ? `<base href="${opts.baseHref}">` : '';
127
+ const styleTag = opts.style ? `<style>${opts.style}</style>` : '';
128
+ const importmapTag = `<script type="importmap">${JSON.stringify({ imports: opts.imports })}${close}`;
129
+ const bodyHtml = opts.bodyHtml ?? '';
130
+ const scriptTag =
131
+ opts.script.kind === 'inline'
132
+ ? `<script type="module">${opts.script.source}${close}`
133
+ : `<script type="module" src="${opts.script.url}">${close}`;
134
+ return (
135
+ `<!doctype html><html><head><meta charset="utf-8">${baseTag}${styleTag}${importmapTag}</head>` +
136
+ `<body>${bodyHtml}${scriptTag}</body></html>`
137
+ );
138
+ }
139
+
140
+ /**
141
+ * Mount a prebuilt bundle inside a same-origin iframe whose `three` is the host's,
142
+ * capture its live scene through the shared-`three` trap, and return an
143
+ * {@link IngestMount} with a real authoring adapter — identical contract to the
144
+ * shared-ESM {@link import('./ingest-game-adapter').mountIngestGame} path.
145
+ */
146
+ export async function mountIngestGameIframeReachable(
147
+ store: EditorStore,
148
+ gameModuleSrc: string,
149
+ gameContainer: HTMLElement,
150
+ opts: MountIngestOptions = {},
151
+ ): Promise<IngestMount> {
152
+ // Expose host `three` to the (same-origin) iframe realm.
153
+ (window as unknown as Record<string, unknown>)[HOST_THREE_KEY] = THREE;
154
+ const reexportUrl = makeHostThreeReexportUrl();
155
+
156
+ // Trap the host renderer prototype BEFORE the bundle constructs its renderer, so
157
+ // the bundle's (host-realm) renderer is captured and the editor's pre-existing one
158
+ // is not (its `render` is an own property set before the trap existed).
159
+ const capture: SceneCaptureHandle = installSceneCapture(THREE);
160
+
161
+ // Build a same-origin iframe and write the bundle document into it. Using
162
+ // document.write (not srcdoc) guarantees the about:blank doc inherits the parent
163
+ // origin, so `window.parent.__VGAI_HOST_THREE` and the blob: URL both resolve.
164
+ const iframe = document.createElement('iframe');
165
+ iframe.setAttribute('data-testid', 'ingest-reachable-frame');
166
+ iframe.style.cssText =
167
+ 'position:absolute;inset:0;width:100%;height:100%;border:0;background:#000;';
168
+ gameContainer.appendChild(iframe);
169
+
170
+ const doc = iframe.contentDocument;
171
+ if (!doc) throw new Error('iframe contentDocument unavailable (cross-origin?)');
172
+ doc.open();
173
+ doc.write(
174
+ assembleReachableThreeDocument({
175
+ imports: { three: reexportUrl },
176
+ style: 'html,body{margin:0;height:100%;overflow:hidden;background:#000}canvas{display:block}',
177
+ script: { kind: 'inline', source: gameModuleSrc },
178
+ }),
179
+ );
180
+ doc.close();
181
+
182
+ // The bundle renders on its first rAF; the host trap captures it.
183
+ const rt = await capture.waitForCapture(opts.captureTimeoutMs ?? 10_000);
184
+
185
+ const loop = {
186
+ pause: () => rt.renderer.setAnimationLoop(null),
187
+ resume: () => {
188
+ const cb = capture.getAnimationLoop(rt.renderer);
189
+ if (cb) rt.renderer.setAnimationLoop(cb);
190
+ },
191
+ };
192
+
193
+ const authoring = new IngestAuthoringAdapter(store, rt.scene, {
194
+ loop,
195
+ overlay: opts.overlay,
196
+ persist: opts.persist,
197
+ destination: opts.destination,
198
+ camera: rt.camera,
199
+ });
200
+
201
+ const cleanup = () => {
202
+ try {
203
+ rt.renderer.setAnimationLoop(null);
204
+ rt.renderer.dispose();
205
+ } catch {
206
+ /* ignore */
207
+ }
208
+ capture.uninstall();
209
+ URL.revokeObjectURL(reexportUrl);
210
+ iframe.remove();
211
+ delete (window as unknown as Record<string, unknown>)[HOST_THREE_KEY];
212
+ };
213
+
214
+ const mounted: MountedGame = {
215
+ kind: 'threejs',
216
+ scene: rt.scene,
217
+ camera: rt.camera,
218
+ drivesOwnLoop: true,
219
+ setPaused: (paused) => (paused ? loop.pause() : loop.resume()),
220
+ resize: (w, h) => rt.renderer.setSize(w, h, false),
221
+ dispose: cleanup,
222
+ authoring,
223
+ };
224
+
225
+ return { game: mounted, authoring, hostEl: iframe, capture, scene: rt.scene };
226
+ }
227
+
228
+ /**
229
+ * D-W1 (docs/WAVE4-FTUE-HARDENING-DESIGN.md) — options for
230
+ * {@link mountIngestGameIframeReachableBundle}, the prebuilt-EXTERNALIZED-
231
+ * bundle sibling of {@link mountIngestGameIframeReachable} above (which takes
232
+ * an INLINE module-source string). Mirrors pixi's
233
+ * `IframeReachableMultiOpts`/`mountIngestGame2DIframeReachableMulti`
234
+ * (`@engine/world2d/ingest-iframe-2d.ts`) field-for-field, minus the
235
+ * pixi-specific `pixiModuleUrl`/`inlineSrc` (three has no version-skew
236
+ * mechanism this wave — D-W7 — and this function is bundle-only, never
237
+ * inline).
238
+ */
239
+ export interface IframeReachableBundleOpts extends MountIngestOptions {
240
+ /** Load the game bundle from this absolute URL (a built, externalized-`three` multi-file game). */
241
+ bundleUrl: string;
242
+ /** `<base href>` for the iframe so the game's relative asset URLs resolve. */
243
+ baseHref?: string | undefined;
244
+ /** DOM injected into the iframe body before the bundle's script boots. */
245
+ bodyHtml?: string | undefined;
246
+ /** Bare-specifier -> host module-namespace map for this game's OTHER externalized deps beyond `three` (already-resolved, D-Z3 `namespaces`). */
247
+ extraDeps?: Record<string, Record<string, unknown>> | undefined;
248
+ /** Bare-specifier -> VERBATIM URL importmap entries (the D-Z3 project-node_modules fallback, already-resolved). */
249
+ extraDepUrls?: Record<string, string> | undefined;
250
+ /** Host-side `DefaultLoadingManager.setURLModifier` basePath remap (already absolutized by the caller). */
251
+ assetBaseUrl?: string | undefined;
252
+ }
253
+
254
+ /**
255
+ * Mount a PREBUILT, externalized-`three` bundle (its own `<script type="module"
256
+ * src=...>`, not an inline source string) inside the same same-origin
257
+ * reachable-three iframe recipe {@link mountIngestGameIframeReachable} uses:
258
+ * `installSceneCapture` trapped BEFORE the bundle's module executes (the
259
+ * importmap/capture setup lives in the SAME `document.write` call, so the
260
+ * deferred `type="module"` script only ever sees the trap already installed
261
+ * — verified against the existing inline path's identical ordering), an
262
+ * importmap resolving `three` (+ `extraDeps`) to host-namespace re-export
263
+ * blobs (or verbatim D-Z3 URLs), an optional `<base href>`/`bodyHtml`, and an
264
+ * `assetBaseUrl` asset adapter on the host `three`'s
265
+ * `DefaultLoadingManager.setURLModifier` (installed before the bundle runs,
266
+ * restored on dispose) — the three analog of pixi's `Assets.init` wrap.
267
+ *
268
+ * Capture-timeout handling is NOT the same as the inline-source mount above
269
+ * (that one still throws — this task keeps it byte-compatible on purpose):
270
+ * this bundle path DEGRADES to an embed-only `IngestMount` on a capture
271
+ * failure (three unreachable/mismatched/bundle never rendered in time),
272
+ * mirroring `mountIngestGame`'s own degrade shape — so
273
+ * `enterIngestModeWithGame`'s existing `mount.embedOnly` check (ingest-mode.ts)
274
+ * applies uniformly to this new route too (D-W1's explicit requirement).
275
+ */
276
+ export async function mountIngestGameIframeReachableBundle(
277
+ store: EditorStore,
278
+ gameContainer: HTMLElement,
279
+ opts: IframeReachableBundleOpts,
280
+ ): Promise<IngestMount> {
281
+ (window as unknown as Record<string, unknown>)[HOST_THREE_KEY] = THREE;
282
+ const created: string[] = [];
283
+ const threeReexportUrl = makeHostThreeReexportUrl();
284
+ created.push(threeReexportUrl);
285
+
286
+ const imports: Record<string, string> = { three: threeReexportUrl };
287
+ for (const [spec, ns] of Object.entries(opts.extraDeps ?? {})) {
288
+ const url = makeHostNamespaceReexportUrl(ns);
289
+ created.push(url);
290
+ imports[spec] = url;
291
+ }
292
+ // D-Z3 fallback: verbatim URL entries, no blob wrapping, merged in beside
293
+ // the host-namespace ones (mirrors `mountIngestGame2DIframeReachableMulti`'s
294
+ // identical merge).
295
+ for (const [spec, url] of Object.entries(opts.extraDepUrls ?? {})) imports[spec] = url;
296
+
297
+ // Trap the host renderer prototype BEFORE the bundle constructs its renderer
298
+ // (same ordering guarantee as the inline-source mount above).
299
+ const capture: SceneCaptureHandle = installSceneCapture(THREE);
300
+
301
+ // Host-side asset URL remap (asset adapter): install BEFORE the bundle
302
+ // runs, restore on dispose — pure host library configuration, never
303
+ // touches the game source.
304
+ let restoreUrlModifier: (() => void) | undefined;
305
+ if (opts.assetBaseUrl) {
306
+ THREE.DefaultLoadingManager.setURLModifier(makeAssetUrlModifier(opts.assetBaseUrl));
307
+ restoreUrlModifier = () => THREE.DefaultLoadingManager.setURLModifier((u) => u);
308
+ }
309
+
310
+ const iframe = document.createElement('iframe');
311
+ iframe.setAttribute('data-testid', 'ingest-reachable-frame');
312
+ iframe.style.cssText =
313
+ 'position:absolute;inset:0;width:100%;height:100%;border:0;background:#000;';
314
+ gameContainer.appendChild(iframe);
315
+
316
+ const cleanupHost = () => {
317
+ capture.uninstall();
318
+ restoreUrlModifier?.();
319
+ for (const u of created) URL.revokeObjectURL(u);
320
+ iframe.remove();
321
+ delete (window as unknown as Record<string, unknown>)[HOST_THREE_KEY];
322
+ };
323
+
324
+ const doc = iframe.contentDocument;
325
+ if (!doc) {
326
+ cleanupHost();
327
+ throw new Error('iframe contentDocument unavailable (cross-origin?)');
328
+ }
329
+ doc.open();
330
+ doc.write(
331
+ assembleReachableThreeDocument({
332
+ imports,
333
+ baseHref: opts.baseHref,
334
+ bodyHtml: opts.bodyHtml,
335
+ script: { kind: 'src', url: opts.bundleUrl },
336
+ }),
337
+ );
338
+ doc.close();
339
+
340
+ let rt: Awaited<ReturnType<SceneCaptureHandle['waitForCapture']>>;
341
+ try {
342
+ rt = await capture.waitForCapture(opts.captureTimeoutMs ?? 10_000);
343
+ } catch {
344
+ // three unreachable / bundle failed to render a host-realm frame within
345
+ // the timeout — degrade to embed-only (mountIngestGame's own degrade
346
+ // shape) rather than throwing, so the caller's existing
347
+ // `mount.embedOnly` check takes over (D-W1).
348
+ return {
349
+ embedOnly: true,
350
+ hostEl: iframe,
351
+ capture,
352
+ game: {
353
+ kind: 'threejs',
354
+ scene: new THREE.Scene(),
355
+ camera: new THREE.PerspectiveCamera(),
356
+ drivesOwnLoop: true,
357
+ resize: () => {},
358
+ dispose: cleanupHost,
359
+ },
360
+ };
361
+ }
362
+
363
+ const loop = {
364
+ pause: () => rt.renderer.setAnimationLoop(null),
365
+ resume: () => {
366
+ const cb = capture.getAnimationLoop(rt.renderer);
367
+ if (cb) rt.renderer.setAnimationLoop(cb);
368
+ },
369
+ };
370
+
371
+ const authoring = new IngestAuthoringAdapter(store, rt.scene, {
372
+ loop,
373
+ overlay: opts.overlay,
374
+ persist: opts.persist,
375
+ destination: opts.destination,
376
+ camera: rt.camera,
377
+ });
378
+
379
+ const cleanup = () => {
380
+ try {
381
+ rt.renderer.setAnimationLoop(null);
382
+ rt.renderer.dispose();
383
+ } catch {
384
+ /* ignore */
385
+ }
386
+ cleanupHost();
387
+ };
388
+
389
+ const mounted: MountedGame = {
390
+ kind: 'threejs',
391
+ scene: rt.scene,
392
+ camera: rt.camera,
393
+ drivesOwnLoop: true,
394
+ setPaused: (paused) => (paused ? loop.pause() : loop.resume()),
395
+ resize: (w, h) => rt.renderer.setSize(w, h, false),
396
+ dispose: cleanup,
397
+ authoring,
398
+ };
399
+
400
+ return { game: mounted, authoring, hostEl: iframe, capture, scene: rt.scene };
401
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Ingest overlay persistence — the honest serialize-back for an UNMODIFIED game.
3
+ *
4
+ * We never touch the game's source. Instead we persist the editor's edits beside
5
+ * it as an OVERLAY keyed by the object's stable structural-path id, and reapply it
6
+ * on the next ingest (after the game rebuilds its scene). This is the founding
7
+ * doc's "Overlay file" persistence mode (`docs/ADAPTER-ARCHITECTURE.md` §3.2,
8
+ * `docs/ADAPTER-VISION-ORIGINAL.md` "Patch File"): same `PersistenceProvider`
9
+ * interface as `.vscn`, different source of truth.
10
+ *
11
+ * The envelope TYPE + pure parsing (`OverlayFile`, `parseOverlayFile`, the
12
+ * legacy-flat-file upgrade) moved engine-side (T3.8, docs/DECISIONS-PENDING.md
13
+ * §D9) so the runtime ship-path applier (`@engine/adapter/ingest/overlay-applier.ts`)
14
+ * can share the SAME parsing rather than duplicating it — re-exported below
15
+ * unchanged so every existing import site here keeps working byte-for-byte.
16
+ *
17
+ * Storage transport (the "overlay unification" fix, docs/BACKBONE-TASKS.md's
18
+ * T3.3 close-out finding): `loadOverlay`/`saveOverlay` used to go straight
19
+ * through `getStorageBackend()`, which in server mode is `HttpStorage` —
20
+ * hard-rooted at `<projectRoot>/public/` — so the overlay actually landed at
21
+ * `<projectRoot>/public/.vgai/overlays/<id>.json`, contradicting `overlayPath()`'s
22
+ * "project-root-relative" doc comment and splitting the project into TWO
23
+ * `.vgai/` dirs (the editor-state/thumbnail/scaffold-baseline machinery, and
24
+ * `authoring/world-overlay.ts`'s manifest-world overlays, both use the
25
+ * project-root `.vgai/`). This module now follows the EXACT mode-branched
26
+ * pattern `world-overlay.ts` established (T6.1 §1.G):
27
+ * - browser mode (`getEditorMode() === 'browser'`): `getStorageBackend()`
28
+ * directly — OPFS/FSA already root at the TRUE project folder, no `public/`
29
+ * insertion exists there.
30
+ * - server mode: the dedicated `/__editor/vgai-file` GET/POST route pair
31
+ * (`editor-server.ts`), which reads/writes `<projectRoot>/.vgai/...`
32
+ * directly via `node:fs`, bypassing `HttpStorage`'s `public/` rooting.
33
+ * `overlayPath(gameId)` itself is UNCHANGED — it is also the ship path's
34
+ * default fetch URL (`overlay-applier.ts`).
35
+ *
36
+ * Back-compat read: `loadOverlay` tries the unified project-root location
37
+ * first, and — only if nothing is found there — falls back to the legacy
38
+ * `public/`-rooted location a pre-fix save would have written, so overlays
39
+ * saved before this fix keep loading. A hit on the legacy path logs one
40
+ * `console.warn` per `gameId` (never repeated) and is NEVER written back or
41
+ * deleted; the next `saveOverlay` for that id writes only the unified
42
+ * project-root location.
43
+ */
44
+
45
+ import {
46
+ emptyOverlayFile,
47
+ type IngestOverlay,
48
+ type IngestOverride,
49
+ OVERLAY_FORMAT_VERSION,
50
+ type OverlayFile,
51
+ overlayPath,
52
+ parseOverlayFile,
53
+ } from '@engine/adapter/ingest/overlay-file';
54
+ import { getEditorMode } from '../editor-mode';
55
+ import { getStorageBackend } from '../storage';
56
+
57
+ export type { IngestOverlay, IngestOverride, OverlayFile };
58
+ export { OVERLAY_FORMAT_VERSION, overlayPath };
59
+
60
+ const BASE = '/__editor';
61
+
62
+ /** `gameId`s we've already warned about finding only at the legacy location. */
63
+ const warnedLegacyIds = new Set<string>();
64
+
65
+ /** Read the unified project-root overlay location, or `undefined` if absent/unreadable. */
66
+ async function readProjectRoot(path: string): Promise<string | undefined> {
67
+ try {
68
+ if (getEditorMode() === 'browser') {
69
+ const backend = getStorageBackend();
70
+ if (!(await backend.exists(path))) return undefined;
71
+ return await backend.read(path);
72
+ }
73
+ const res = await fetch(`${BASE}/vgai-file?${new URLSearchParams({ path })}`);
74
+ if (!res.ok) return undefined;
75
+ return await res.text();
76
+ } catch {
77
+ return undefined;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Read the legacy `public/`-rooted overlay location, or `undefined` if
83
+ * absent/unreadable. Server mode's `getStorageBackend()` IS `HttpStorage`
84
+ * (hard-rooted at `<projectRoot>/public/`), so the plain `overlayPath(gameId)`
85
+ * string already resolves there; browser mode's backend roots at the TRUE
86
+ * project folder, so the legacy file must be addressed with an explicit
87
+ * `public/` prefix.
88
+ */
89
+ async function readLegacy(gameId: string): Promise<string | undefined> {
90
+ try {
91
+ const backend = getStorageBackend();
92
+ const legacyPath =
93
+ getEditorMode() === 'browser' ? `public/${overlayPath(gameId)}` : overlayPath(gameId);
94
+ if (!(await backend.exists(legacyPath))) return undefined;
95
+ return await backend.read(legacyPath);
96
+ } catch {
97
+ return undefined;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Load a game's saved overlay envelope, or an empty envelope if none exists /
103
+ * is unreadable anywhere. Always returns the {@link OverlayFile} shape —
104
+ * legacy flat-file content is transparently wrapped (`parseOverlayFile`,
105
+ * engine-side), whether found at the unified or the legacy location. Loads
106
+ * are read-only: a legacy hit is never rewritten or deleted here — only the
107
+ * next `saveOverlay` moves this `gameId` onto the unified location.
108
+ */
109
+ export async function loadOverlay(gameId: string): Promise<OverlayFile> {
110
+ const path = overlayPath(gameId);
111
+
112
+ const primary = await readProjectRoot(path);
113
+ if (primary !== undefined) {
114
+ try {
115
+ return parseOverlayFile(JSON.parse(primary) as unknown);
116
+ } catch {
117
+ // Unparseable at the unified location — fall through and try legacy too.
118
+ }
119
+ }
120
+
121
+ const legacy = await readLegacy(gameId);
122
+ if (legacy !== undefined) {
123
+ try {
124
+ const parsed = parseOverlayFile(JSON.parse(legacy) as unknown);
125
+ if (!warnedLegacyIds.has(gameId)) {
126
+ warnedLegacyIds.add(gameId);
127
+ const legacyPath = getEditorMode() === 'browser' ? `public/${path}` : path;
128
+ console.warn(
129
+ `ingest-overlay: '${gameId}' overlay found only at the legacy location (${legacyPath}), ` +
130
+ `not the unified project-root location (${path}). The next save will write ${path}.`,
131
+ );
132
+ }
133
+ return parsed;
134
+ } catch {
135
+ // Unparseable legacy content either — fall through to empty.
136
+ }
137
+ }
138
+
139
+ return emptyOverlayFile();
140
+ }
141
+
142
+ /**
143
+ * Persist a game's overlay — always as the D9 envelope (including the first
144
+ * save after loading a legacy flat file: the silent-upgrade path), and always
145
+ * to the unified project-root location (browser: `getStorageBackend()`
146
+ * directly; server: the dedicated `/__editor/vgai-file` POST route — never
147
+ * the legacy `public/`-rooted path, even if this `gameId`'s overlay was just
148
+ * loaded from there). `gameVersion` is the mounted game's manifest `version`
149
+ * (`ingest-mode.ts` threads it from either the manifest route's resolved
150
+ * manifest or the fixture route's `IngestGame.gameVersion`); omitted/`null`
151
+ * stamps `authoredAgainst.gameVersion: null` rather than fabricating one
152
+ * (anti-shim).
153
+ *
154
+ * `upstreamPin` (R5, docs/MASTER-ARCHITECTURE-REVIEW.md §4(c)) is the
155
+ * mounted game's `UPSTREAM.md` pin, when `ingest-mode.ts` could discover one
156
+ * for this session (see its `readUpstreamPinForActiveProject`). Deliberately
157
+ * OMITTED (not defaulted to `null`) when the caller doesn't pass it at all —
158
+ * that keeps every pre-R5 call site (and every existing test pinning the
159
+ * on-disk envelope shape byte-for-byte) writing the EXACT SAME JSON as
160
+ * before, with no `upstreamPin` key at all: that omission is itself the
161
+ * signal `./overlay-file.ts`'s `parseOverlayFile`/`overlay-report.ts` treat
162
+ * as "legacy, never checked" on a later load. Once a caller DOES pass a
163
+ * value (a string, or an explicit `null` for "looked, found nothing" —
164
+ * `ingest-mode.ts` always passes one of these two once it's R5-aware), the
165
+ * key is always written from then on.
166
+ */
167
+ export async function saveOverlay(
168
+ gameId: string,
169
+ overlay: IngestOverlay,
170
+ gameVersion?: string | null,
171
+ upstreamPin?: string | null,
172
+ ): Promise<void> {
173
+ const authoredAgainst: OverlayFile['authoredAgainst'] = { gameVersion: gameVersion ?? null };
174
+ if (upstreamPin !== undefined) authoredAgainst.upstreamPin = upstreamPin;
175
+ const file: OverlayFile = {
176
+ formatVersion: OVERLAY_FORMAT_VERSION,
177
+ authoredAgainst,
178
+ overrides: overlay,
179
+ };
180
+ const path = overlayPath(gameId);
181
+ const content = JSON.stringify(file, null, 2);
182
+
183
+ if (getEditorMode() === 'browser') {
184
+ await getStorageBackend().write(path, content);
185
+ return;
186
+ }
187
+ const res = await fetch(`${BASE}/vgai-file`, {
188
+ method: 'POST',
189
+ headers: { 'Content-Type': 'application/json' },
190
+ body: JSON.stringify({ path, content }),
191
+ });
192
+ if (!res.ok) throw new Error(`saveOverlay '${gameId}': HTTP ${res.status}`);
193
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Layered viewport picking (B4, D12) — "click anything you can see; topmost
3
+ * visible layer wins." Shell POLICY over the composite's public seams, kept a
4
+ * separate module (rather than living inline in `editor-viewport.ts`) so it
5
+ * stays lean AND unit-testable with plain fake adapters — no real DOM/canvas
6
+ * needed.
7
+ *
8
+ * This module speaks ONLY the public contract: `CompositeAuthoringAdapter
9
+ * .childAdapters()` (T0), the `AuthoringAdapter.pickable` provider (D12,
10
+ * `@engine/adapter`), and `world-session-state.ts`'s session toggles. It never
11
+ * reaches into any one child adapter's own private, format-specific model (no
12
+ * concrete-world-shape vocabulary of any kind here) — `instanceof
13
+ * CompositeAuthoringAdapter` is the ONE sanctioned type check (that class's
14
+ * own doc comment blesses exactly this use, mirroring
15
+ * `authoring/first-party-inspector/is-first-party-selection.ts`'s identical
16
+ * precedent).
17
+ *
18
+ * Walk order: the composite's NON-threejs children (react/pixi design-time
19
+ * layers), topmost-first by (install-time) manifest zOrder — `stackOrder`
20
+ * sorts bottom-to-top, so `.reverse()` gives topmost-first while preserving
21
+ * its own tie-break rule. The focused threejs world is appended LAST
22
+ * (canvas-bottom) regardless of its manifest zOrder — it always renders
23
+ * beneath every positioned design-time layer (`design-time-layers.ts`'s own
24
+ * doc comment: "the threejs canvas keeps its existing slot"), so it is
25
+ * correct for it to lose every pick to a layer that also claims the point.
26
+ *
27
+ * A layer that is eye-hidden or pick-locked (`world-session-state.ts`, B1) is
28
+ * skipped ENTIRELY — its `pickable.pick` is never even called. This is a
29
+ * SEPARATE mechanism from a layer's `interactive` toggle: an interactive
30
+ * layer's DOM has `pointer-events: auto`, so a real pointer event lands on the
31
+ * layer's own element and this walk (triggered only by the CANVAS's own
32
+ * `pointerup` listener) never runs at all for that event — mutually exclusive
33
+ * by construction, no code needed here for it.
34
+ *
35
+ * When the active adapter is NOT a composite (single-world project, or a
36
+ * non-composite override), this degrades to that one adapter's own
37
+ * `pickable.pick` (or `null` if it doesn't have one) — today's single-world
38
+ * behavior, unchanged.
39
+ */
40
+
41
+ import type { AuthoringAdapter } from '@engine/adapter';
42
+ import { stackOrder } from '@engine/runtime/input-router';
43
+ import type { EditorStore } from '../editor-store';
44
+ import { getActiveAuthoring } from './active-adapter';
45
+ import { CompositeAuthoringAdapter } from './composite-authoring-adapter';
46
+ import { isWorldHidden, isWorldPickLocked } from './world-session-state';
47
+
48
+ /** Topmost-first pick across every visible, pick-unlocked layer of the active
49
+ * (possibly composite) authoring adapter. Returns the winning node id, or
50
+ * `null` when nothing under `(clientX, clientY)` claims the point. */
51
+ export function pickTopmost(store: EditorStore, clientX: number, clientY: number): string | null {
52
+ const active = getActiveAuthoring(store);
53
+ if (!(active instanceof CompositeAuthoringAdapter)) {
54
+ return active.pickable?.pick(clientX, clientY) ?? null;
55
+ }
56
+
57
+ const children = active.childAdapters();
58
+ const layered = children.filter((c) => c.kind !== 'threejs');
59
+ const threejs = children.filter((c) => c.kind === 'threejs');
60
+
61
+ // `stackOrder` wants `{id, zOrder}` (`ClaimEntry`) — reuse `worldId` as its
62
+ // `id` (this walk has no separate id vocabulary of its own).
63
+ const layeredClaims = layered.map((c) => ({
64
+ id: c.worldId,
65
+ zOrder: c.zOrder,
66
+ worldId: c.worldId,
67
+ adapter: c.adapter,
68
+ }));
69
+
70
+ // Topmost-first: `stackOrder` is bottom-to-top (ascending zOrder, ties by
71
+ // original array order) — `.reverse()` flips it without disturbing its own
72
+ // tie-break rule (a later-declared same-zOrder layer still wins the tie,
73
+ // now by being checked FIRST instead of sorting last).
74
+ const order: Array<{ worldId: string; adapter: AuthoringAdapter }> = [
75
+ ...stackOrder(layeredClaims).reverse(),
76
+ // The focused threejs world paints/picks LAST — canvas-bottom, regardless
77
+ // of its own manifest zOrder (see module doc comment).
78
+ ...threejs,
79
+ ];
80
+
81
+ for (const { worldId, adapter } of order) {
82
+ if (isWorldHidden(worldId) || isWorldPickLocked(worldId)) continue;
83
+ const hit = adapter.pickable?.pick(clientX, clientY);
84
+ if (hit) return hit;
85
+ }
86
+ return null;
87
+ }