@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,746 @@
1
+ /**
2
+ * VgaiSceneAuthoringAdapter — the FIRST-PARTY {@link AuthoringAdapter}.
3
+ *
4
+ * It implements the editor's authoring contract by delegating to the existing
5
+ * {@link EditorStore}. `SceneEntity` / `.vscn` is this adapter's PRIVATE model:
6
+ * this is the one editor module (besides the store/serializer internals) that is
7
+ * allowed to import `SceneEntity`. The editor UI talks to the `AuthoringAdapter`
8
+ * interface and never sees `SceneEntity` — that confinement is the inversion.
9
+ *
10
+ * An ingested external game's `IngestAuthoringAdapter` (Phase B) implements the
11
+ * SAME providers directly over its live `Object3D` tree, with no `SceneEntity`.
12
+ */
13
+
14
+ import type {
15
+ AssetDropProvider,
16
+ AuthoringAdapter,
17
+ AuthoringCapabilities,
18
+ ComponentsProvider,
19
+ EditorNode,
20
+ FileMapProvider,
21
+ HierarchyProvider,
22
+ InspectorProvider,
23
+ PersistenceProvider,
24
+ PickProvider,
25
+ PropertyDescriptor,
26
+ RootGroup,
27
+ RootGroupsProvider,
28
+ SelectionProvider,
29
+ StructureProvider,
30
+ Transform,
31
+ TransformOwner,
32
+ TransformProvider,
33
+ } from '@engine/adapter';
34
+ import { resolveUrl } from '@engine/loader';
35
+ import { parseSceneFile } from '@engine/scene/parse';
36
+ import type { SceneEntity, SceneFile } from '@engine/scene/scene-types';
37
+ import { getUserData } from '@engine/scene/user-data';
38
+ import type { FieldKind } from '../components/inspectors/zod-fields';
39
+ import { introspectSchema } from '../components/inspectors/zod-fields';
40
+ import { getEditorMode } from '../editor-mode';
41
+ import type { EditorStore } from '../editor-store';
42
+ import { getLoadedComponents } from '../script-loader';
43
+ import { getStorageBackend } from '../storage';
44
+ import { getActiveNetworking, getActivePhysics } from './active-systems';
45
+ import { ensureProjectScenesLoaded, getCachedProjectScenes } from './project-scenes';
46
+ import { raycastPick } from './viewport-raycast';
47
+
48
+ type ComponentDataMap = NonNullable<SceneEntity['components']>;
49
+
50
+ /** `zod-fields.ts`'s `FieldKind` → `PropertyDescriptor.type` (D6 — zod stays
51
+ * behind `introspectSchema`; the contract only ever sees this format-neutral
52
+ * mapping, never a zod shape). `select` folds into `enum` — both render as a
53
+ * dropdown over `options`; every other kind name matches 1:1. */
54
+ function fieldKindToPropertyType(kind: FieldKind): PropertyDescriptor['type'] {
55
+ return kind === 'select' ? 'enum' : kind;
56
+ }
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // D8 — root groups (radio roots over the project's `*.vscn.json` files)
60
+ // ---------------------------------------------------------------------------
61
+
62
+ const SCENE_ROOT_PREFIX = 'sceneRoot:';
63
+
64
+ /** Member node id for a project-relative scene path — stable, opaque to the
65
+ * shell (GameHierarchy discovers membership only via `rootGroups.groups()`
66
+ * data, never by decoding this prefix itself). */
67
+ function sceneRootId(path: string): string {
68
+ return `${SCENE_ROOT_PREFIX}${path}`;
69
+ }
70
+
71
+ /** Inverse of {@link sceneRootId} — `null` for any id this adapter didn't mint. */
72
+ function pathForSceneRootId(id: string): string | null {
73
+ return id.startsWith(SCENE_ROOT_PREFIX) ? id.slice(SCENE_ROOT_PREFIX.length) : null;
74
+ }
75
+
76
+ function normalizeScenePath(path: string): string {
77
+ return path.startsWith('/') ? path.slice(1) : path;
78
+ }
79
+
80
+ async function fetchSceneDoc(path: string): Promise<SceneFile> {
81
+ // Mirrors `initial-scene.ts`'s browser-vs-server branch: browser builds
82
+ // read scene files through the active `StorageBackend` (OPFS/File System
83
+ // Access), not `fetch()`; the server dev build serves them over HTTP.
84
+ if (getEditorMode() === 'browser') {
85
+ return parseSceneFile(JSON.parse(await getStorageBackend().read(path)));
86
+ }
87
+ const res = await fetch(resolveUrl(`/${path}`));
88
+ if (!res.ok) throw new Error(`HTTP ${res.status} fetching ${path}`);
89
+ return parseSceneFile(await res.json());
90
+ }
91
+
92
+ function kindOf(e: SceneEntity): EditorNode['kind'] {
93
+ if (e.light) return 'light';
94
+ if (e.camera) return 'camera';
95
+ if (e.mesh) return 'mesh';
96
+ if (e.children && e.children.length > 0) return 'group';
97
+ return 'object';
98
+ }
99
+
100
+ function toNode(e: SceneEntity, parentId: string | null): EditorNode {
101
+ return {
102
+ id: e.id!,
103
+ label: e.name,
104
+ kind: kindOf(e),
105
+ parentId,
106
+ childIds: (e.children ?? []).map((c) => c.id!),
107
+ flags: { locked: e.locked === true },
108
+ };
109
+ }
110
+
111
+ /** Locate an entity + its parent id within the store's entity tree. */
112
+ function findInTree(
113
+ roots: SceneEntity[],
114
+ id: string,
115
+ ): { entity: SceneEntity; parentId: string | null } | null {
116
+ const walk = (
117
+ list: SceneEntity[],
118
+ parentId: string | null,
119
+ ): { entity: SceneEntity; parentId: string | null } | null => {
120
+ for (const e of list) {
121
+ if (e.id === id) return { entity: e, parentId };
122
+ const hit = e.children ? walk(e.children, e.id!) : null;
123
+ if (hit) return hit;
124
+ }
125
+ return null;
126
+ };
127
+ return walk(roots, null);
128
+ }
129
+
130
+ /**
131
+ * The 12-kind creation palette (A2 parity growth — ported verbatim from
132
+ * `SceneHierarchy.tsx`'s `addMenuItems`, formerly toolbar-only) plus the two
133
+ * legacy slugs ('box'/'mesh' both -> Box, 'group' -> an empty container) that
134
+ * pre-date this table and are pinned by `structure-provider.test.ts`. Shared
135
+ * by `structure.create` and `structure.creatableKinds` so the palette and the
136
+ * actual entity shape can never drift apart.
137
+ */
138
+ const KIND_TEMPLATES: Record<string, { label: string; make: () => SceneEntity }> = {
139
+ empty: { label: 'Empty', make: () => ({ id: crypto.randomUUID(), name: 'Empty' }) },
140
+ box: {
141
+ label: 'Box',
142
+ make: () => ({
143
+ id: crypto.randomUUID(),
144
+ name: 'Box',
145
+ mesh: { type: 'box' },
146
+ material: { type: 'standard', color: '#888888' },
147
+ }),
148
+ },
149
+ mesh: {
150
+ label: 'Box',
151
+ make: () => ({
152
+ id: crypto.randomUUID(),
153
+ name: 'Box',
154
+ mesh: { type: 'box' },
155
+ material: { type: 'standard', color: '#888888' },
156
+ }),
157
+ },
158
+ sphere: {
159
+ label: 'Sphere',
160
+ make: () => ({
161
+ id: crypto.randomUUID(),
162
+ name: 'Sphere',
163
+ mesh: { type: 'sphere' },
164
+ material: { type: 'standard', color: '#888888' },
165
+ }),
166
+ },
167
+ plane: {
168
+ label: 'Plane',
169
+ make: () => ({
170
+ id: crypto.randomUUID(),
171
+ name: 'Plane',
172
+ mesh: { type: 'plane' },
173
+ material: { type: 'standard', color: '#888888' },
174
+ }),
175
+ },
176
+ cylinder: {
177
+ label: 'Cylinder',
178
+ make: () => ({
179
+ id: crypto.randomUUID(),
180
+ name: 'Cylinder',
181
+ mesh: { type: 'cylinder' },
182
+ material: { type: 'standard', color: '#888888' },
183
+ }),
184
+ },
185
+ capsule: {
186
+ label: 'Capsule',
187
+ make: () => ({
188
+ id: crypto.randomUUID(),
189
+ name: 'Capsule',
190
+ mesh: { type: 'capsule' },
191
+ material: { type: 'standard', color: '#888888' },
192
+ }),
193
+ },
194
+ group: { label: 'Group', make: () => ({ id: crypto.randomUUID(), name: 'Group', children: [] }) },
195
+ dirLight: {
196
+ label: 'Dir Light',
197
+ make: () => ({
198
+ id: crypto.randomUUID(),
199
+ name: 'DirectionalLight',
200
+ light: { type: 'directional', intensity: 1 },
201
+ transform: { position: [5, 10, 5] },
202
+ }),
203
+ },
204
+ pointLight: {
205
+ label: 'Point Light',
206
+ make: () => ({
207
+ id: crypto.randomUUID(),
208
+ name: 'PointLight',
209
+ light: { type: 'point', intensity: 1 },
210
+ }),
211
+ },
212
+ spotLight: {
213
+ label: 'Spot Light',
214
+ make: () => ({
215
+ id: crypto.randomUUID(),
216
+ name: 'SpotLight',
217
+ light: { type: 'spot', intensity: 1 },
218
+ }),
219
+ },
220
+ camera: {
221
+ label: 'Camera',
222
+ make: () => ({ id: crypto.randomUUID(), name: 'Camera', camera: { type: 'perspective' } }),
223
+ },
224
+ audioSource: {
225
+ label: 'Audio Source',
226
+ make: () => ({
227
+ id: crypto.randomUUID(),
228
+ name: 'AudioSource',
229
+ audio: { spatial: true, volume: 1 },
230
+ }),
231
+ },
232
+ spline: {
233
+ label: 'Spline',
234
+ make: () => ({
235
+ id: crypto.randomUUID(),
236
+ name: 'Spline',
237
+ spline: {
238
+ curveType: 'centripetal',
239
+ points: [
240
+ [0, 0, 0],
241
+ [1, 0, 1],
242
+ [2, 0, 0],
243
+ ],
244
+ closed: false,
245
+ },
246
+ }),
247
+ },
248
+ };
249
+
250
+ /** The palette order shown in the creation menu — the 12 first-class kinds
251
+ * (legacy 'mesh'/'box' duplicate and internal-only 'group' are reachable by
252
+ * slug but not listed twice / not part of the user-facing palette). */
253
+ const PALETTE_ORDER = [
254
+ 'empty',
255
+ 'box',
256
+ 'sphere',
257
+ 'plane',
258
+ 'cylinder',
259
+ 'capsule',
260
+ 'dirLight',
261
+ 'pointLight',
262
+ 'spotLight',
263
+ 'camera',
264
+ 'audioSource',
265
+ 'spline',
266
+ ] as const;
267
+
268
+ /** A minimal first-party `SceneEntity` for a structural `create(kind)`. */
269
+ function entityForKind(kind: string): SceneEntity {
270
+ return (KIND_TEMPLATES[kind] ?? KIND_TEMPLATES['empty']!).make();
271
+ }
272
+
273
+ type AssetDropKind = 'model' | 'image' | 'prefab' | 'audio';
274
+
275
+ /** Classify a dropped asset path by extension — the adapter's own private
276
+ * reading of the shell's asset path string (the shell never passes a `kind`;
277
+ * see `AssetDropProvider`'s contract). Mirrors `AssetBrowser.tsx`'s
278
+ * `getAssetKind`, trimmed to the kinds this adapter can turn into an entity. */
279
+ function kindFromAssetPath(path: string): AssetDropKind | null {
280
+ const lower = path.toLowerCase();
281
+ if (lower.endsWith('.glb') || lower.endsWith('.gltf')) return 'model';
282
+ if (/\.(png|jpe?g|webp|svg|gif)$/.test(lower)) return 'image';
283
+ if (lower.endsWith('.prefab.json')) return 'prefab';
284
+ if (/\.(mp3|ogg|wav|flac)$/.test(lower)) return 'audio';
285
+ return null;
286
+ }
287
+
288
+ /** Create entity data from a dropped asset path (mirrors the former
289
+ * `SceneHierarchy.tsx`'s `entityFromAssetDrop`, now keyed off the
290
+ * path-derived kind instead of a caller-supplied one). */
291
+ function entityFromAssetPath(path: string, kind: AssetDropKind): SceneEntity | null {
292
+ const name = path.split('/').pop() ?? path;
293
+ const baseName = name.replace(/\.[^.]+$/, '');
294
+ const id = crypto.randomUUID();
295
+ switch (kind) {
296
+ case 'model':
297
+ return { id, name: baseName, mesh: { type: 'gltf', src: path } };
298
+ case 'image':
299
+ return {
300
+ id,
301
+ name: baseName,
302
+ mesh: { type: 'plane', args: [2, 2] },
303
+ material: { type: 'standard', map: path },
304
+ };
305
+ case 'prefab':
306
+ return { id, name: baseName, prefab: path };
307
+ case 'audio':
308
+ return { id, name: baseName, audio: { src: path, spatial: true, volume: 1 } };
309
+ }
310
+ }
311
+
312
+ export class VgaiSceneAuthoringAdapter implements AuthoringAdapter {
313
+ constructor(private readonly store: EditorStore) {}
314
+
315
+ readonly capabilities: AuthoringCapabilities = {
316
+ transform: true,
317
+ material: true,
318
+ inspectorFields: true,
319
+ create: true,
320
+ delete: true,
321
+ reparent: true,
322
+ persist: true,
323
+ };
324
+
325
+ subscribe(listener: () => void): () => void {
326
+ return this.store.subscribe(listener);
327
+ }
328
+
329
+ readonly hierarchy: HierarchyProvider = {
330
+ // D8 — member (scene-file root) rows are APPENDED after the store's own
331
+ // entity roots (never interleaved between them): several existing e2e
332
+ // specs range-select or index-scan entity rows by relative position
333
+ // (`29-game-hierarchy.spec.ts`'s multi-select, drag-reorder) — appending
334
+ // keeps every entity's relative order/adjacency byte-identical to today,
335
+ // the only delta is N additional childless rows at the tail.
336
+ roots: () => [...this.store.entities.map((e) => toNode(e, null)), ...this.memberNodes()],
337
+ node: (id) => {
338
+ const memberPath = pathForSceneRootId(id);
339
+ if (memberPath !== null) return this.memberNode(memberPath);
340
+ const hit = findInTree(this.store.entities, id);
341
+ return hit ? toNode(hit.entity, hit.parentId) : null;
342
+ },
343
+ object3D: (id) => this.store.objectMap.get(id) ?? null,
344
+ idForObject3D: (o) => (getUserData(o, 'entityId') as string | undefined) ?? null,
345
+ };
346
+
347
+ /** The project's scene files (module-cached, D8) — [] until the async
348
+ * listing lands, at which point `notifyIngestEdit` re-renders the panel. */
349
+ private scenesList(): string[] {
350
+ ensureProjectScenesLoaded(() => this.store.notifyIngestEdit());
351
+ return getCachedProjectScenes();
352
+ }
353
+
354
+ private memberNodes(): EditorNode[] {
355
+ return this.scenesList()
356
+ .map((path) => this.memberNode(path))
357
+ .filter((n): n is EditorNode => n !== null);
358
+ }
359
+
360
+ /** `null` for a path that isn't (or is no longer) a known project scene —
361
+ * a stale/dangling `sceneRoot:` id resolves to nothing rather than a
362
+ * fabricated node. */
363
+ private memberNode(path: string): EditorNode | null {
364
+ if (!this.scenesList().includes(path)) return null;
365
+ const isActive =
366
+ this.store.savePath !== null && normalizeScenePath(this.store.savePath) === path;
367
+ return {
368
+ id: sceneRootId(path),
369
+ label: path,
370
+ kind: 'object',
371
+ parentId: null,
372
+ childIds: [],
373
+ // D8 — member of a RootGroup, not the active one (see `authoring.ts`'s
374
+ // `EditorNode.flags.inactiveRoot` doc). The active member gets NO flag
375
+ // so both radio states are plain childless rows (GameHierarchy renders
376
+ // the ● / ○ radio from `rootGroups` data, not from this flag alone).
377
+ flags: isActive ? {} : { inactiveRoot: true },
378
+ };
379
+ }
380
+
381
+ /**
382
+ * D8 — one mutually-exclusive "Scenes" group over every project
383
+ * `*.vscn.json`. `activate()` flushes a pending autosave first (belt: the
384
+ * 300ms debounce could otherwise lose an edit made just before switching),
385
+ * then loads the target scene into the live store exactly like opening it
386
+ * from the asset browser (`DefaultEditorLayout.tsx`'s `openSceneFromAsset`
387
+ * recipe) — `loadDocument` itself notifies subscribers.
388
+ */
389
+ readonly rootGroups: RootGroupsProvider = {
390
+ groups: (): RootGroup[] => {
391
+ const scenes = this.scenesList();
392
+ const activeRootId = this.store.savePath
393
+ ? sceneRootId(normalizeScenePath(this.store.savePath))
394
+ : null;
395
+ return [
396
+ {
397
+ id: 'scenes',
398
+ label: 'Scenes',
399
+ memberRootIds: scenes.map(sceneRootId),
400
+ activeRootId,
401
+ },
402
+ ];
403
+ },
404
+ activate: async (groupId, rootId) => {
405
+ if (groupId !== 'scenes') return;
406
+ const path = pathForSceneRootId(rootId);
407
+ if (!path) return;
408
+ if (this.store.isDirty) await this.store.saveNow();
409
+ try {
410
+ const doc = await fetchSceneDoc(path);
411
+ this.store.loadDocument(doc, path);
412
+ } catch (err) {
413
+ // Plain `console` (not `editorConsole`, which touches `window` at
414
+ // module scope) — this adapter is exercised by node-environment unit
415
+ // tests with no DOM, same reasoning as `editor-store.ts`'s own
416
+ // `console.warn` in `_autoSave`.
417
+ console.error(`[VgaiSceneAuthoringAdapter] failed to activate scene root "${path}":`, err);
418
+ }
419
+ },
420
+ };
421
+
422
+ /** D8 — `*.vscn.json` files map to their scene-root id; anything else maps
423
+ * to nothing (this adapter owns no other file kind as a root). */
424
+ readonly files: FileMapProvider = {
425
+ rootForFile: (path) => {
426
+ const normalized = normalizeScenePath(path);
427
+ return normalized.endsWith('.vscn.json') ? sceneRootId(normalized) : null;
428
+ },
429
+ };
430
+
431
+ readonly selection: SelectionProvider = {
432
+ get: () => [...this.store.selectedEntityIds],
433
+ set: (ids) => this.store.selectMultiple(ids),
434
+ };
435
+
436
+ /**
437
+ * D12 (B4) — the threejs viewport raycast, delegating to the SHARED
438
+ * `raycastPick` helper (`viewport-raycast.ts`) that was extracted verbatim
439
+ * from `editor-viewport.ts`'s former `_raycastEntity` (behavior-identical
440
+ * checkpoint: `09-editor-interaction.spec.ts` must stay green). Shared so
441
+ * `IngestAuthoringAdapter` reuses the EXACT same raycast (restoring ingest/
442
+ * multi-world-play canvas click-select, which pre-B4 ran unconditionally) —
443
+ * see that helper's doc comment. Absent viewport context ⇒ `null`, an honest
444
+ * headless degrade.
445
+ */
446
+ readonly pickable: PickProvider = {
447
+ pick: (clientX, clientY) => raycastPick(this.store, clientX, clientY),
448
+ };
449
+
450
+ readonly transforms: TransformProvider = {
451
+ get: (id): Transform => {
452
+ const obj = this.store.objectMap.get(id);
453
+ if (!obj) return { position: [0, 0, 0], rotation: [0, 0, 0, 1], scale: [1, 1, 1] };
454
+ return {
455
+ position: obj.position.toArray() as [number, number, number],
456
+ rotation: obj.quaternion.toArray() as [number, number, number, number],
457
+ scale: obj.scale.toArray() as [number, number, number],
458
+ };
459
+ },
460
+ // Transform owner reflects the active system adapters: a Rapier-driven body
461
+ // reports 'physics'; a replicated remote/server object reports 'network'.
462
+ owner: (id): TransformOwner => {
463
+ const obj = this.store.objectMap.get(id);
464
+ if (!obj) return 'editor';
465
+ const net = getActiveNetworking();
466
+ if (net?.networkId(obj) && net.authority(obj) !== 'local') return 'network';
467
+ return getActivePhysics()?.ownerOf(obj) ?? 'editor';
468
+ },
469
+ // beginEdit snapshots for undo and, for a physics-owned object, FREEZES the
470
+ // body (kinematic) so the gizmo edit isn't overwritten mid-drag. Remote/server
471
+ // network-authoritative objects are inspect-only → the edit is a no-op.
472
+ beginEdit: (id) => {
473
+ const obj = this.store.objectMap.get(id);
474
+ if (obj && !this.editable(obj)) return; // inspect-only (network-owned)
475
+ this.store.pushUndoSnapshot();
476
+ if (obj) getActivePhysics()?.freeze(obj);
477
+ },
478
+ // apply is the LIVE path: move the object + sync the descriptor without
479
+ // pushing undo (beginEdit already snapshotted). The store's ECS-sync callback
480
+ // commits the pose to the owning body (PhysicsAdapter.commit) so it tracks.
481
+ apply: (id, t) => {
482
+ const obj = this.store.objectMap.get(id);
483
+ if (obj && !this.editable(obj)) return; // inspect-only (network-owned)
484
+ if (obj) {
485
+ obj.position.set(t.position[0], t.position[1], t.position[2]);
486
+ obj.quaternion.set(t.rotation[0], t.rotation[1], t.rotation[2], t.rotation[3]);
487
+ obj.scale.set(t.scale[0], t.scale[1], t.scale[2]);
488
+ }
489
+ this.store.updateTransformLive(id, t.position, t.rotation, t.scale);
490
+ },
491
+ endEdit: (id) => {
492
+ const obj = this.store.objectMap.get(id);
493
+ if (obj) getActivePhysics()?.unfreeze(obj);
494
+ this.store.commitLiveTransform();
495
+ },
496
+ };
497
+
498
+ /** False when the active networking adapter marks the object inspect-only. */
499
+ private editable(obj: import('three').Object3D): boolean {
500
+ const net = getActiveNetworking();
501
+ return net ? net.editable(obj) : true;
502
+ }
503
+
504
+ readonly inspector: InspectorProvider = {
505
+ properties: (id): PropertyDescriptor[] => {
506
+ const e = this.store.getEntityById(id);
507
+ if (!e) return [];
508
+ const props: PropertyDescriptor[] = [
509
+ { path: 'name', label: 'Name', type: 'string' },
510
+ { path: 'visible', label: 'Visible', type: 'boolean' },
511
+ // A2 parity growth — surfaced so the shell's reserved-path lock
512
+ // affordance (hierarchy panel) sees it via `properties()`, exactly
513
+ // like `visible`. Own flag only (NOT `store.isEntityLocked`'s
514
+ // ancestor-merged read) — matches the pre-A2 hierarchy row, whose
515
+ // lock icon/toggle always reflected/toggled the entity's OWN flag.
516
+ { path: 'locked', label: 'Locked', type: 'boolean' },
517
+ ];
518
+ if (e.material) props.push({ path: 'material.color', label: 'Color', type: 'color' });
519
+ // C1 — component CONFIG fields, one PropertyDescriptor per schema field,
520
+ // grouped by component name (mirrors `ComponentsSection.tsx`'s
521
+ // per-component field rows). Only components whose class has a `static
522
+ // schema` get descriptors — schema-less components' raw data isn't
523
+ // format-neutral, so this contract surface stays silent on them (same
524
+ // gate `ComponentsSection` uses for `addable`/`renderSchemaFields`).
525
+ const classes = getLoadedComponents();
526
+ for (const compName of Object.keys(e.components ?? {})) {
527
+ const schema = classes[compName]?.schema;
528
+ if (!schema) continue;
529
+ for (const f of introspectSchema(schema)) {
530
+ props.push({
531
+ path: `components.${compName}.${f.name}`,
532
+ label: f.name,
533
+ type: fieldKindToPropertyType(f.kind),
534
+ group: compName,
535
+ ...(f.options ? { options: f.options } : {}),
536
+ });
537
+ }
538
+ }
539
+ return props;
540
+ },
541
+ get: (id, path) => {
542
+ const e = this.store.getEntityById(id);
543
+ if (!e) return undefined;
544
+ if (path === 'name') return e.name;
545
+ if (path === 'visible') return e.visible !== false;
546
+ if (path === 'locked') return e.locked === true;
547
+ if (path === 'material.color') return e.material?.color;
548
+ const compField = /^components\.([^.]+)\.(.+)$/.exec(path);
549
+ const compName = compField?.[1];
550
+ const fieldName = compField?.[2];
551
+ if (compName !== undefined && fieldName !== undefined) {
552
+ const comp = e.components?.[compName] as Record<string, unknown> | undefined;
553
+ return comp?.[fieldName];
554
+ }
555
+ return undefined;
556
+ },
557
+ set: (id, path, value) => {
558
+ const compField = /^components\.([^.]+)\.(.+)$/.exec(path);
559
+ const compName = compField?.[1];
560
+ const fieldName = compField?.[2];
561
+ if (compName !== undefined && fieldName !== undefined) {
562
+ const e = this.store.getEntityById(id);
563
+ if (!e) return;
564
+ const components = (e.components ?? {}) as Record<string, Record<string, unknown>>;
565
+ const next: Record<string, Record<string, unknown>> = {
566
+ ...components,
567
+ [compName]: { ...components[compName], [fieldName]: value },
568
+ };
569
+ this.store.updateEntity(id, { components: next as unknown as ComponentDataMap });
570
+ return;
571
+ }
572
+ if (path === 'name') this.store.updateEntity(id, { name: String(value) });
573
+ else if (path === 'visible') this.store.updateEntity(id, { visible: Boolean(value) });
574
+ else if (path === 'locked') {
575
+ const e = this.store.getEntityById(id);
576
+ if (e && (e.locked === true) !== Boolean(value)) this.store.toggleLocked(id);
577
+ } else if (path === 'material.color') {
578
+ this.store.batchMutate([id], (e) => {
579
+ if (e.material) e.material.color = String(value);
580
+ });
581
+ }
582
+ },
583
+ };
584
+
585
+ /**
586
+ * C1 (D6, `docs/unified-world-editor/26-unified-editor-spec.md` §C1) —
587
+ * GameComponent management. Mirrors `ComponentsSection.tsx:44-75` byte for
588
+ * byte: there are no dedicated store verbs, add/remove both go through
589
+ * `store.updateEntity(id, { components })` — undo is FREE (`updateEntity`
590
+ * calls `_pushUndo()` before mutating), so this provider needs no `undo()`
591
+ * of its own and must never push its own snapshot. `getLoadedComponents()`
592
+ * is empty until a project's scripts have been loaded at least once (play
593
+ * mode, or `setLoadedComponents` in tests) — that is a real design-time
594
+ * limitation this provider mirrors rather than works around (out of scope
595
+ * here — see the plan's eager-load exclusion).
596
+ */
597
+ readonly components: ComponentsProvider = {
598
+ available: (nodeId) => {
599
+ const e = this.store.getEntityById(nodeId);
600
+ if (!e) return [];
601
+ const components = (e.components ?? {}) as Record<string, Record<string, unknown>>;
602
+ const classes = getLoadedComponents();
603
+ return Object.keys(classes)
604
+ .filter((name) => classes[name]?.schema && !(name in components))
605
+ .sort();
606
+ },
607
+ list: (nodeId) => {
608
+ const e = this.store.getEntityById(nodeId);
609
+ if (!e) return [];
610
+ return Object.keys(e.components ?? {}).map((type) => ({ type }));
611
+ },
612
+ config: (nodeId, type) => {
613
+ const e = this.store.getEntityById(nodeId);
614
+ const comp = (e?.components as Record<string, Record<string, unknown>> | undefined)?.[type];
615
+ return comp ? { ...comp } : null;
616
+ },
617
+ add: (nodeId, type) => {
618
+ const e = this.store.getEntityById(nodeId);
619
+ if (!e) return;
620
+ const components = (e.components ?? {}) as Record<string, Record<string, unknown>>;
621
+ const schema = getLoadedComponents()[type]?.schema;
622
+ const data: Record<string, unknown> = {};
623
+ if (schema) {
624
+ for (const f of introspectSchema(schema)) {
625
+ if (f.default !== undefined) data[f.name] = f.default;
626
+ }
627
+ }
628
+ const next: Record<string, Record<string, unknown>> = { ...components, [type]: data };
629
+ this.store.updateEntity(nodeId, { components: next as unknown as ComponentDataMap });
630
+ },
631
+ remove: (nodeId, type) => {
632
+ const e = this.store.getEntityById(nodeId);
633
+ if (!e) return;
634
+ const next = { ...(e.components ?? {}) } as Record<string, Record<string, unknown>>;
635
+ delete next[type];
636
+ this.store.updateEntity(nodeId, { components: next as unknown as ComponentDataMap });
637
+ },
638
+ };
639
+
640
+ readonly structure: StructureProvider = {
641
+ create: (kind, parentId) => {
642
+ const entity = entityForKind(kind);
643
+ this.store.addEntity(parentId ?? null, entity);
644
+ return entity.id!;
645
+ },
646
+ remove: (id) => this.store.removeEntity(id),
647
+ duplicate: (id) => {
648
+ this.store.duplicateEntity(id);
649
+ // duplicateEntity selects the clone → read its id back.
650
+ return [...this.store.selectedEntityIds][0] ?? id;
651
+ },
652
+ reparent: (id, newParentId) => {
653
+ // Append under the new parent (large index → splice appends).
654
+ this.store.moveEntity([id], newParentId, Number.MAX_SAFE_INTEGER);
655
+ },
656
+ // A2 parity growth — sibling reorder, wrapping `store.moveEntity` (the
657
+ // same verb `reparent` above already wraps) with an index computed from
658
+ // the CURRENT sibling list minus the moving id itself (moveEntity always
659
+ // removes the dragged objects from their old parent before re-reading
660
+ // the target's children, so the index must already exclude them).
661
+ reorder: (id, beforeSiblingId) => {
662
+ const hit = findInTree(this.store.entities, id);
663
+ if (!hit) return;
664
+ const parentId = hit.parentId;
665
+ const parentEntity = parentId ? findInTree(this.store.entities, parentId)?.entity : null;
666
+ const siblings = (parentId ? (parentEntity?.children ?? []) : this.store.entities).filter(
667
+ (e) => e.id !== id,
668
+ );
669
+ const index =
670
+ beforeSiblingId === null
671
+ ? siblings.length
672
+ : (() => {
673
+ const idx = siblings.findIndex((e) => e.id === beforeSiblingId);
674
+ return idx < 0 ? siblings.length : idx;
675
+ })();
676
+ this.store.moveEntity([id], parentId, index);
677
+ },
678
+ // A2 parity growth — the former toolbar-only `addMenuItems` list (12
679
+ // kinds), now driving the shell's generic Create▸ palette for ANY
680
+ // parent (root via the toolbar `+`, or a specific row via the context
681
+ // menu) — a capability the old toolbar-only Add menu never had.
682
+ creatableKinds: () =>
683
+ PALETTE_ORDER.map((kind) => ({ kind, label: KIND_TEMPLATES[kind]!.label })),
684
+ };
685
+
686
+ /**
687
+ * A2 parity growth — port of the former `SceneHierarchy.tsx`'s
688
+ * `entityFromAssetDrop` + its animation-attach special case, now living
689
+ * behind the format-neutral `AssetDropProvider` seam. The shell passes only
690
+ * a node id + asset path (never a `kind` — that would leak `SceneEntity`
691
+ * vocabulary across Rule Zero), so this adapter re-derives the kind from
692
+ * the path extension itself (`kindFromAssetPath`).
693
+ *
694
+ * Root-drop sentinel: `nodeId === ''` means "no target row" (the shell's
695
+ * tail/background drop) — there being no `AssetDropProvider` parameter for
696
+ * "no parent" (unlike `StructureProvider.create`'s optional `parentId`),
697
+ * this adapter and its callers (the shell, and
698
+ * `CompositeAuthoringAdapter`'s group-node translation) share this one
699
+ * local convention. Any OTHER nodeId is treated as "drop into/onto this
700
+ * entity" (becomes its child). (The former 'animation' asset kind — drop a
701
+ * `.animgraph.json` onto an entity to attach it directly — was removed by
702
+ * E5; there is no drop-an-asset equivalent for XState-driven animation, it
703
+ * is authored in component code.)
704
+ */
705
+ readonly assetDrop: AssetDropProvider = {
706
+ accepts: (_nodeId, assetPath) => kindFromAssetPath(assetPath) !== null,
707
+ drop: (nodeId, assetPath) => {
708
+ const kind = kindFromAssetPath(assetPath);
709
+ if (!kind) return;
710
+ const parentId = nodeId === '' ? null : nodeId;
711
+ const entity = entityFromAssetPath(assetPath, kind);
712
+ if (entity) this.store.addEntity(parentId, entity);
713
+ },
714
+ };
715
+
716
+ // A class GETTER (not a field initializer) — a field initializer runs before
717
+ // the constructor body assigns `this.store` (parameter properties assign in
718
+ // the constructor body, same as any other constructor statement), so a plain
719
+ // object-literal field would capture `this.store` as `undefined`. A getter
720
+ // defers construction of the provider object until first access, by which
721
+ // point the instance is fully built.
722
+ get persistence(): PersistenceProvider {
723
+ const store = this.store;
724
+ return {
725
+ isDirty: () => store.isDirty,
726
+ save: async () => {
727
+ await store.saveNow();
728
+ },
729
+ serialize: () => store.getDocument(),
730
+ captureToVscn: (ids) =>
731
+ ids.map((id) => store.getEntityById(id)).filter((e): e is SceneEntity => e !== null),
732
+ // Live — reflects `store.savePath`, which changes on loadDocument/loadPrefab.
733
+ get destination(): string {
734
+ return store.savePath ?? '(no scene loaded)';
735
+ },
736
+ // The reload contract (design §5): the existing `applyExternalUpdate`
737
+ // diff-apply path, exposed through the provider instead of the SSE handler
738
+ // reaching into the store directly (`rawContent` forwarded so own-echo
739
+ // detection — `EditorStore`'s private epoch/cooldown discipline — still
740
+ // works exactly as it did before this was routed through the provider).
741
+ applyExternal: (content, rawContent) => {
742
+ store.applyExternalUpdate(content as SceneFile, rawContent);
743
+ },
744
+ };
745
+ }
746
+ }