@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,466 @@
1
+ /**
2
+ * Composite sibling mounts — D-V2/D-V3/D-V4/D-V7
3
+ * (docs/WAVE5-MULTIWORLD-INGEST-DESIGN.md, F24 residual), extended by D-B1/
4
+ * D-B3 (docs/WAVE6-DOCTOR-DESIGN.md Slice B, wave-5 residual closure): when a
5
+ * manifest declares exactly one `{ ingest }` world alongside OTHER worlds
6
+ * ("siblings"), `ingest-mode.ts`'s composite routes mount the ingest world
7
+ * through their existing enter path and then call {@link mountIngestSiblings}
8
+ * here to mount every OTHER world in manifest order (already z-sorted by the
9
+ * loader), degrading loudly per world rather than silently ignoring them
10
+ * (the F24/F9 anti-pattern this closes):
11
+ *
12
+ * - `default-react` — a bare (no `<GameProvider>`) DOM layer, D-V3. There is
13
+ * no native `Game` beside an ingest world, so wrapping one would fabricate
14
+ * first-party data (anti-shim) — the sibling's own `useGameState`/
15
+ * `useGame` calls are simply unavailable, and this says so once per world.
16
+ * Has no loop at all, so its `SiblingMount.setPaused` stays `undefined`
17
+ * (D-B1) — never a fabricated no-op.
18
+ * - `{ module }` (any kind) OR a native `default-three`/`default-pixi` world
19
+ * (D-B3 — the wave-5 residual this closes: a second `{ ingest }` world is
20
+ * structurally unreachable here, D-V2 already fails the whole route
21
+ * before any sibling loop runs, so `resolveAllWorlds`'s one-element call
22
+ * below never hits its `MULTI_WORLD_ALLOWED_IDENTITIES` guard, which only
23
+ * applies for `worlds.length > 1`) — a SCOPED single-world runtime, D-V4:
24
+ * the exact resolve+mount `module-mode.ts`'s `enterModuleModeWorldsPath`
25
+ * proves out, reused via `mountModuleWorldRuntime`, but with NONE of that
26
+ * route's global side effects (no `setActiveAuthoring`/`setActiveSystems`,
27
+ * no viewport-tab switch, no `__vgaiModule` hook, no play-surface writes).
28
+ * Its `SiblingMount.setPaused` (D-B1) is wired straight from the scoped
29
+ * `GameSession.pause()`/`.resume()` `mountModuleWorldRuntime` hands back —
30
+ * the SAME honest, already-existing capability `play-mode.ts`'s own
31
+ * `pausePlayMode`/`resumePlayMode` call on the PRIMARY route's session
32
+ * (`create-runtime.ts`'s `Game.play.pause()`/`.resume()`, gated per
33
+ * `pausable` world) — never a fabricated pause.
34
+ * - anything else — `rejectUnmountableSibling`'s named, loud non-goal for a
35
+ * genuinely unroutable shape (there is none known today; every currently
36
+ * resolvable identity is handled above): a per-world mount-failure entry,
37
+ * never silence.
38
+ *
39
+ * A failing sibling NEVER unmounts the ingest world: each failure is caught
40
+ * individually and appended to the (D-V5) multi-entry mount-failure report;
41
+ * the loop continues to the next sibling. Every successfully-mounted
42
+ * sibling's handle is returned so `ingest-mode.ts` can store it on the
43
+ * unified `_active` slot, dispose it (reverse order) on exit, and fan pause/
44
+ * resume out to it (D-B2, `getIngestPlayControl()`).
45
+ */
46
+
47
+ import type { AuthoringAdapter } from '@engine/adapter';
48
+ import type { ResolvedGameManifest, ResolvedWorldEntry } from '@engine/manifest/load';
49
+ import type { Overlay2D } from '@engine/world2d/authoring-2d';
50
+ import { createPhysics2DRegistry } from '@engine/world2d/physics2d-registry';
51
+ import { createPhysicsAdapter2D } from '@engine/world2d/system-adapters-2d';
52
+ import { Component, createElement, type ReactNode } from 'react';
53
+ import { flushSync } from 'react-dom';
54
+ import { createRoot } from 'react-dom/client';
55
+ import { resolveReactWorldEntryComponent } from './adapter-resolver';
56
+ import { makeEmbedOnlyAdapter } from './authoring/embed-only-adapter';
57
+ import { IngestAuthoringAdapter } from './authoring/ingest-authoring-adapter';
58
+ import type { IngestOverlay } from './authoring/ingest-overlay';
59
+ import { addMountFailureReport, formatMountFailureMessage } from './authoring/mount-failure-report';
60
+ import {
61
+ ReactDomAuthoringAdapter,
62
+ type ReactDomOverlay,
63
+ } from './authoring/react-dom-authoring-adapter';
64
+ import { ReactWorldAuthoringAdapter, walkOidTree } from './authoring/react-world-authoring-adapter';
65
+ import { unregisterUndoAdapter } from './authoring/undo-timeline';
66
+ import { loadWorldOverlay, saveWorldOverlay, worldOverlayPath } from './authoring/world-overlay';
67
+ import { World2DAuthoringAdapter } from './authoring/world2d-authoring-adapter';
68
+ import { editorConsole } from './editor-console';
69
+ import type { EditorStore } from './editor-store';
70
+ import { mountModuleWorldRuntime, withStoreSelection } from './module-mode';
71
+ import { createHttpSourceWriteBackend } from './ui-source/source-write-backend';
72
+
73
+ /** One mounted sibling world's handle — stored on `ingest-mode.ts`'s unified
74
+ * `_active` slot and disposed (reverse order) by `exitActiveIngest()`. */
75
+ export interface SiblingMount {
76
+ readonly worldId: string;
77
+ /**
78
+ * D-B1 (docs/WAVE6-DOCTOR-DESIGN.md Slice B): an OPTIONAL, honest pause
79
+ * capability — present only when this sibling's own mounted session
80
+ * exposes a REAL pause (today: every `mountScopedRuntimeSibling` kind, via
81
+ * its scoped `GameSession.pause()`/`.resume()`). Absent (never a fake
82
+ * no-op) for a kind with no loop to gate at all — the bare `default-react`
83
+ * sibling. `getIngestPlayControl()` (`ingest-mode.ts`, D-B2) calls this
84
+ * `?.`-optionally after its own primary-surface call, mirroring the
85
+ * reverse-order dispose fan-out below.
86
+ */
87
+ setPaused?(paused: boolean): void;
88
+ /**
89
+ * D-E1 (docs/WAVE8-SIBLING-AUTHORING-DESIGN.md): an OPTIONAL, HONEST
90
+ * authoring surface for this sibling — present iff `store` was supplied to
91
+ * {@link mountIngestSiblings} (every production caller supplies one; the
92
+ * parameter stays optional only so the one pre-existing test that calls
93
+ * this module directly without a store keeps compiling/running unchanged).
94
+ * When present, it is NEVER a fabricated stand-in:
95
+ * - a scoped-runtime sibling (`{ module }`, or a native
96
+ * `default-three`/`default-pixi` world) gets its OWN mounted
97
+ * `AuthoringAdapter` (`mounted.authoring`), store-selection-wrapped
98
+ * (`withStoreSelection`) — or, when it exposes none, the SAME named
99
+ * embed-only floor `enterModuleModeWorldsPath` uses
100
+ * (`makeEmbedOnlyAdapter`), so the world still appears in the hierarchy
101
+ * as a group with its honest label, never silently omitted.
102
+ * - a bare `default-react` sibling gets a {@link ReactWorldAuthoringAdapter}
103
+ * over its own layer DOM root — an un-instrumented entry (no
104
+ * `data-oid`) yields an EMPTY subtree under its group node, never
105
+ * fabricated nodes.
106
+ * `ingest-mode.ts`'s composite routes promote to a `CompositeAuthoringAdapter`
107
+ * over the primary + every sibling's `authoring` once any sibling mounts
108
+ * (D-E2); a `SiblingMount` with no `authoring` (store omitted) simply
109
+ * cannot participate in that promotion.
110
+ */
111
+ readonly authoring?: AuthoringAdapter;
112
+ dispose(): void;
113
+ }
114
+
115
+ /**
116
+ * Minimal class-component error boundary (D-V3): a react sibling mounts with
117
+ * NO `<GameProvider>` (there is no native `Game` beside an ingest world), so
118
+ * a sibling component that calls `useGame`/`useGameState` anyway throws
119
+ * synchronously at first render — an error boundary is the only way React
120
+ * lets a HOST catch that (there is no hook equivalent), and it lets ONE
121
+ * misbehaving sibling fail loudly (via `onCaught`) without ever throwing out
122
+ * of `root.render()` uncaught or crashing any OTHER sibling / the ingest
123
+ * world itself.
124
+ */
125
+ class SiblingErrorBoundary extends Component<
126
+ { onCaught: (error: unknown) => void; children: ReactNode },
127
+ { crashed: boolean }
128
+ > {
129
+ override state = { crashed: false };
130
+ static getDerivedStateFromError(): { crashed: boolean } {
131
+ return { crashed: true };
132
+ }
133
+ override componentDidCatch(error: unknown): void {
134
+ this.props.onCaught(error);
135
+ }
136
+ override render(): ReactNode {
137
+ return this.state.crashed ? null : this.props.children;
138
+ }
139
+ }
140
+
141
+ /** Position/size/z-order every sibling layer shares — same absolute-fill
142
+ * convention `create-runtime.ts`'s own react-world DOM layer uses
143
+ * (`mountOneReactWorld`), so a sibling stacks exactly like a native world
144
+ * would have. `pointerEvents` differs per sibling kind below (module worlds
145
+ * own real canvases that need real hit-testing; the react sibling's layer
146
+ * stays `none` by the same HUD-overlay convention `mountOneReactWorld` uses,
147
+ * its rendered content opts back in per-element). */
148
+ function styleSiblingLayer(
149
+ layer: HTMLElement,
150
+ world: ResolvedWorldEntry,
151
+ pointerEvents: string,
152
+ ): void {
153
+ layer.style.position = 'absolute';
154
+ layer.style.top = '0';
155
+ layer.style.left = '0';
156
+ layer.style.width = '100%';
157
+ layer.style.height = '100%';
158
+ layer.style.zIndex = String(world.zOrder ?? 0);
159
+ layer.style.pointerEvents = pointerEvents;
160
+ }
161
+
162
+ /**
163
+ * D-V3: mount a `default-react` sibling as a bare DOM layer — no
164
+ * `<GameProvider>` wrap (anti-shim: there is no native `Game` to hand it).
165
+ * Reuses `adapter-resolver.ts`'s exact entry-loading step
166
+ * (`resolveReactWorldEntryComponent` — the same D4 `scene`-forbidden/
167
+ * `entry`-required checks and default-export validation
168
+ * `resolveDefaultReactAdapter` itself uses) rather than duplicating it.
169
+ *
170
+ * If the entry component throws at mount/first render (most commonly: it
171
+ * calls `useGame`/`useGameState` with no provider in the tree), the error
172
+ * boundary above catches it, this function removes the layer it just
173
+ * created, and RETHROWS so the caller's uniform per-sibling catch
174
+ * (`mountOneIngestSibling`'s caller, {@link mountIngestSiblings}) can turn it
175
+ * into a named mount-failure-report entry for THIS world, exactly like any
176
+ * other sibling failure — the ingest world stays mounted regardless.
177
+ */
178
+ async function mountDefaultReactSibling(
179
+ world: ResolvedWorldEntry,
180
+ gameContainer: HTMLElement,
181
+ projectRoot: string,
182
+ store: EditorStore | undefined,
183
+ ): Promise<SiblingMount> {
184
+ const Entry = await resolveReactWorldEntryComponent(world, projectRoot);
185
+
186
+ const layer = document.createElement('div');
187
+ styleSiblingLayer(layer, world, 'none');
188
+ gameContainer.appendChild(layer);
189
+
190
+ // D-V3: one structured note per world naming the degrade — never silent
191
+ // about the missing bridge.
192
+ editorConsole.log(
193
+ `react sibling "${world.id}": no native Game beside an ingest world — useGameState unavailable`,
194
+ 'ingest',
195
+ );
196
+
197
+ const root = createRoot(layer);
198
+ let caught: unknown = null;
199
+ // `flushSync` forces the initial mount (render + commit + error-boundary
200
+ // recovery, if the entry throws) to complete SYNCHRONOUSLY, so `caught` is
201
+ // reliably populated by the time this call returns — react-dom's default
202
+ // scheduler does not guarantee that for a bare `root.render()` call (a
203
+ // production API, not a test-only affordance: this host needs the throw/
204
+ // no-throw outcome immediately, to decide whether to keep the layer or
205
+ // tear it down and report a named failure).
206
+ flushSync(() => {
207
+ root.render(
208
+ createElement(SiblingErrorBoundary, {
209
+ onCaught: (error: unknown) => {
210
+ caught = error;
211
+ },
212
+ children: createElement(Entry),
213
+ }),
214
+ );
215
+ });
216
+
217
+ if (caught !== null) {
218
+ root.unmount();
219
+ layer.remove();
220
+ throw caught instanceof Error ? caught : new Error(String(caught));
221
+ }
222
+
223
+ // D-M1 (docs/WAVE11-REACT-SIBLING-DOM-FALLBACK-DESIGN.md): once mounted,
224
+ // probe the REAL committed layer with `walkOidTree` — a partially-stamped
225
+ // tree counts as stamped (the walk's transparent-skip contract already
226
+ // handles instrumentation gaps), so this is a single "any node at all?"
227
+ // check, not a completeness one. A non-empty tree keeps the exact D-E1
228
+ // construction below (a STAMPED first-party entry — the sanctioned JSX
229
+ // write-back seam). A genuinely EMPTY tree (no `data-oid` reachable at
230
+ // all — e.g. an ingest-REACT sibling swept into the D-Y4 foreign-game
231
+ // exclusion, or an entry that rendered nothing yet) falls back to
232
+ // `ReactDomAuthoringAdapter`: an overlay-only surface over the live DOM,
233
+ // no `SourceWriteBackend` of any kind — never a write seam over source the
234
+ // stamping pipeline declined to instrument. Only computed when a store was
235
+ // supplied (see `SiblingMount.authoring`'s doc comment) — never fabricated.
236
+ let authoring: ReactWorldAuthoringAdapter | ReactDomAuthoringAdapter | undefined;
237
+ if (store) {
238
+ const stamped = walkOidTree(layer).nodes.size > 0;
239
+ authoring = stamped
240
+ ? // D-E1: the exact play-mode react-branch construction (`play-mode.ts`
241
+ // `installMultiWorldAuthoring` :341-347) over THIS sibling's own layer
242
+ // DOM root — no `<GameProvider>`/`ReactWorldHost` required.
243
+ new ReactWorldAuthoringAdapter(layer, store, {
244
+ ...(import.meta.env.DEV ? { writeBackend: createHttpSourceWriteBackend() } : {}),
245
+ })
246
+ : new ReactDomAuthoringAdapter(layer, store, {
247
+ overlay: await loadWorldOverlay<ReactDomOverlay>(world.id),
248
+ persist: (o) => saveWorldOverlay(world.id, o),
249
+ destination: worldOverlayPath(world.id),
250
+ });
251
+ }
252
+
253
+ return {
254
+ worldId: world.id,
255
+ ...(authoring ? { authoring } : {}),
256
+ dispose(): void {
257
+ // D-M3: both branches above self-register with the global undo
258
+ // timeline at construction (`registerUndoAdapter`, in each adapter's
259
+ // own constructor) — drop it here so any of its entries still on the
260
+ // global stack are dropped (with a warn) rather than misapplied to a
261
+ // torn-down layer, mirroring every other undo-registering adapter's
262
+ // teardown in this codebase (`disposeReactSession`/`disposeThreejsSession`
263
+ // in `ingest-mode.ts`, the scene-UI child's `UIEditStore` unregister
264
+ // in `edit-mode-authoring.ts`'s `exitEditModeAuthoring`).
265
+ if (authoring) unregisterUndoAdapter(authoring);
266
+ root.unmount();
267
+ layer.remove();
268
+ },
269
+ };
270
+ }
271
+
272
+ /**
273
+ * D-V4/D-B3: mount a `{ module }` sibling (any kind) OR a native
274
+ * `default-three`/`default-pixi` sibling through the SCOPED single-world
275
+ * runtime `module-mode.ts`'s `mountModuleWorldRuntime` provides — the exact
276
+ * resolve+mount `enterModuleModeWorldsPath` performs, reused here with NONE
277
+ * of that route's global side effects: no `setActiveAuthoring`/
278
+ * `setActiveSystems`, no viewport-tab switch, no `__vgaiModule` hook, no
279
+ * mount-failure-report clear. Mounts into its OWN layer div (never straight
280
+ * into the shared `gameContainer` the way the primary/single-world routes
281
+ * do) so its internal canvas/DOM stacking never collides with the ingest
282
+ * world's own canvas/DOM layer already in that container.
283
+ *
284
+ * `mountModuleWorldRuntime` is kind-agnostic BY CONSTRUCTION here, not just
285
+ * for `{ module }` adapters: it calls `resolveAllWorlds` with a ONE-element
286
+ * `worlds` array, and `resolveAllWorlds`'s `MULTI_WORLD_ALLOWED_IDENTITIES`
287
+ * scope guard only fires for `worlds.length > 1` — so `resolveWorldAdapter`'s
288
+ * full identity switch (including `default-three`/`default-pixi`) is reached
289
+ * directly, unmodified, with the guard never consulted (D-B3's ground-truth
290
+ * finding; `MULTI_WORLD_ALLOWED_IDENTITIES` itself stays byte-identical).
291
+ *
292
+ * D-B1: the returned `SiblingMount.setPaused` calls the scoped
293
+ * `GameSession.pause()`/`.resume()` `mountModuleWorldRuntime` hands back —
294
+ * the SAME honest, already-existing capability `play-mode.ts`'s own
295
+ * `pausePlayMode`/`resumePlayMode` call on the PRIMARY route's session
296
+ * (`create-runtime.ts`'s `Game.play.pause()`/`.resume()`, gated per
297
+ * `pausable` world within this scoped, single-world `Game`) — never a
298
+ * fabricated no-op.
299
+ *
300
+ * D-G1 (docs/WAVE9-SIBLING-PERSISTENCE-DOCTOR-DESIGN.md §1, closing the
301
+ * wave-8 residual this doc comment used to name): the mounted world's
302
+ * authoring surface converges with play-mode's own multi-world construction
303
+ * (`play-mode.ts`'s `installMultiWorldAuthoring`, verbatim per branch):
304
+ * 1. `mounted.authoring` present (a `{ module }` adapter that ships its own
305
+ * surface, with its own persistence if any) — kept byte-for-byte,
306
+ * store-selection-wrapped, exactly as before this slice. NEVER wrapped
307
+ * in an editor overlay: the module's own choice wins.
308
+ * 2. Else `worldInstance.kind === 'threejs'` (a native `default-three`
309
+ * sibling, or a `{ module }` sibling that mounted a threejs world with no
310
+ * authoring of its own) — a real `IngestAuthoringAdapter` over
311
+ * `worldInstance.threeRoot()`, with REAL `.vgai/overlays/<world-id>.json`
312
+ * persistence (D-G2: the SAME overlay identity play-mode would use for
313
+ * this same world id — one persisted document either route converges
314
+ * on). D-G4: `loadWorldOverlay` already returns `{}` on miss/error — no
315
+ * new swallowing added here.
316
+ * 3. Else `kind === 'pixijs'` — the pixijs analog through
317
+ * `World2DAuthoringAdapter`, physics wired via
318
+ * `createPhysicsAdapter2D(worldInstance.physics2d ??
319
+ * createPhysics2DRegistry())` exactly like play-mode's own branch.
320
+ * 4. Else (no mounted.authoring, not a first-party-authorable kind) — the
321
+ * named `makeEmbedOnlyAdapter` floor, unchanged.
322
+ * D-G3: `composite-authoring-adapter.ts`'s persistence routing (OR-dirty,
323
+ * per-child save, `' + '`-joined destinations) needs NO changes — once this
324
+ * function hands back an adapter with `capabilities.persist` +
325
+ * `persistence`, the composite's existing routing picks it up automatically.
326
+ */
327
+ async function mountScopedRuntimeSibling(
328
+ manifest: ResolvedGameManifest,
329
+ world: ResolvedWorldEntry,
330
+ gameContainer: HTMLElement,
331
+ projectRoot: string,
332
+ store: EditorStore | undefined,
333
+ ): Promise<SiblingMount> {
334
+ const layer = document.createElement('div');
335
+ styleSiblingLayer(layer, world, 'auto');
336
+ gameContainer.appendChild(layer);
337
+
338
+ try {
339
+ const w = gameContainer.clientWidth || 800;
340
+ const h = gameContainer.clientHeight || 600;
341
+ const { session } = await mountModuleWorldRuntime(manifest, world, projectRoot, layer, w, h);
342
+ // D-G1: `session.game.worlds[0]` is the SAME `WorldInstance` shape
343
+ // play-mode iterates (`kind`, `threeRoot()`, `pixiRoot()`, `physics2d`) —
344
+ // a single-element `resolveAllWorlds` call always registers exactly one.
345
+ const worldInstance = session.game.worlds[0]!;
346
+ const mountedAuthoring = worldInstance.mounted.authoring;
347
+ let authoring: AuthoringAdapter | undefined;
348
+ if (store) {
349
+ if (mountedAuthoring) {
350
+ // D-E1 (byte-stable, docs/WAVE8-SIBLING-AUTHORING-DESIGN.md): the
351
+ // module's own surface (and its own persistence, if any) wins —
352
+ // never wrap an editor overlay around it.
353
+ authoring = withStoreSelection(mountedAuthoring, store);
354
+ } else if (worldInstance.kind === 'threejs') {
355
+ authoring = new IngestAuthoringAdapter(store, worldInstance.threeRoot(), {
356
+ overlay: await loadWorldOverlay<IngestOverlay>(worldInstance.id),
357
+ persist: (o) => saveWorldOverlay(worldInstance.id, o),
358
+ destination: worldOverlayPath(worldInstance.id),
359
+ });
360
+ } else if (worldInstance.kind === 'pixijs') {
361
+ const physics = createPhysicsAdapter2D(
362
+ worldInstance.physics2d ?? createPhysics2DRegistry(),
363
+ );
364
+ authoring = new World2DAuthoringAdapter(worldInstance.pixiRoot(), physics, store, {
365
+ overlay: await loadWorldOverlay<Overlay2D>(worldInstance.id),
366
+ persist: (o) => saveWorldOverlay(worldInstance.id, o),
367
+ destination: worldOverlayPath(worldInstance.id),
368
+ });
369
+ } else {
370
+ authoring = makeEmbedOnlyAdapter(store, `${world.id} (no authoring surface)`);
371
+ }
372
+ }
373
+ return {
374
+ worldId: world.id,
375
+ ...(authoring ? { authoring } : {}),
376
+ setPaused(paused: boolean): void {
377
+ if (paused) session.pause();
378
+ else session.resume();
379
+ },
380
+ dispose(): void {
381
+ session.stop();
382
+ layer.remove();
383
+ },
384
+ };
385
+ } catch (err) {
386
+ layer.remove();
387
+ throw err;
388
+ }
389
+ }
390
+
391
+ /**
392
+ * D-V7 (updated by D-B3): a sibling reaching here is neither `default-react`
393
+ * nor routable through the scoped runtime above (`{ module }` of any kind,
394
+ * or a native `default-three`/`default-pixi` world) — a genuinely unroutable
395
+ * shape. No such shape is known to exist today (every currently-resolvable
396
+ * adapter identity is handled above); this is a named, loud non-goal for a
397
+ * hypothetical future one, never silently ignored (the F24 anti-pattern) and
398
+ * never half-mounted. (A second `{ ingest }` world is structurally
399
+ * unreachable here — D-V2's route-level guard already fails the whole
400
+ * composite before any sibling loop runs.)
401
+ */
402
+ function rejectUnmountableSibling(world: ResolvedWorldEntry): never {
403
+ throw new Error(
404
+ `sibling world "${world.id}" (kind: ${world.kind}, adapter identity: ` +
405
+ `"${world.adapter.identity}") has no known composite-sibling mount path — every ` +
406
+ 'currently-resolvable identity (default-three/default-pixi/default-react/module) is ' +
407
+ 'routed above; this is reached only for a genuinely unroutable shape ' +
408
+ '(docs/WAVE5-MULTIWORLD-INGEST-DESIGN.md D-V7, updated by docs/WAVE6-DOCTOR-DESIGN.md D-B3).',
409
+ );
410
+ }
411
+
412
+ async function mountOneIngestSibling(
413
+ manifest: ResolvedGameManifest,
414
+ world: ResolvedWorldEntry,
415
+ gameContainer: HTMLElement,
416
+ projectRoot: string,
417
+ store: EditorStore | undefined,
418
+ ): Promise<SiblingMount> {
419
+ if (
420
+ world.adapter.type === 'module' ||
421
+ world.adapter.identity === 'default-three' ||
422
+ world.adapter.identity === 'default-pixi'
423
+ ) {
424
+ return mountScopedRuntimeSibling(manifest, world, gameContainer, projectRoot, store);
425
+ }
426
+ if (world.adapter.identity === 'default-react') {
427
+ return mountDefaultReactSibling(world, gameContainer, projectRoot, store);
428
+ }
429
+ rejectUnmountableSibling(world);
430
+ }
431
+
432
+ /**
433
+ * D-V2: mount every world in `manifest.worlds` OTHER than the primary ingest
434
+ * world (`ingestWorldId`), in manifest order (already z-sorted by the
435
+ * loader). A failing sibling never unmounts the ingest world or stops the
436
+ * loop — it is caught, reported (D-V5's multi-entry mount-failure report,
437
+ * deduped by worldId), and the loop continues to the next sibling.
438
+ */
439
+ export async function mountIngestSiblings(
440
+ manifest: ResolvedGameManifest,
441
+ ingestWorldId: string,
442
+ gameContainer: HTMLElement,
443
+ projectRoot: string,
444
+ // D-E1: optional ONLY so the one pre-existing direct caller
445
+ // (`ingest-composite-sibling-pause.test.ts`, which never inspects
446
+ // `SiblingMount.authoring`) keeps compiling/running unchanged — every
447
+ // production caller (`ingest-mode.ts`'s composite routes) supplies one.
448
+ store?: EditorStore,
449
+ ): Promise<SiblingMount[]> {
450
+ const mounted: SiblingMount[] = [];
451
+ for (const world of manifest.worlds) {
452
+ if (world.id === ingestWorldId) continue;
453
+ try {
454
+ mounted.push(await mountOneIngestSibling(manifest, world, gameContainer, projectRoot, store));
455
+ } catch (err) {
456
+ editorConsole.error(`Composite sibling "${world.id}" failed to mount: ${err}`, 'ingest');
457
+ addMountFailureReport({
458
+ worldId: world.id,
459
+ kind: world.kind,
460
+ identity: world.adapter.identity,
461
+ message: formatMountFailureMessage(err),
462
+ });
463
+ }
464
+ }
465
+ return mounted;
466
+ }
@@ -0,0 +1,194 @@
1
+ import { resolveUrl } from '@engine/loader';
2
+ import { parseSceneFile } from '@engine/scene/parse';
3
+ import type { SceneFile } from '@engine/scene/scene-types';
4
+ import { saveFile } from './editor-api';
5
+ import { editorConsole } from './editor-console';
6
+ import { getEditorMode } from './editor-mode';
7
+ import type { EditorStore } from './editor-store';
8
+ import {
9
+ type ActiveProject,
10
+ detectServerProject,
11
+ getCurrentProject,
12
+ getProjectPathFromURL,
13
+ openProject,
14
+ } from './project-manager';
15
+ import { getStorageBackend } from './storage';
16
+
17
+ /** Detect the active project (from URL or server) and log the result. */
18
+ export async function initProject(): Promise<void> {
19
+ // If the project manager already has a project (set by AppRoot/ProjectScreen),
20
+ // just log it and return — no need to re-detect.
21
+ const existing = getCurrentProject();
22
+ if (existing) {
23
+ editorConsole.log(`Opened project: ${existing.config.name}`, 'project');
24
+ return;
25
+ }
26
+
27
+ const projectPath = getProjectPathFromURL();
28
+ if (projectPath) {
29
+ try {
30
+ const project = await openProject(projectPath);
31
+ editorConsole.log(`Opened project: ${project.config.name}`, 'project');
32
+ } catch (err) {
33
+ editorConsole.error(`Failed to open project: ${err}`, 'project');
34
+ }
35
+ } else {
36
+ try {
37
+ const project = await detectServerProject();
38
+ if (project) {
39
+ editorConsole.log(`Opened project: ${project.config.name}`, 'project');
40
+ }
41
+ } catch {
42
+ // No server project — run without a project
43
+ }
44
+ }
45
+ }
46
+
47
+ /** A minimal empty scene — camera + a box to look at. Used both for the
48
+ * "no scene declared" in-memory fallback and the "declared scene failed to
49
+ * load" on-disk recovery fallback below. */
50
+ function buildFallbackSceneDoc(): SceneFile {
51
+ return {
52
+ version: 1,
53
+ name: 'Untitled',
54
+ entities: [
55
+ {
56
+ name: 'Camera',
57
+ camera: { type: 'perspective' as const },
58
+ transform: { position: [0, 2, 5] as [number, number, number] },
59
+ },
60
+ {
61
+ name: 'Box',
62
+ mesh: { type: 'box' as const },
63
+ material: { type: 'standard' as const, color: '#888888' },
64
+ },
65
+ ],
66
+ };
67
+ }
68
+
69
+ /**
70
+ * A1 (`authoring/edit-mode-authoring.ts`): the scene path `loadInitialScene`
71
+ * WOULD resolve to, computed with NO I/O — used only to NAME the `scene`
72
+ * field of the single-world manifest synthesized for a manifest-less project,
73
+ * so that synthesized world is a faithful (if inert) description of what's
74
+ * actually loaded. Mirrors `loadInitialScene`'s resolution order exactly
75
+ * (`?scene=` URL param -> `ProjectConfig.defaultScene` -> legacy
76
+ * `'scenes/default.vscn.json'` fallback) but never fetches/loads/writes
77
+ * anything — `loadInitialScene` itself is UNTOUCHED by this addition and
78
+ * remains the only function that actually loads a scene into the store, so
79
+ * this can never drift into a second, competing load path.
80
+ */
81
+ export function resolveInitialScenePathForManifestSynthesis(
82
+ project: ActiveProject | null,
83
+ ): string | null {
84
+ const sceneParam = new URLSearchParams(window.location.search).get('scene');
85
+ if (sceneParam) return sceneParam;
86
+ // A legal "no scene declared" project (§7.1-12, see `loadInitialScene`
87
+ // below) — an entry-only first world has no scene path to name.
88
+ if (project && !project.config.defaultScene) return null;
89
+ return project?.config.defaultScene ?? 'scenes/default.vscn.json';
90
+ }
91
+
92
+ /** Load the initial scene from URL param, project default, or create a fallback. */
93
+ export async function loadInitialScene(store: EditorStore): Promise<void> {
94
+ const urlParams = new URLSearchParams(window.location.search);
95
+ const sceneUrl = urlParams.get('scene');
96
+ const project = getCurrentProject();
97
+
98
+ if (sceneUrl) {
99
+ try {
100
+ // Hosted example project (Track H): `?scene=` is PROJECT-RELATIVE —
101
+ // `resolveUrl` carries the `/examples/<id>/` prefix set at open — and
102
+ // loads READ-ONLY (no save path ⇒ autosave inert), since the staged
103
+ // example is not writable storage.
104
+ if (project?.exampleId && !sceneUrl.startsWith('/')) {
105
+ const res = await fetch(resolveUrl(sceneUrl));
106
+ if (!res.ok) {
107
+ throw new Error(`HTTP ${res.status} fetching ${sceneUrl}`);
108
+ }
109
+ store.loadDocument(parseSceneFile(await res.json()));
110
+ return;
111
+ }
112
+
113
+ const res = await fetch(resolveUrl(sceneUrl));
114
+ if (!res.ok) {
115
+ throw new Error(`HTTP ${res.status} fetching ${sceneUrl}`);
116
+ }
117
+ const doc = parseSceneFile(await res.json());
118
+ const savePath = sceneUrl.startsWith('/') ? sceneUrl.slice(1) : sceneUrl;
119
+ store.loadDocument(doc, savePath);
120
+ return;
121
+ } catch (err) {
122
+ editorConsole.error(`Failed to load scene from URL: ${err}`, 'scene');
123
+ }
124
+ }
125
+
126
+ // A KNOWN project (readProjectView resolved it — see project-view.ts) that
127
+ // declares NO scene at all (its `defaultScene` is explicit `null`, or —
128
+ // legacy `project.json` verbatim pass-through — the key is simply absent)
129
+ // is a legal shape (an entry-only first world). NEVER guess a path or probe
130
+ // the network for one here: that is exactly what silently WROTE a
131
+ // placeholder 'scenes/untitled.vscn.json' for a project the user never
132
+ // asked to persist anything into (§7.1-12). Build the fallback purely in
133
+ // memory and hand it to the store WITHOUT a save path — `loadDocument`
134
+ // sets `savePath` to `null` in that case, which makes `saveNow()`/autosave
135
+ // an inert no-op (`editor-store.ts`'s `if (!this._savePath || ...) return
136
+ // false`) until an explicit user save gives it a real, user-visible path.
137
+ if (project && !project.config.defaultScene) {
138
+ store.loadDocument(buildFallbackSceneDoc());
139
+ return;
140
+ }
141
+
142
+ // No project resolved at all (e.g. no project open) — preserve the
143
+ // pre-existing guess as the last resort for finding SOMETHING to load.
144
+ const defaultScenePath = project?.config.defaultScene ?? 'scenes/default.vscn.json';
145
+
146
+ // Browser-first build: scene files live in the in-browser VFS, not on an HTTP
147
+ // server, so read through the storage backend instead of fetch(). Bundled
148
+ // EXAMPLE projects are the exception — their scenes are static files staged
149
+ // under `/examples/<id>/`, fetched through the asset prefix and loaded
150
+ // READ-ONLY (no save path), same as the `?scene=` branch above.
151
+ if (getEditorMode() === 'browser') {
152
+ if (project?.exampleId) {
153
+ try {
154
+ const res = await fetch(resolveUrl(defaultScenePath));
155
+ if (!res.ok) {
156
+ throw new Error(`HTTP ${res.status} fetching ${defaultScenePath}`);
157
+ }
158
+ store.loadDocument(parseSceneFile(await res.json()));
159
+ return;
160
+ } catch (err) {
161
+ editorConsole.error(`Failed to load example scene: ${err}`, 'scene');
162
+ store.loadDocument(buildFallbackSceneDoc());
163
+ return;
164
+ }
165
+ }
166
+ try {
167
+ const doc = parseSceneFile(JSON.parse(await getStorageBackend().read(defaultScenePath)));
168
+ store.loadDocument(doc, defaultScenePath);
169
+ return;
170
+ } catch (err) {
171
+ editorConsole.error(`Failed to load scene from storage: ${err}`, 'scene');
172
+ }
173
+ }
174
+
175
+ try {
176
+ const res = await fetch(resolveUrl(`/${defaultScenePath}`));
177
+ if (res.ok) {
178
+ const doc = parseSceneFile(await res.json());
179
+ store.loadDocument(doc, defaultScenePath);
180
+ return;
181
+ }
182
+ } catch {
183
+ // Default scene not found — fall through to fallback
184
+ }
185
+
186
+ // Fallback: create a minimal scene. Reached only when a scene path WAS
187
+ // resolved (declared, or guessed because no project was known at all) but
188
+ // failed to load — pre-existing recovery behavior, unchanged: persist a
189
+ // fresh untitled scene so there is something to edit and save.
190
+ const fallbackDoc = buildFallbackSceneDoc();
191
+ const fallbackPath = 'scenes/untitled.vscn.json';
192
+ await saveFile(fallbackPath, JSON.stringify(fallbackDoc, null, 2));
193
+ store.loadDocument(fallbackDoc, fallbackPath);
194
+ }