@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,643 @@
1
+ /**
2
+ * Edit-mode authoring (A1, docs/unified-world-editor/26-unified-editor-spec.md
3
+ * §2 A1) — project open loads `vgai.game.json` and installs a
4
+ * {@link CompositeAuthoringAdapter} over EVERY declared world, so a
5
+ * multi-world project's composition is disclosed in edit mode (#28) instead
6
+ * of only in play mode (`play-mode.ts`'s `installMultiWorldAuthoring`, the
7
+ * pattern this mirrors). Unlike play mode, worlds are NOT running here: only
8
+ * the FOCUSED threejs world is live (the existing `VgaiSceneAuthoringAdapter`
9
+ * over the store's already-loaded document — `loadInitialScene` is
10
+ * untouched, still the only thing that ever loads a scene into the store);
11
+ * every other declared world gets a read-only {@link BoundaryAuthoringAdapter}
12
+ * describing it from the manifest, upgraded to a live adapter only once its
13
+ * design-time layer actually mounts (B1).
14
+ *
15
+ * **Back-compat floor (exact, not "equivalent"):** a single-world project —
16
+ * whether there is no manifest at all (synthesized in memory, see
17
+ * `installEditModeAuthoringForProject` below) or a manifest that happens to
18
+ * declare exactly one world — has NOTHING new to disclose (#28 is a
19
+ * multi-world problem), so `installEditModeAuthoring` deliberately does NOT
20
+ * touch the shared `setActiveAuthoring` slot in that case. `getActiveAuthoring`'s
21
+ * existing default (`new VgaiSceneAuthoringAdapter(store)`, `active-adapter.ts`)
22
+ * keeps serving every call site that reads it (the viewport's
23
+ * transforms/selection, `SaveStatus`, `DefaultEditorLayout`'s hierarchy/
24
+ * inspector panel choice, the autosave guard, undo dispatch) completely
25
+ * untouched — `hasAuthoringOverride()` stays `false`, exactly as today. Only
26
+ * a GENUINE multi-world manifest (2+ declared worlds) installs the composite
27
+ * as the active override, the same "a composite is active -> route through
28
+ * the contract-driven panels" pattern `installMultiWorldAuthoring` already
29
+ * established for play mode (verified precedent: `19-multi-world.spec.ts` /
30
+ * `22-tri-world.spec.ts` assert on `ingest-row`/`ingest-position-*` testids
31
+ * once a composite is installed) — A2's `GameHierarchy` replaces that
32
+ * temporary routing wholesale; until then this is the honest, already-proven
33
+ * mechanism for disclosing a composition #28 says must not stay invisible.
34
+ */
35
+
36
+ import type { AuthoringAdapter } from '@engine/adapter';
37
+ import { editorConsole } from '../editor-console';
38
+ import { getEditorMode } from '../editor-mode';
39
+ import type { EditorStore } from '../editor-store';
40
+ import { resolveInitialScenePathForManifestSynthesis } from '../initial-scene';
41
+ import { fetchRawGameManifest } from '../manifest-project';
42
+ import { getCurrentProject } from '../project-manager';
43
+ import { getStorageBackend } from '../storage';
44
+ import { UIEditStore } from '../ui-editor/ui-edit-store';
45
+ import { createHttpSourceWriteBackend } from '../ui-source/source-write-backend';
46
+ import { getAuthoringOverride, markEditModeOverride, setActiveAuthoring } from './active-adapter';
47
+ import { BoundaryAuthoringAdapter, type BoundaryWorldInfo } from './boundary-authoring-adapter';
48
+ import {
49
+ CompositeAuthoringAdapter,
50
+ type CompositeChild,
51
+ type GameRootManifestProvider,
52
+ } from './composite-authoring-adapter';
53
+ import { UIAuthoringAdapter } from './ui-authoring-adapter';
54
+ import { type UndoAdapter, unregisterUndoAdapter } from './undo-timeline';
55
+ import { VgaiSceneAuthoringAdapter } from './vgai-scene-authoring-adapter';
56
+
57
+ /**
58
+ * C3 (docs/unified-world-editor/26-unified-editor-spec.md §4 C3) — dispatched
59
+ * (on `window`) when the open scene's `ui` roots change in a way that
60
+ * requires REINSTALLING the edit-mode composite (today: only the "Add UI
61
+ * Canvas" hierarchy-menu action, `ui-hierarchy-menu.ts` — going from zero UI
62
+ * roots to one flips whether the scene-UI child exists at all, §3.1's
63
+ * install-gate). Mirrors `root-activation.ts`'s
64
+ * `ROOT_ACTIVATION_REQUEST_EVENT` window-CustomEvent decoupling pattern.
65
+ * `ViewportPanel.tsx` listens and reruns its `installAll()`.
66
+ */
67
+ export const EDIT_MODE_REBUILD_EVENT = 'editor:edit-mode-rebuild';
68
+
69
+ /**
70
+ * The display-relevant slice of a manifest world entry this module needs —
71
+ * deliberately LOOSE (a bare `string` `kind`, not the engine's validated
72
+ * `WorldEntry['kind']` union) so a world that fails schema validation
73
+ * (an unknown kind) can still be represented and surfaced as an error node
74
+ * (#18). Built by the LENIENT parse below (`parseEditModeManifest`), reachable
75
+ * from the production project-open path — NOT gated on strict Zod, which would
76
+ * reject the whole file for one bad world and make every declared world vanish
77
+ * (defect 2).
78
+ */
79
+ export interface EditModeWorldSpec {
80
+ readonly id: string;
81
+ readonly kind: string;
82
+ readonly adapter?: string | undefined;
83
+ readonly scene?: string | undefined;
84
+ readonly entry?: string | undefined;
85
+ readonly zOrder?: number | undefined;
86
+ readonly pausable?: boolean | undefined;
87
+ }
88
+
89
+ export interface EditModeManifest {
90
+ readonly worlds: readonly EditModeWorldSpec[];
91
+ }
92
+
93
+ const KNOWN_WORLD_KINDS = new Set(['threejs', 'pixijs', 'react']);
94
+
95
+ function asString(v: unknown): string | undefined {
96
+ return typeof v === 'string' ? v : undefined;
97
+ }
98
+
99
+ /** Display string for a raw `adapter` field — informational only (the Boundary
100
+ * inspector's read-only `adapter` property). Read defensively: the raw JSON
101
+ * may be a string ('default'), a `{ module }`/`{ ingest }` object, or absent. */
102
+ function displayAdapter(adapter: unknown): string | undefined {
103
+ if (adapter === undefined || adapter === null) return undefined;
104
+ if (typeof adapter === 'string') return adapter;
105
+ if (typeof adapter === 'object') {
106
+ const obj = adapter as Record<string, unknown>;
107
+ if (typeof obj['module'] === 'string') return `module:${obj['module']}`;
108
+ const ingest = obj['ingest'];
109
+ if (ingest && typeof ingest === 'object') {
110
+ const strategy = asString((ingest as Record<string, unknown>)['strategy']);
111
+ return strategy ? `ingest:${strategy}` : 'ingest';
112
+ }
113
+ }
114
+ return undefined;
115
+ }
116
+
117
+ /**
118
+ * LENIENT parse (defect 2): read a raw `vgai.game.json` object's `worlds[]`
119
+ * defensively into `EditModeWorldSpec[]`, so a single bad world (unknown
120
+ * `kind`, missing `scene`+`entry`) becomes an error node (#18) while its VALID
121
+ * siblings still open — never the strict-Zod all-or-nothing of
122
+ * `loadGameManifest`. Returns `null` when there is no usable `worlds` array at
123
+ * all (a present-but-unusable manifest, distinct from an ABSENT one) so the
124
+ * caller can fall back to the synthesized single-world floor. A world missing
125
+ * an `id` gets a positional placeholder so it still surfaces rather than
126
+ * vanishing. `kind` is carried through verbatim (even a bogus string) —
127
+ * `resolveWorldFailureReason` decides resolvability downstream.
128
+ */
129
+ export function parseEditModeManifest(raw: unknown): EditModeManifest | null {
130
+ if (!raw || typeof raw !== 'object') return null;
131
+ const worldsRaw = (raw as Record<string, unknown>)['worlds'];
132
+ if (!Array.isArray(worldsRaw) || worldsRaw.length === 0) return null;
133
+ const worlds: EditModeWorldSpec[] = worldsRaw.map((entry, i) => {
134
+ const w = (entry && typeof entry === 'object' ? entry : {}) as Record<string, unknown>;
135
+ const zOrderRaw = w['zOrder'];
136
+ const pausableRaw = w['pausable'];
137
+ return {
138
+ id: asString(w['id']) ?? `(world ${i})`,
139
+ // Missing/non-string kind is itself unresolvable — carry a sentinel so
140
+ // resolveWorldFailureReason names it, rather than crashing on undefined.
141
+ kind: asString(w['kind']) ?? '(missing kind)',
142
+ adapter: displayAdapter(w['adapter']),
143
+ scene: asString(w['scene']),
144
+ entry: asString(w['entry']),
145
+ zOrder: typeof zOrderRaw === 'number' ? zOrderRaw : undefined,
146
+ pausable: typeof pausableRaw === 'boolean' ? pausableRaw : undefined,
147
+ };
148
+ });
149
+ return { worlds };
150
+ }
151
+
152
+ /**
153
+ * Structural resolvability (#18) — NOT a filesystem check: "missing entry/scene
154
+ * file" here means the world declares NEITHER field (mirrors the manifest
155
+ * schema's own `checkAdapterSceneEntryRules`, `manifest/load.ts`). A live "does
156
+ * the file actually exist on disk" probe is B1 territory (the design-time mount
157
+ * attempt itself degrades to this same error-node floor when a layer throws) —
158
+ * out of scope here, where nothing but the focused world is ever fetched.
159
+ */
160
+ export function resolveWorldFailureReason(world: EditModeWorldSpec): string | null {
161
+ if (!KNOWN_WORLD_KINDS.has(world.kind)) {
162
+ return `Unknown world kind "${world.kind}" — expected one of: threejs, pixijs, react.`;
163
+ }
164
+ if (!world.scene && !world.entry) {
165
+ return `World "${world.id}" declares neither \`scene\` nor \`entry\` — nothing to mount.`;
166
+ }
167
+ return null;
168
+ }
169
+
170
+ function normalizeScenePath(path: string): string {
171
+ return path.startsWith('/') ? path.slice(1) : path;
172
+ }
173
+
174
+ /**
175
+ * Which threejs world is FOCUSED — its scene is the one already loaded into
176
+ * the live store by `loadInitialScene`. Mirrors that function's OWN
177
+ * resolution order (`initial-scene.ts`: `?scene=` URL param wins, else the
178
+ * project's `defaultScene`); if neither names one of the manifest's threejs
179
+ * worlds (or the manifest declares none), falls back to the first threejs
180
+ * world (matches the synthesized single-world manifest, which only ever has
181
+ * exactly one). Exported (pure — no `window`/store reads) so it's directly
182
+ * unit-testable; `installEditModeAuthoring` supplies the ambient
183
+ * `focusScenePath` from `window.location`/the current project.
184
+ */
185
+ export function resolveFocusedThreejsWorldId(
186
+ worlds: readonly EditModeWorldSpec[],
187
+ focusScenePath: string | null | undefined,
188
+ ): string | null {
189
+ const threejsWorlds = worlds.filter((w) => w.kind === 'threejs');
190
+ if (threejsWorlds.length === 0) return null;
191
+ if (focusScenePath) {
192
+ const normalized = normalizeScenePath(focusScenePath);
193
+ const match = threejsWorlds.find(
194
+ (w) => w.scene !== undefined && normalizeScenePath(w.scene) === normalized,
195
+ );
196
+ if (match) return match.id;
197
+ }
198
+ return threejsWorlds[0]!.id;
199
+ }
200
+
201
+ // ---------------------------------------------------------------------------
202
+ // A4 (D8) — ManifestAuthoring: the game root's read-modify-write manifest surface
203
+ // ---------------------------------------------------------------------------
204
+
205
+ /** A raw `vgai.game.json` world entry, read/written defensively — same
206
+ * looseness as {@link EditModeWorldSpec}, but this is the RAW (unknown)
207
+ * object, never Zod-parsed, so unrelated fields survive a round trip. */
208
+ type RawWorldEntry = Record<string, unknown>;
209
+
210
+ function findRawWorld(raw: Record<string, unknown>, worldId: string): RawWorldEntry | undefined {
211
+ const worlds = raw['worlds'];
212
+ if (!Array.isArray(worlds)) return undefined;
213
+ return worlds.find(
214
+ (w): w is RawWorldEntry =>
215
+ !!w && typeof w === 'object' && (w as RawWorldEntry)['id'] === worldId,
216
+ );
217
+ }
218
+
219
+ /**
220
+ * ManifestAuthoring (A4, D8) — the game root's manifest-backed
221
+ * {@link GameRootManifestProvider}: holds the RAW parsed `vgai.game.json`
222
+ * object (read-modify-write — NEVER round-tripped through the strict Zod
223
+ * schema, so fields this module doesn't know about survive untouched) and
224
+ * writes it back via `POST /__editor/manifest` (the one manifest-write verb,
225
+ * `editor-server.ts`).
226
+ *
227
+ * Two write modes:
228
+ * - a REAL manifest (`synthesized: false`) writes through on every edit —
229
+ * the acceptance's "round-trips vgai.game.json on disk" needs no separate
230
+ * save step.
231
+ * - a SYNTHESIZED manifest (`synthesized: true`, no `vgai.game.json` on disk
232
+ * yet) BUFFERS edits in memory only; nothing is written until the
233
+ * explicit `materialize()` call (the generic inspector's "Create
234
+ * vgai.game.json" action). In production this branch is currently
235
+ * UNREACHABLE end-to-end: `installEditModeAuthoring` only ever installs a
236
+ * game root when 2+ worlds are declared, and `synthesizeSingleWorldManifest`
237
+ * (the only source of a `synthesized: true` manifest) always produces
238
+ * exactly ONE world — so a synthesized manifest never gets a game root
239
+ * through today's wiring. The buffering logic is still implemented and
240
+ * unit-tested for correctness/forward-compatibility (see this module's
241
+ * tests), and the limitation is called out explicitly rather than silently
242
+ * assumed away.
243
+ *
244
+ * File-watcher landmine: `chokidar` only watches `public/` (`editor-server.ts`),
245
+ * so a manifest write at the PROJECT ROOT emits no SSE — `notifyIngestEdit()`
246
+ * after every successful write is what keeps the hierarchy panel's badges
247
+ * live instead of stale until reopen.
248
+ */
249
+ export class ManifestAuthoring implements GameRootManifestProvider {
250
+ private readonly raw: Record<string, unknown>;
251
+ readonly synthesized: boolean;
252
+ private readonly store: EditorStore;
253
+
254
+ constructor(raw: Record<string, unknown>, synthesized: boolean, store: EditorStore) {
255
+ this.raw = raw;
256
+ this.synthesized = synthesized;
257
+ this.store = store;
258
+ }
259
+
260
+ getGameName(): string {
261
+ const name = this.raw['name'];
262
+ return typeof name === 'string' ? name : '';
263
+ }
264
+
265
+ setGameName(name: string): void {
266
+ this.raw['name'] = name;
267
+ this.persist();
268
+ }
269
+
270
+ getWorldZOrder(worldId: string): number {
271
+ const z = findRawWorld(this.raw, worldId)?.['zOrder'];
272
+ return typeof z === 'number' ? z : 0;
273
+ }
274
+
275
+ setWorldZOrder(worldId: string, value: number): void {
276
+ const world = findRawWorld(this.raw, worldId);
277
+ if (!world) return;
278
+ world['zOrder'] = value;
279
+ this.persist();
280
+ }
281
+
282
+ getWorldPausable(worldId: string): boolean {
283
+ const p = findRawWorld(this.raw, worldId)?.['pausable'];
284
+ return typeof p === 'boolean' ? p : true;
285
+ }
286
+
287
+ setWorldPausable(worldId: string, value: boolean): void {
288
+ const world = findRawWorld(this.raw, worldId);
289
+ if (!world) return;
290
+ world['pausable'] = value;
291
+ this.persist();
292
+ }
293
+
294
+ /** The "Create vgai.game.json" action (generic inspector, while
295
+ * `synthesized`) — materializes the buffered edits to disk. Also safe to
296
+ * call on a non-synthesized instance (equivalent to an explicit re-save). */
297
+ async materialize(): Promise<boolean> {
298
+ return this.write();
299
+ }
300
+
301
+ private persist(): void {
302
+ if (this.synthesized) return; // buffered only — wait for materialize()
303
+ void this.write();
304
+ }
305
+
306
+ private async write(): Promise<boolean> {
307
+ const content = JSON.stringify(this.raw, null, 2);
308
+ try {
309
+ // Phase A cleanup: `vgai.game.json` is project-root-relative, the same
310
+ // namespace `authoring/world-overlay.ts`/`authoring/ingest-overlay.ts`
311
+ // already mode-branch on — browser mode has no `/__editor/*` server at
312
+ // all, so this used to silently fail every manifest edit there (caught
313
+ // below, logged, never surfaced beyond the console). Browser mode goes
314
+ // straight through `getStorageBackend()` (OPFS/FSA root at the TRUE
315
+ // project folder, no `public/` insertion); server mode is unchanged.
316
+ if (getEditorMode() === 'browser') {
317
+ await getStorageBackend().write('vgai.game.json', content);
318
+ } else {
319
+ const res = await fetch('/__editor/manifest', {
320
+ method: 'POST',
321
+ headers: { 'Content-Type': 'application/json' },
322
+ body: JSON.stringify({ path: 'vgai.game.json', content }),
323
+ });
324
+ if (!res.ok) return false;
325
+ }
326
+ // No SSE for a project-root write (file-watcher only covers `public/`)
327
+ // — notify directly so the hierarchy panel's badges refresh in place.
328
+ this.store.notifyIngestEdit();
329
+ return true;
330
+ } catch (err) {
331
+ editorConsole.error(`Failed to write vgai.game.json: ${err}`, 'project');
332
+ return false;
333
+ }
334
+ }
335
+ }
336
+
337
+ /** Children built by the last `installEditModeAuthoring` call — tracked so
338
+ * `exitEditModeAuthoring` can unregister any that self-registered with the
339
+ * undo timeline (mirrors `play-mode.ts`'s `_multiWorldChildren`/
340
+ * `exitMultiWorldAuthoring`). Boundary adapters never register (no `undo`/
341
+ * `redo`); the focused `VgaiSceneAuthoringAdapter` delegates undo/redo to
342
+ * the store, which registers ITSELF once at construction
343
+ * (`editor-store.ts`) — so this is currently always a no-op in practice,
344
+ * kept for the same defensive reason `play-mode.ts`'s guard is: a future
345
+ * live child adapter (B1's `ReactWorldAuthoringAdapter`) DOES self-register. */
346
+ let _editModeChildren: AuthoringAdapter[] = [];
347
+
348
+ /**
349
+ * C3 / R2 — the `UIEditStore`(s) built for this session's scene-UI child(ren)
350
+ * (today: at most one, the focused world's). Tracked SEPARATELY from
351
+ * `_editModeChildren`: the undo timeline registers the `UIEditStore` itself
352
+ * (`ui-edit-store.ts`'s constructor, `registerUndoAdapter(this)`), NOT the
353
+ * `UIAuthoringAdapter` wrapping it — `exitEditModeAuthoring`'s generic
354
+ * `_editModeChildren` loop below unregisters the ADAPTER, which was never in
355
+ * the undo timeline's `live` set (a harmless no-op for it), so the real store
356
+ * needs its OWN explicit unregister here, exactly what the retired standalone
357
+ * UI-edit mode's own teardown used to do.
358
+ */
359
+ let _editModeUIStores: UIEditStore[] = [];
360
+
361
+ /**
362
+ * C3 — build the scene-UI child for `focusedWorldId`'s composite when the
363
+ * open scene has authored UI roots (`store.getSceneUI().length > 0`,
364
+ * §3.1's install gate — a ui-less scene gets no child at all, keeping the
365
+ * A1 single-world back-compat floor byte-identical). Mirrors the retired
366
+ * standalone UI-edit mode's store/adapter construction, minus its
367
+ * demo-fixture fallback (Q3: "should never have existed" — dropped by
368
+ * design, not lost; see the harness/15/16 for the demo fixture's only
369
+ * remaining home) and its own `setActiveAuthoring` call (the shared
370
+ * composite install below already does that once, for every child).
371
+ */
372
+ function buildUIChild(store: EditorStore, focusedWorldId: string, zOrder: number): CompositeChild {
373
+ const uiStore = new UIEditStore(store.getSceneUI(), {
374
+ // Persist every committed edit back into the open scene's `ui` + autosave
375
+ // (C9) — the exact same commit path the retired standalone UI-edit
376
+ // mode's `onCommit` used.
377
+ onCommit: (next) => store.setSceneUI(next),
378
+ });
379
+ _editModeUIStores.push(uiStore);
380
+ const adapter = new UIAuthoringAdapter(uiStore, {
381
+ ...(import.meta.env.DEV ? { writeBackend: createHttpSourceWriteBackend() } : {}),
382
+ });
383
+ return {
384
+ worldId: `${focusedWorldId}:ui`,
385
+ kind: 'scene-ui',
386
+ adapter,
387
+ zOrder,
388
+ parentWorldId: focusedWorldId,
389
+ };
390
+ }
391
+
392
+ /**
393
+ * B1 — register a child adapter minted AFTER `installEditModeAuthoring`
394
+ * already ran (a design-time layer's successful mount, which upgrades a
395
+ * world's Boundary adapter to a live one via `CompositeAuthoringAdapter.
396
+ * replaceChild` — `design-time-layers.ts`). Mirrors the constructor-time
397
+ * seeding above (`_editModeChildren = children.map(...)`) so `exitEditModeAuthoring`
398
+ * unregisters this adapter from the undo timeline too, exactly like every
399
+ * child present at install time. A no-op call with an adapter that never
400
+ * self-registered (no `undo`/`redo`) is harmless — `exitEditModeAuthoring`'s
401
+ * loop already guards on that pair being present.
402
+ */
403
+ export function registerEditModeChild(adapter: AuthoringAdapter): void {
404
+ _editModeChildren.push(adapter);
405
+ }
406
+
407
+ /**
408
+ * Build the composite over every declared world and — ONLY when there is
409
+ * more than one world to disclose — install it as the active authoring
410
+ * override (see this module's header comment for why the single-world case
411
+ * deliberately leaves the shared slot untouched). Always returns the built
412
+ * composite so callers (tests) can inspect it regardless of whether it was
413
+ * installed as the override.
414
+ *
415
+ * The focused threejs world is whichever one's scene `loadInitialScene`
416
+ * already loaded into the store (URL `?scene=` / `defaultScene` order); it
417
+ * gets the real `VgaiSceneAuthoringAdapter`. A1 deliberately ships NO
418
+ * focus-SWITCH (loading a DIFFERENT world's scene into the store) — that
419
+ * requires a dirty-check-before-switch prompt + the scene-load path and lands
420
+ * in A4 ({@link switchFocusedWorld} is the explicit, loud stub). Accepting a
421
+ * "focus this other world" override here would label the new world's identity
422
+ * onto the OLD world's still-loaded document — a silent misattribution
423
+ * (defect 5) — so that parameter was removed.
424
+ *
425
+ * `rawManifestInfo` (A4, D8, optional) — the RAW parsed `vgai.game.json` +
426
+ * whether it was synthesized (never written to disk); when given AND the
427
+ * composite ends up with 2+ children, a `ManifestAuthoring` is built over it
428
+ * and passed to `CompositeAuthoringAdapter` as its game-root manifest
429
+ * surface. Omitted (every existing unit-test call site) ⇒ no game root, the
430
+ * exact pre-A4 shape — see `CompositeAuthoringAdapter`'s own doc comment.
431
+ */
432
+ export function installEditModeAuthoring(
433
+ store: EditorStore,
434
+ manifest: EditModeManifest,
435
+ rawManifestInfo?: { raw: Record<string, unknown>; synthesized: boolean },
436
+ ): CompositeAuthoringAdapter {
437
+ const urlParams = new URLSearchParams(window.location.search);
438
+ const sceneParam = urlParams.get('scene');
439
+ const defaultScene = getCurrentProject()?.config.defaultScene ?? undefined;
440
+ const focusScenePath = sceneParam ?? defaultScene ?? null;
441
+ const focusedWorldId = resolveFocusedThreejsWorldId(manifest.worlds, focusScenePath);
442
+
443
+ const children: CompositeChild[] = manifest.worlds.map((world) => {
444
+ // D12 (B4) — install-time zOrder for every child, not just the Boundary's
445
+ // own inspector-displayed value: `childAdapters()`'s pick/paint-order
446
+ // consumers (the layered pick walk, the hierarchy zOrder badge) need it
447
+ // off ANY child, including the focused threejs world.
448
+ const zOrder = world.zOrder ?? 0;
449
+ if (world.id === focusedWorldId) {
450
+ return {
451
+ worldId: world.id,
452
+ kind: world.kind,
453
+ adapter: new VgaiSceneAuthoringAdapter(store),
454
+ zOrder,
455
+ };
456
+ }
457
+ const reason = resolveWorldFailureReason(world);
458
+ const info: BoundaryWorldInfo = {
459
+ id: world.id,
460
+ kind: world.kind,
461
+ adapter: world.adapter,
462
+ entryOrScenePath: world.scene ?? world.entry,
463
+ zOrder,
464
+ pausable: world.pausable ?? true,
465
+ };
466
+ return {
467
+ worldId: world.id,
468
+ kind: world.kind,
469
+ adapter: new BoundaryAuthoringAdapter(store, info, reason ?? undefined),
470
+ zOrder,
471
+ };
472
+ });
473
+
474
+ // C3 — the focused world's scene-UI child (§3.1): only when the open scene
475
+ // actually has UI roots to disclose, and only when there IS a focused
476
+ // threejs world to nest it under (a manifest declaring zero threejs worlds
477
+ // has nowhere to attach one — an edge case `resolveFocusedThreejsWorldId`
478
+ // already surfaces as `null`). Counts toward the `children.length > 1`
479
+ // install gate below BY DESIGN (R5): a single-world project that already
480
+ // authored UI content gets the composite + the nested "ui (scene-ui)" row;
481
+ // a ui-less single-world project's hierarchy shape is untouched.
482
+ if (focusedWorldId && store.getSceneUI().length > 0) {
483
+ const focusedChild = children.find((c) => c.worldId === focusedWorldId);
484
+ children.push(buildUIChild(store, focusedWorldId, focusedChild?.zOrder ?? 0));
485
+ }
486
+
487
+ // A4 (D8) — the game root only makes sense once there IS a composition to
488
+ // disclose (2+ worlds); a single-world project (synthesized or a real
489
+ // 1-world manifest) never installs the composite as the override at all
490
+ // (below), so it would never be reachable regardless.
491
+ const gameRoot =
492
+ rawManifestInfo && children.length > 1
493
+ ? new ManifestAuthoring(rawManifestInfo.raw, rawManifestInfo.synthesized, store)
494
+ : undefined;
495
+ const composite = new CompositeAuthoringAdapter(children, gameRoot);
496
+ _editModeChildren = children.map((c) => c.adapter);
497
+
498
+ // A1 delta (Wave-1 Phase-A close-out, spec:232-235, spec §3) — the ORIGINAL
499
+ // floor here was bare `children.length > 1`: a manifest whose only world is
500
+ // react (or any world-set with NO focused threejs world at all) never met
501
+ // it, so that world never entered the composite/hierarchy and
502
+ // `WorldSelectionOverlay` self-gated off, even though a real world exists
503
+ // to disclose. `focusedWorldId === null` (see `resolveFocusedThreejsWorldId`'s
504
+ // own doc comment: it returns `null` exactly when the manifest declares NO
505
+ // threejs world) with at least one child is that no-threejs case — install
506
+ // the composite there too. The single-threejs-world floor above (`children.
507
+ // length === 1` AND that one world IS the focused threejs world, so
508
+ // `focusedWorldId` is non-null) is UNCHANGED and must stay so — see the doc
509
+ // comment at this file's top (~:15-33) explaining why that back-compat
510
+ // floor is exact, not "equivalent". `mountDesignTimeLayers` already handles
511
+ // the layer mount for the newly-activated no-threejs case unchanged — no
512
+ // parallel react-only entry point is added here (spec-27 E3 all-worlds
513
+ // generalization).
514
+ const noFocusedThreejsWorld = focusedWorldId === null;
515
+ if (children.length > 1 || (children.length >= 1 && noFocusedThreejsWorld)) {
516
+ // Brand BEFORE installing so the store's save guard sees it the instant
517
+ // the override is live (defect 1: the focused first-party child must be
518
+ // able to Ctrl+S/autosave through the store, unlike a foreign override).
519
+ markEditModeOverride(composite);
520
+ setActiveAuthoring(composite);
521
+ store.notifyIngestEdit();
522
+ }
523
+
524
+ return composite;
525
+ }
526
+
527
+ /** Teardown symmetric to `installEditModeAuthoring` — clears the override
528
+ * ONLY if it is still exactly the composite this module last installed
529
+ * (never clobbers a DIFFERENT session, e.g. ingest/play, that took over
530
+ * afterward), and unregisters any child that self-registered with the undo
531
+ * timeline. */
532
+ export function exitEditModeAuthoring(installed?: CompositeAuthoringAdapter): void {
533
+ if (installed && getAuthoringOverride() === installed) {
534
+ setActiveAuthoring(null);
535
+ }
536
+ for (const adapter of _editModeChildren) {
537
+ if (adapter.undo && adapter.redo) {
538
+ unregisterUndoAdapter(adapter as AuthoringAdapter & UndoAdapter);
539
+ }
540
+ }
541
+ _editModeChildren = [];
542
+ // R2 — see `_editModeUIStores`'s doc comment: the real undo-timeline
543
+ // registrant for a scene-UI child is its `UIEditStore`, not the
544
+ // `UIAuthoringAdapter` the loop above already (harmlessly, no-op) tried.
545
+ for (const uiStore of _editModeUIStores) {
546
+ unregisterUndoAdapter(uiStore);
547
+ }
548
+ _editModeUIStores = [];
549
+ }
550
+
551
+ /**
552
+ * Focus-SWITCH (A4, defect 5): switching which threejs world is live requires
553
+ * (a) a dirty-check-before-switch prompt and (b) LOADING the target world's
554
+ * scene into the store — neither of which A1 ships. This explicit stub exists
555
+ * so a future caller gets a loud, named failure instead of A1 silently
556
+ * rebuilding a composite that labels the target world's identity onto the
557
+ * currently-loaded document. A1's composite always focuses the world
558
+ * `loadInitialScene` already loaded.
559
+ */
560
+ export function switchFocusedWorld(): never {
561
+ throw new Error(
562
+ 'Focus-switch (loading a different threejs world into the store, with a ' +
563
+ 'dirty-check prompt) is not implemented in A1 — it lands in A4. A1 installs the ' +
564
+ 'composite over the world loadInitialScene already loaded.',
565
+ );
566
+ }
567
+
568
+ /**
569
+ * Project-open wiring: fetch the RAW `vgai.game.json` and install the edit-mode
570
+ * composite via the LENIENT parse (defect 2) — a manifest with one bad world
571
+ * still opens its valid worlds, surfacing the bad one as an error node (#18).
572
+ * ONLY a genuinely ABSENT manifest (the route 404s → `fetchRawGameManifest`
573
+ * returns `null`), or one so malformed it has no usable `worlds` array,
574
+ * synthesizes a single `{ id: 'main', kind: 'threejs', scene: <resolved> }`
575
+ * game IN MEMORY (never written to disk), where `<resolved>` mirrors
576
+ * `loadInitialScene`'s OWN resolution order. This is the only call site that
577
+ * should call `installEditModeAuthoring` at ordinary project open
578
+ * (`ViewportPanel.tsx`).
579
+ */
580
+ export async function installEditModeAuthoringForProject(
581
+ store: EditorStore,
582
+ ): Promise<CompositeAuthoringAdapter> {
583
+ let manifest: EditModeManifest;
584
+ // A4 (D8) — the RAW object backing `ManifestAuthoring` (read-modify-write,
585
+ // never round-tripped through Zod). `synthesized: true` when there is no
586
+ // real `vgai.game.json` on disk (a 404, or one so malformed it has no
587
+ // usable `worlds` array) — see `ManifestAuthoring`'s doc comment for why
588
+ // this branch is unreachable end-to-end today (synthesis always produces
589
+ // exactly one world; a game root only installs for 2+).
590
+ let rawManifestInfo: { raw: Record<string, unknown>; synthesized: boolean };
591
+ try {
592
+ const raw = await fetchRawGameManifest();
593
+ const parsed = raw !== null && parseEditModeManifest(raw);
594
+ if (parsed) {
595
+ manifest = parsed;
596
+ rawManifestInfo = { raw: raw as Record<string, unknown>, synthesized: false };
597
+ } else {
598
+ manifest = synthesizeSingleWorldManifest();
599
+ rawManifestInfo = { raw: synthesizeRawManifest(manifest), synthesized: true };
600
+ }
601
+ } catch (err) {
602
+ editorConsole.error(
603
+ `Failed to load vgai.game.json, falling back to a synthesized single-world game: ${err}`,
604
+ 'project',
605
+ );
606
+ manifest = synthesizeSingleWorldManifest();
607
+ rawManifestInfo = { raw: synthesizeRawManifest(manifest), synthesized: true };
608
+ }
609
+ return installEditModeAuthoring(store, manifest, rawManifestInfo);
610
+ }
611
+
612
+ function synthesizeSingleWorldManifest(): EditModeManifest {
613
+ return {
614
+ worlds: [
615
+ {
616
+ id: 'main',
617
+ kind: 'threejs',
618
+ scene: resolveInitialScenePathForManifestSynthesis(getCurrentProject()) ?? undefined,
619
+ },
620
+ ],
621
+ };
622
+ }
623
+
624
+ /** A minimally-valid RAW manifest object matching a synthesized in-memory
625
+ * game (A4) — never written to disk on its own; only feeds `ManifestAuthoring`
626
+ * for the (currently unreachable, see that class's doc comment) 1-world
627
+ * "Create vgai.game.json" path. */
628
+ function synthesizeRawManifest(manifest: EditModeManifest): Record<string, unknown> {
629
+ return {
630
+ manifestVersion: 1,
631
+ name: getCurrentProject()?.config.name ?? 'My Game',
632
+ version: '0.1.0',
633
+ engine: { version: '0.0.0' },
634
+ worlds: manifest.worlds.map((w) => ({
635
+ id: w.id,
636
+ kind: w.kind,
637
+ ...(w.scene !== undefined ? { scene: w.scene } : {}),
638
+ ...(w.entry !== undefined ? { entry: w.entry } : {}),
639
+ zOrder: w.zOrder ?? 0,
640
+ pausable: w.pausable ?? true,
641
+ })),
642
+ };
643
+ }