@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,325 @@
1
+ /**
2
+ * UIAuthoringAdapter — drives the UI editor through the SAME format-neutral
3
+ * {@link AuthoringAdapter} contract the 3D editor uses (K6). It wraps a
4
+ * {@link UIEditStore} (the .vscn `ui` tree) and exposes hierarchy / selection /
5
+ * inspector / LAYOUT (K7, NOT transform — UI has no 3D gizmo) / structure /
6
+ * persistence / undo. `object3D` is always null (UI nodes are DOM, not Object3D);
7
+ * the editor's UI selection surface is the DOM overlay, parallel to the 3D viewport
8
+ * (architectural_decision §C).
9
+ *
10
+ * It is registered via `setActiveAuthoring` directly by the standalone harness
11
+ * (`ui-editor/harness.tsx`), and — as a `kind: 'scene-ui'` child of the edit-mode
12
+ * `CompositeAuthoringAdapter` — by `authoring/edit-mode-authoring.ts` (C3,
13
+ * docs/unified-world-editor/26-unified-editor-spec.md §4 C3): the panel-swap into
14
+ * the main editor this doc comment used to call "the remaining integration" has
15
+ * landed — a UI node is now a layer node under its owning world in the ONE
16
+ * hierarchy, and this adapter's `pickable` (below) drives B4 click-select for it.
17
+ */
18
+ import type {
19
+ AuthoringAdapter,
20
+ AuthoringCapabilities,
21
+ EditorNode,
22
+ HierarchyProvider,
23
+ InspectorProvider,
24
+ LayoutProvider,
25
+ PersistenceProvider,
26
+ PickProvider,
27
+ PropertyDescriptor,
28
+ SelectionProvider,
29
+ StructureProvider,
30
+ } from '@engine/adapter';
31
+ import type { UINode } from '@engine/scene/schema';
32
+ import { UIAnimationController } from '@vgai/scene-ui/animation';
33
+ import { findNode, getNodePath, walk } from '../ui-editor/node-ops';
34
+ import { hitTest } from '../ui-editor/react-store';
35
+ import type { UIEditStore } from '../ui-editor/ui-edit-store';
36
+ import type { SourceWriteBackend } from '../ui-source/source-write-backend';
37
+
38
+ /** C3 — the shell's generic "Create ▸" kind list (`structure.creatableKinds`
39
+ * below): every kind `defaultNodeForKind` (`ui-edit-store.ts`) seeds a
40
+ * sensible default for. Containers (`box`/`panel`/`grid`) can themselves be
41
+ * a `create` target; kept intentionally smaller than the harness palette's
42
+ * full set (`ui-editor/palette.tsx`) — that richer picker stays harness-only. */
43
+ const CREATABLE_KINDS: ReadonlyArray<{ kind: string; label: string }> = [
44
+ { kind: 'box', label: 'Box' },
45
+ { kind: 'panel', label: 'Panel' },
46
+ { kind: 'grid', label: 'Grid' },
47
+ { kind: 'text', label: 'Text' },
48
+ { kind: 'button', label: 'Button' },
49
+ { kind: 'image', label: 'Image' },
50
+ { kind: 'input', label: 'Input' },
51
+ { kind: 'toggle', label: 'Toggle' },
52
+ { kind: 'slider', label: 'Slider' },
53
+ { kind: 'progress', label: 'Progress' },
54
+ ];
55
+
56
+ function toEditorNode(node: UINode, parentId: string | null): EditorNode {
57
+ return {
58
+ id: node.id as string,
59
+ label: node.name,
60
+ kind: node.kind,
61
+ parentId,
62
+ childIds: (node.children ?? []).map((c) => c.id as string),
63
+ flags: { locked: node.locked ?? false },
64
+ };
65
+ }
66
+
67
+ export class UIAuthoringAdapter implements AuthoringAdapter {
68
+ readonly capabilities: AuthoringCapabilities;
69
+ /**
70
+ * T3.2 slice 3: the source-write flush seam (design §4). Absent ⇒ no dev-server
71
+ * backend in this session (a hosted/browser build) — see `../ui-source/source-write-backend.ts`.
72
+ *
73
+ * NOT folded into `capabilities.persist` (deliberately — reported as a premise
74
+ * mismatch in the T3.2 slice 3 report): `EditorStore._activePersistence()` gates
75
+ * ALL autosave for whichever adapter is active on `capabilities.persist`,
76
+ * including the SEPARATE, already-real ui-tree persistence path (`onCommit` →
77
+ * `EditorStore.setSceneUI` → the normal `.vscn` autosave, unconditional today).
78
+ * Flipping `capabilities.persist` off when no source-write backend exists would
79
+ * silently disable THAT working path too, in every hosted/no-dev-server build —
80
+ * not the intended effect. `capabilities.persist` therefore stays `true`
81
+ * (matching pre-slice-3 behavior); backend absence is instead surfaced honestly
82
+ * through `persistence.destination` below.
83
+ */
84
+ private readonly writeBackend: SourceWriteBackend | undefined;
85
+
86
+ /**
87
+ * C3 (D12/B4) — the live viewport DOM element to hit-test against,
88
+ * attached once the unified editor's scene-UI design-time layer mounts
89
+ * (`authoring/scene-ui-layer.ts`) and detached on its teardown. `null` in
90
+ * the standalone harness (which never attaches one) and briefly between
91
+ * composite-install and layer-mount — `pickable.pick` degrades to `null`
92
+ * either way, never throws.
93
+ */
94
+ private _pickSurface: HTMLElement | null = null;
95
+
96
+ /**
97
+ * D4 (spec27 §6 D4, layer-tree "lock" toggle) — SESSION-LOCAL, never
98
+ * persisted: `UINode` dropped its `locked` schema field as a dead field
99
+ * with no runtime reader (CLAUDE.md, `ui.ts` — same fate as `tags`), so a
100
+ * scene-UI node's lock state can't round-trip through `.vscn` either. See
101
+ * `ReactWorldAuthoringAdapter.lockedIds`'s doc comment for the identical
102
+ * reasoning. D4.R1 — now also read by `pickable.pick` below, so a locked
103
+ * node is skipped by canvas click/marquee pick, matching the first-party
104
+ * threejs raycast's own locked-skip behavior (`viewport-raycast.ts`).
105
+ */
106
+ private readonly lockedIds = new Set<string>();
107
+
108
+ /**
109
+ * E1.b (spec-27 §7 E1) — the ONE `UIAnimationController` shared by every
110
+ * consumer of this child's timeline: the design-time stage
111
+ * (`scene-ui-layer.ts`'s `SceneUILayerRoot`, live preview) AND the unified
112
+ * editor's keyframe timeline panel (`scene-ui-mode/scene-ui-timeline-tab.tsx`,
113
+ * playback/scrub controls). Owned here (not `useMemo`'d separately in each
114
+ * mount) so scrubbing the timeline moves the SAME preview the stage renders
115
+ * — the harness's `UIEditor.tsx` got this for free by constructing both in
116
+ * one component; the unified editor mounts the stage and the timeline as
117
+ * two independent React roots, so the shared instance has to live on the
118
+ * adapter both already hold a reference to.
119
+ */
120
+ readonly anim = new UIAnimationController();
121
+
122
+ constructor(
123
+ private readonly store: UIEditStore,
124
+ opts: { writeBackend?: SourceWriteBackend } = {},
125
+ ) {
126
+ this.writeBackend = opts.writeBackend;
127
+ this.capabilities = {
128
+ transform: false,
129
+ material: false,
130
+ inspectorFields: true,
131
+ create: true,
132
+ delete: true,
133
+ reparent: true,
134
+ persist: true,
135
+ layout: true,
136
+ };
137
+ }
138
+
139
+ /** C3 (D12) — geometric hit-test over the attached pick surface, reusing
140
+ * the SAME `hitTest` primitive the harness's `SelectionOverlay` already
141
+ * uses for in-realm hover/click (`ui-editor/react-store.ts`) — the B4-
142
+ * blessed geometric `data-ui-id` rect test, not `elementFromPoint`.
143
+ *
144
+ * D4.R1 — a `lockedIds` member is SKIPPED by the hit-test (falls through
145
+ * to whatever unlocked element is behind it, or `null`), closing the gap
146
+ * where the layer-tree/overlay lock toggle gated undo/round-trip but
147
+ * nothing on the pick path — see `lockedIds`'s doc comment. */
148
+ readonly pickable: PickProvider = {
149
+ pick: (clientX, clientY) =>
150
+ this._pickSurface
151
+ ? hitTest(this._pickSurface, clientX, clientY, (id) => this.lockedIds.has(id))
152
+ : null,
153
+ };
154
+
155
+ /** Attach/detach the live stage element `pickable.pick` hit-tests against
156
+ * (`scene-ui-layer.ts`'s mount/dispose). */
157
+ attachPickSurface(el: HTMLElement): void {
158
+ this._pickSurface = el;
159
+ }
160
+ detachPickSurface(): void {
161
+ this._pickSurface = null;
162
+ }
163
+
164
+ // --- hierarchy ---
165
+ readonly hierarchy: HierarchyProvider = {
166
+ roots: () => this.store.roots.map((r) => toEditorNode(r, null)),
167
+ node: (id) => {
168
+ const loc = findNode(this.store.roots, id);
169
+ return loc ? toEditorNode(loc.node, loc.parent?.id ?? null) : null;
170
+ },
171
+ object3D: () => null, // UI nodes are DOM, not Object3D
172
+ idForObject3D: () => null,
173
+ };
174
+
175
+ // --- selection ---
176
+ readonly selection: SelectionProvider = {
177
+ get: () => this.store.selection,
178
+ set: (ids) => this.store.select(ids),
179
+ };
180
+
181
+ // --- inspector (schema-driven property descriptors) ---
182
+ readonly inspector: InspectorProvider = {
183
+ properties: (id) => this.describe(id),
184
+ get: (id, path) => {
185
+ // D4 — see `lockedIds`'s doc comment; `visible` is a real `UINode`
186
+ // field already reached generically via `getNodePath` below, so it
187
+ // needs no special case here.
188
+ if (path === 'locked') return this.lockedIds.has(id);
189
+ const loc = findNode(this.store.roots, id);
190
+ return loc ? getNodePath(loc.node, path) : undefined;
191
+ },
192
+ set: (id, path, value) => {
193
+ if (path === 'locked') {
194
+ if (value) this.lockedIds.add(id);
195
+ else this.lockedIds.delete(id);
196
+ return;
197
+ }
198
+ this.store.setProp(id, path, value);
199
+ },
200
+ };
201
+
202
+ // --- layout (K7) ---
203
+ readonly layout: LayoutProvider = {
204
+ get: (id) => {
205
+ const loc = findNode(this.store.roots, id);
206
+ return (loc?.node.layout as Record<string, unknown> | undefined) ?? null;
207
+ },
208
+ beginEdit: () => this.store.beginEdit(),
209
+ apply: (id, patch) => {
210
+ for (const [k, v] of Object.entries(patch)) this.store.previewSetProp(id, `layout.${k}`, v);
211
+ },
212
+ endEdit: () => this.store.endEdit(),
213
+ };
214
+
215
+ // --- structure ---
216
+ readonly structure: StructureProvider = {
217
+ create: (kind, parentId) =>
218
+ this.store.addNode(parentId ?? (this.store.roots[0]?.id as string), kind as UINode['kind']),
219
+ remove: (id) => this.store.remove(id),
220
+ // D4.R2 — batch a multi-node delete into the store's ONE-commit
221
+ // `removeMany` (a single undo step for the whole selection) instead of
222
+ // this shell looping `remove(id)` N times (N undo steps). Cheap here:
223
+ // `UIEditStore`'s undo is an in-memory JSON-tree snapshot per commit, no
224
+ // async/network round trip to sequence — unlike the react-world
225
+ // adapter's whole-file source writes, there's no race to guard against.
226
+ removeMany: (ids) => this.store.removeMany(ids),
227
+ duplicate: (id) => this.store.duplicate(id) ?? id,
228
+ reparent: (id, newParentId) => {
229
+ if (newParentId) this.store.reparent(id, newParentId);
230
+ },
231
+ // C3 — drives the shell's generic "Create ▸" hierarchy-menu submenu
232
+ // (`GameHierarchy.tsx`), the SAME contract-only affordance every other
233
+ // adapter's rows already get; the richer drag/drop palette (K1) stays
234
+ // harness-only (`ui-editor/palette.tsx`, spec-27 E1 scope). Every kind
235
+ // `defaultNodeForKind` (`ui-edit-store.ts`) actually knows how to seed.
236
+ creatableKinds: () => [...CREATABLE_KINDS],
237
+ };
238
+
239
+ // --- persistence ---
240
+ // De-stubbed (T3.2 slice 3, design §4) over the `SourceWriteBackend` seam. A class
241
+ // GETTER, not a field initializer — a field initializer runs before the
242
+ // constructor body assigns `this.writeBackend` (the slice-1 ordering bug; see
243
+ // `vgai-scene-authoring-adapter.ts` for the pattern).
244
+ //
245
+ // What "de-stubbed" means here, concretely: `SourceEditPanel` writes every source
246
+ // edit to disk IMMEDIATELY via fetch the instant it is committed (Enter/blur) —
247
+ // there is NO pending-write queue anywhere in this architecture. `save()`
248
+ // therefore has nothing to flush, backend or not; its honest implementation is a
249
+ // no-op (see `../ui-source/source-write-backend.ts`'s doc comment for the
250
+ // investigation). The real de-stub delivered here is `destination` truthfully
251
+ // reporting whether a backend even EXISTS in this session, instead of the old
252
+ // hard-coded "save() not yet wired" stub text.
253
+ get persistence(): PersistenceProvider {
254
+ const backend = this.writeBackend;
255
+ const store = this.store;
256
+ return {
257
+ isDirty: () => store.canUndo,
258
+ save: async () => {
259
+ // Nothing pending to flush (immediate-write architecture) — see doc
260
+ // comment above. The ui-tree's OWN persistence (layout/prop edits → the
261
+ // open scene) is a separate, unconditional path (`onCommit` →
262
+ // `EditorStore.setSceneUI`) that does not route through this method.
263
+ },
264
+ serialize: () => store.serialize(),
265
+ destination: backend
266
+ ? 'component source (JSX, via /__ui-source — writes are immediate; nothing pending to flush)'
267
+ : 'component source (JSX) — no source-write backend in this session (hosted/no dev server)',
268
+ };
269
+ }
270
+
271
+ undo(): void {
272
+ this.store.undo();
273
+ }
274
+ redo(): void {
275
+ this.store.redo();
276
+ }
277
+ subscribe(listener: () => void): () => void {
278
+ return this.store.subscribe(listener);
279
+ }
280
+
281
+ /** Build property descriptors for a node (inspector schema). */
282
+ private describe(id: string): PropertyDescriptor[] {
283
+ const loc = findNode(this.store.roots, id);
284
+ if (!loc) return [];
285
+ const node = loc.node;
286
+ const out: PropertyDescriptor[] = [
287
+ { path: 'name', label: 'name', type: 'string' },
288
+ { path: 'visible', label: 'visible', type: 'boolean' },
289
+ { path: 'layout.width', label: 'width', type: 'number' },
290
+ { path: 'layout.height', label: 'height', type: 'number' },
291
+ { path: 'layout.gap', label: 'gap', type: 'number' },
292
+ { path: 'style.backgroundColor', label: 'background', type: 'color' },
293
+ { path: 'style.color', label: 'color', type: 'color' },
294
+ { path: 'style.fontSize', label: 'fontSize', type: 'number' },
295
+ ];
296
+ for (const k of Object.keys(node.props ?? {})) {
297
+ out.push({ path: `props.${k}`, label: k, type: 'json' });
298
+ }
299
+ return out;
300
+ }
301
+
302
+ /** Every node id (for select-all). */
303
+ allIds(): string[] {
304
+ const ids: string[] = [];
305
+ walk(this.store.roots, (n) => ids.push(n.id as string));
306
+ return ids;
307
+ }
308
+
309
+ /**
310
+ * C2 (docs/unified-world-editor/26-unified-editor-spec.md §4 C2) — the
311
+ * wrapped {@link UIEditStore}, exposed for this adapter's OWN inspector-
312
+ * section registrations (`authoring/ui-inspector-sections.tsx`) to read/
313
+ * write UI-tree state the narrow `AuthoringAdapter` contract can't express
314
+ * (multi-select batch writes via `setMany`, K9 mixed-value detection) — the
315
+ * same "adapter-owned module privilege" the first-party threejs sections
316
+ * use via `EditorContext`'s `useEditorStore()` (there is no equivalent
317
+ * React context here — a bare `UIAuthoringAdapter` is never wrapped in one).
318
+ * NOT part of the `AuthoringAdapter` interface — a
319
+ * concrete-class-only escape hatch, consumed only by that registration
320
+ * module (which already does `instanceof UIAuthoringAdapter` to match).
321
+ */
322
+ get uiStore(): UIEditStore {
323
+ return this.store;
324
+ }
325
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * "Add UI Canvas" hierarchy context-menu item (C3, docs/unified-world-editor/
3
+ * 26-unified-editor-spec.md §4 C3) — the first-canvas affordance that
4
+ * replaces the retired standalone UI-edit mode's demo-absent
5
+ * `defaultCanvasRoot()` seeding (no shim — Q3). Shown on a first-party
6
+ * scene-ROOT row while the open scene has no `ui` roots yet; seeds ONE empty
7
+ * canvas (design/24-scene-ui.md D10 — the same schema-minimal empty canvas
8
+ * project mode already seeds) and asks `ViewportPanel.tsx` to rebuild the
9
+ * edit-mode composite so the new `scene-ui` child appears (§3.1's install
10
+ * gate — `getSceneUI().length > 0` — flips the instant this commits).
11
+ *
12
+ * Registered through `hierarchy-menu-registry.ts`'s permanent adapter-module
13
+ * escape hatch (C2), the same seam `first-party-hierarchy-menu.ts` uses.
14
+ * Imported once for its side effect at boot (`main.tsx`) — NOT loaded by the
15
+ * standalone harness (`ui-editor/harness.tsx`), which has no `EditorStore`
16
+ * to seed and no composite to rebuild.
17
+ */
18
+
19
+ import { registerHierarchyMenuItems } from '../hierarchy-menu-registry';
20
+ import { defaultCanvasRoot } from '../ui-editor/ui-edit-store';
21
+ import { EDIT_MODE_REBUILD_EVENT } from './edit-mode-authoring';
22
+
23
+ registerHierarchyMenuItems(
24
+ (ctx) => {
25
+ if (ctx.store.getSceneUI().length > 0) return false;
26
+ // A first-party scene ROOT row only (not every entity row) — one of the
27
+ // open document's own top-level entities (`store.entities` is the
28
+ // top-level `SceneEntity[]` tree, `EditorStore`'s own doc comment).
29
+ return ctx.store.entities.some((e) => e.id === ctx.nodeId);
30
+ },
31
+ (ctx) => [
32
+ {
33
+ label: 'Add UI Canvas',
34
+ action: () => {
35
+ ctx.store.setSceneUI([defaultCanvasRoot()]);
36
+ window.dispatchEvent(new CustomEvent(EDIT_MODE_REBUILD_EVENT));
37
+ },
38
+ },
39
+ ],
40
+ );
@@ -0,0 +1,156 @@
1
+ /**
2
+ * UI adapter inspector-section registrations (C2/C3, docs/unified-world-
3
+ * editor/26-unified-editor-spec.md §4 C2/C3) — the UI adapter module's
4
+ * contribution to the shared `inspector-section-registry.ts` seam, the
5
+ * sibling of `authoring/first-party-inspector/register.tsx`. Wraps `ui-editor/
6
+ * inspector.tsx`'s extracted `LayoutSection`/`StyleSection`/`PropsSection`/
7
+ * `EventsSection` (moved out of the monolithic `UIInspector` body, testids
8
+ * unchanged) so `UIInspector` itself becomes a thin shell that renders
9
+ * whatever matches via `matchedInspectorSections` (see that file's own doc
10
+ * comment).
11
+ *
12
+ * Matched for a real UI node (`node !== null`) reachable through a
13
+ * `UIAuthoringAdapter` — {@link resolveUIAdapter} below resolves that EITHER
14
+ * WAY the adapter can present it: the ACTIVE adapter is the bare
15
+ * `UIAuthoringAdapter` itself (the pre-C3 `setActiveAuthoring(adapter)` shape
16
+ * — no live construction site remains post-E2's harness retirement, but the
17
+ * branch stays as the defensive fallback), OR — since C3 — the edit-mode
18
+ * `CompositeAuthoringAdapter`, whose `ownerOf(node.id)` names the UI CHILD
19
+ * that actually owns the node (mirrors `first-party-inspector/
20
+ * is-first-party-selection.ts`'s identical composite-resolution precedent).
21
+ * The naive pre-C3 `adapter instanceof UIAuthoringAdapter` check ALWAYS
22
+ * fails once the composite is installed (`Inspector.tsx` passes the
23
+ * COMPOSITE, never the child, as its `adapter` prop) — this is the fix.
24
+ *
25
+ * Props/Events additionally require a SINGLE selection, mirroring the
26
+ * pre-C2 `!multi` gate in `UIInspector`. The Layout registration carries
27
+ * `suppresses`/`panelName` (C3): under the ONE shell (`Inspector.tsx`), the
28
+ * generic `UIAuthoringAdapter.inspector.properties()` grid would otherwise
29
+ * render NEXT TO this richer Layout/Style pair — the bare-adapter path
30
+ * (`UIInspector`, which has no such built-ins) ignores both.
31
+ *
32
+ * Imported once for its side effect, by `main.tsx` (so `UIInspector`
33
+ * renders correctly for the unified editor's scene-UI child, C3).
34
+ */
35
+
36
+ import type { AuthoringAdapter, EditorNode } from '@engine/adapter';
37
+ import type { UINode } from '@engine/scene/schema';
38
+ import {
39
+ type InspectorSectionProps,
40
+ registerInspectorSections,
41
+ } from '../inspector-section-registry';
42
+ import {
43
+ EventsSection,
44
+ LayoutSection,
45
+ mixedValue,
46
+ PropsSection,
47
+ StyleSection,
48
+ } from '../ui-editor/inspector';
49
+ import { findNode } from '../ui-editor/node-ops';
50
+ import type { UIEditStore } from '../ui-editor/ui-edit-store';
51
+ import { CompositeAuthoringAdapter } from './composite-authoring-adapter';
52
+ import { UIAuthoringAdapter } from './ui-authoring-adapter';
53
+
54
+ /**
55
+ * C3 — resolve the `UIAuthoringAdapter` that actually owns `nodeId`, whether
56
+ * `adapter` IS one (bare/harness path) or is the edit-mode composite wrapping
57
+ * one as a `kind: 'scene-ui'` child (unified-editor path). `null` when
58
+ * neither applies (a non-UI selection, or a composite with no UI child).
59
+ */
60
+ function resolveUIAdapter(
61
+ nodeId: string | null,
62
+ adapter: AuthoringAdapter,
63
+ ): UIAuthoringAdapter | null {
64
+ if (adapter instanceof UIAuthoringAdapter) return adapter;
65
+ if (adapter instanceof CompositeAuthoringAdapter && nodeId) {
66
+ const owner = adapter.ownerOf(nodeId);
67
+ const child = owner ? adapter.childAdapters().find((c) => c.worldId === owner) : undefined;
68
+ return child?.adapter instanceof UIAuthoringAdapter ? child.adapter : null;
69
+ }
70
+ return null;
71
+ }
72
+
73
+ const isUISelection = (node: EditorNode | null, adapter: AuthoringAdapter): boolean =>
74
+ node !== null && resolveUIAdapter(node.id, adapter) !== null;
75
+
76
+ const isSingleUISelection = (node: EditorNode | null, adapter: AuthoringAdapter): boolean => {
77
+ if (node === null) return false;
78
+ const ui = resolveUIAdapter(node.id, adapter);
79
+ return ui !== null && ui.uiStore.selection.length <= 1;
80
+ };
81
+
82
+ /** Shared "resolve the live node" prelude every wrapped section below needs
83
+ * — `null` when `nodeId` no longer resolves (selection changed mid-render;
84
+ * the shell simply skips the section). */
85
+ function resolveUINode(store: UIEditStore, nodeId: string | null): UINode | null {
86
+ const loc = nodeId ? findNode(store.roots, nodeId) : null;
87
+ return loc?.node ?? null;
88
+ }
89
+
90
+ function makeSetAndMixed(store: UIEditStore, ids: string[], nodeId: string) {
91
+ const set = (path: string, value: unknown): void => {
92
+ if (ids.length > 1) store.setPropMulti(ids, path, value);
93
+ else store.setProp(nodeId, path, value);
94
+ };
95
+ const mixed = (path: string): boolean => mixedValue(store, ids, path);
96
+ return { set, mixed };
97
+ }
98
+
99
+ registerInspectorSections({
100
+ match: isUISelection,
101
+ Section: function WrappedLayoutSection({ adapter, nodeId }: InspectorSectionProps) {
102
+ const ui = resolveUIAdapter(nodeId, adapter);
103
+ if (!ui) return null;
104
+ const store = ui.uiStore;
105
+ const ids = store.selection;
106
+ const node = resolveUINode(store, nodeId);
107
+ if (!node) return null;
108
+ const { set, mixed } = makeSetAndMixed(store, ids, nodeId!);
109
+ return <LayoutSection store={store} ids={ids} node={node} set={set} mixed={mixed} />;
110
+ },
111
+ // C3 — under the unified editor's ONE inspector shell, suppress the
112
+ // generic descriptor grid `UIAuthoringAdapter.inspector.properties()`
113
+ // would otherwise render alongside this richer pair (§4 C3/R4). The
114
+ // bare-adapter (harness) path never renders the shell's generic grid at
115
+ // all, so `suppresses`/`panelName` are no-ops there.
116
+ suppresses: ['header', 'transform', 'properties'],
117
+ panelName: 'Inspector',
118
+ });
119
+
120
+ registerInspectorSections({
121
+ match: isUISelection,
122
+ Section: function WrappedStyleSection({ adapter, nodeId }: InspectorSectionProps) {
123
+ const ui = resolveUIAdapter(nodeId, adapter);
124
+ if (!ui) return null;
125
+ const store = ui.uiStore;
126
+ const ids = store.selection;
127
+ const node = resolveUINode(store, nodeId);
128
+ if (!node) return null;
129
+ const { set, mixed } = makeSetAndMixed(store, ids, nodeId!);
130
+ return <StyleSection node={node} set={set} mixed={mixed} />;
131
+ },
132
+ });
133
+
134
+ registerInspectorSections({
135
+ match: isSingleUISelection,
136
+ Section: function WrappedPropsSection({ adapter, nodeId }: InspectorSectionProps) {
137
+ const ui = resolveUIAdapter(nodeId, adapter);
138
+ if (!ui) return null;
139
+ const store = ui.uiStore;
140
+ const node = resolveUINode(store, nodeId);
141
+ if (!node) return null;
142
+ return <PropsSection store={store} node={node} id={nodeId!} />;
143
+ },
144
+ });
145
+
146
+ registerInspectorSections({
147
+ match: isSingleUISelection,
148
+ Section: function WrappedEventsSection({ adapter, nodeId }: InspectorSectionProps) {
149
+ const ui = resolveUIAdapter(nodeId, adapter);
150
+ if (!ui) return null;
151
+ const store = ui.uiStore;
152
+ const node = resolveUINode(store, nodeId);
153
+ if (!node) return null;
154
+ return <EventsSection store={store} node={node} id={nodeId!} />;
155
+ },
156
+ });
@@ -0,0 +1,145 @@
1
+ /**
2
+ * The editor-global undo TIMELINE (T3.2 slice 2 — design
3
+ * `docs/PERSISTENCE-PROVIDER-DESIGN.md` §3/§7 slice 2).
4
+ *
5
+ * Today there are THREE independent undo stacks (the first-party `EditorStore`'s
6
+ * whole-doc snapshots, an `IngestAuthoringAdapter`'s overlay-backed closures, a
7
+ * `UIEditStore`'s tree snapshots) and nothing orders them against each other: a
8
+ * Ctrl+Z after "first-party edit → enter ingest → ingest edit" only ever undoes
9
+ * within whichever stack happens to be active. This module is the ONE shared
10
+ * timeline that fixes that: a bounded stack of `{ adapter }` ENTRIES — tags, not
11
+ * state. Each participating store keeps recording into its OWN stack exactly as
12
+ * it always has (this file never touches `_undoStack`/`undoStack`/`_undo`
13
+ * directly) and calls {@link notifyUndoableOp} with itself right after it pushes,
14
+ * so the entry's only job is to say "the next undo of THIS one goes here."
15
+ *
16
+ * Ctrl+Z pops the timeline and calls `entry.adapter.undo()`; Ctrl+Shift+Z /
17
+ * Cmd+Shift+Z / Ctrl+Y mirror with `redo()`. A brand-new op recorded ANYWHERE
18
+ * clears the global redo mirror (standard undo semantics), on top of whatever
19
+ * per-adapter redo-clearing each store already does for its own stack.
20
+ *
21
+ * Adapter identity — the choice this file makes and why: an entry holds the LIVE
22
+ * OBJECT REFERENCE that recorded it (the `EditorStore` instance for first-party,
23
+ * an `IngestAuthoringAdapter` instance, a `UIEditStore` instance — all three
24
+ * already expose `undo()`/`redo()` so no wrapper type is needed), checked against
25
+ * a `Set` of currently-registered instances at dispatch time. A synthesized
26
+ * string id (`'ingest'`, `'ui'`) was considered and rejected: a SECOND ingest/UI
27
+ * session reuses the same string, so a stale entry from a first, since-uninstalled
28
+ * session would resolve against the WRONG (new) live adapter instead of being
29
+ * dropped. Keying on the object itself sidesteps that — a new session is a new
30
+ * object, so an old entry can only ever match its OWN (registered) instance or
31
+ * fail the liveness check and get dropped, which is exactly the disposed-adapter
32
+ * rule below. (A `WeakRef` was the other option raised; rejected because
33
+ * "disposed" here is a LOGICAL state — the instance can still be strongly
34
+ * reachable elsewhere when its session ends — so liveness must be tracked by an
35
+ * explicit unregister call, not by GC.)
36
+ *
37
+ * Session lifetime: `registerUndoAdapter`/`unregisterUndoAdapter` are called at
38
+ * each store's construction/teardown (see `EditorStore`, `IngestAuthoringAdapter`,
39
+ * `UIEditStore`, `ingest-mode.ts`'s `exitIngestMode`, `edit-mode-authoring.ts`'s
40
+ * `exitEditModeAuthoring` for a scene-UI child's `UIEditStore`). An entry
41
+ * whose adapter has been unregistered since it was
42
+ * recorded is dropped (with a console warning) when popped; popping continues
43
+ * past dropped entries until a live one is found or the stack is empty — a
44
+ * session-scoped stack dies with its session, same rule the per-adapter stacks
45
+ * already had on their own.
46
+ *
47
+ * Cross-adapter transactions are OUT of scope (design §6): a gesture spanning two
48
+ * adapters records two entries and undoes in two Ctrl+Z presses.
49
+ */
50
+
51
+ /** Anything with its own undo/redo the timeline can dispatch to — `EditorStore`,
52
+ * `IngestAuthoringAdapter`, and `UIEditStore` all satisfy this structurally.
53
+ * A strict `false` return means "I could not perform this right now" (blocked
54
+ * during play, empty local stack, no scene bound) — the timeline then puts the
55
+ * entry BACK instead of consuming it, so a blocked Ctrl+Z doesn't silently eat
56
+ * an undo level. `void`/`true` both count as performed. */
57
+ export interface UndoAdapter {
58
+ // biome-ignore lint/suspicious/noConfusingVoidType: deliberate contract (see doc comment) — legacy adapters return void ("performed"), refusal-aware ones return false; `boolean | undefined` would reject `(): void` implementers
59
+ undo(): boolean | void;
60
+ // biome-ignore lint/suspicious/noConfusingVoidType: same contract as undo()
61
+ redo(): boolean | void;
62
+ }
63
+
64
+ /** Matches the 50-deep bound every per-adapter stack already uses. */
65
+ const MAX_TIMELINE = 50;
66
+
67
+ /** Currently-registered (not disposed/uninstalled) adapters — see doc comment above. */
68
+ const live = new Set<UndoAdapter>();
69
+
70
+ let undoStack: UndoAdapter[] = [];
71
+ let redoStack: UndoAdapter[] = [];
72
+
73
+ /** Register a store/adapter as a valid dispatch target for entries tagging it. */
74
+ export function registerUndoAdapter(adapter: UndoAdapter): void {
75
+ live.add(adapter);
76
+ }
77
+
78
+ /**
79
+ * Uninstall a store/adapter (session end). Any timeline entries already recorded
80
+ * for it are NOT removed from the stacks — they are simply dropped (with a warn)
81
+ * the next time they would be popped, per the disposed-adapter rule.
82
+ */
83
+ export function unregisterUndoAdapter(adapter: UndoAdapter): void {
84
+ live.delete(adapter);
85
+ }
86
+
87
+ /**
88
+ * Record a new undoable op that `adapter` just pushed onto its OWN stack. Clears
89
+ * the GLOBAL redo mirror (in addition to whatever per-adapter redo-clearing the
90
+ * caller already does for its own stack) — a new op anywhere invalidates redo.
91
+ */
92
+ export function notifyUndoableOp(adapter: UndoAdapter): void {
93
+ undoStack.push(adapter);
94
+ if (undoStack.length > MAX_TIMELINE) undoStack.shift();
95
+ redoStack = [];
96
+ }
97
+
98
+ function dispatch(from: UndoAdapter[], to: UndoAdapter[], op: 'undo' | 'redo'): void {
99
+ while (from.length > 0) {
100
+ const adapter = from.pop()!;
101
+ if (!live.has(adapter)) {
102
+ console.warn(
103
+ `[undo-timeline] dropping ${op} entry for a disposed/uninstalled adapter — ` +
104
+ 'its session ended before this entry was reached',
105
+ );
106
+ continue;
107
+ }
108
+ if (adapter[op]() === false) {
109
+ // The adapter is live but couldn't act (e.g. first-party undo blocked
110
+ // during play). Put the entry back and stop — falling through to an OLDER
111
+ // entry from another adapter would undo out of global order.
112
+ from.push(adapter);
113
+ return;
114
+ }
115
+ to.push(adapter);
116
+ return;
117
+ }
118
+ }
119
+
120
+ /** Ctrl+Z / Cmd+Z. */
121
+ export function timelineUndo(): void {
122
+ dispatch(undoStack, redoStack, 'undo');
123
+ }
124
+
125
+ /** Ctrl+Shift+Z / Cmd+Shift+Z / Ctrl+Y. */
126
+ export function timelineRedo(): void {
127
+ dispatch(redoStack, undoStack, 'redo');
128
+ }
129
+
130
+ /** True when the next Ctrl+Z has at least one (possibly dead) entry to try. */
131
+ export function timelineCanUndo(): boolean {
132
+ return undoStack.length > 0;
133
+ }
134
+
135
+ /** True when the next redo has at least one (possibly dead) entry to try. */
136
+ export function timelineCanRedo(): boolean {
137
+ return redoStack.length > 0;
138
+ }
139
+
140
+ /** Test-only: wipe all registrations + stacks so test files don't bleed into each other. */
141
+ export function __resetUndoTimelineForTest(): void {
142
+ live.clear();
143
+ undoStack = [];
144
+ redoStack = [];
145
+ }