@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,61 @@
1
+ /**
2
+ * D8 root activation — the SHARED dirty-gate choke point (A4,
3
+ * docs/unified-world-editor/26-unified-editor-spec.md §2 A4). Both
4
+ * `GameHierarchy`'s radio-row double-click and the asset-browser's scene
5
+ * double-click (`DefaultEditorLayout.tsx`'s `handleOpenScene`) call
6
+ * {@link requestRootActivation} instead of `adapter.rootGroups.activate()`
7
+ * directly, so the confirmation UX can never drift between the two call
8
+ * sites (map §4).
9
+ *
10
+ * `window.confirm()` is banned (editor-wide convention — see CLAUDE.md's
11
+ * "Dialogs" note): adapters can't render UI at all, and the shell must not
12
+ * block synchronously either. When the active adapter is dirty, this
13
+ * dispatches a `window` `CustomEvent` (the same decoupling pattern
14
+ * `first-party-hierarchy-menu.ts` already uses for
15
+ * `editor:save-as-prefab`/`editor:open-asset`) for `DefaultEditorLayout` to
16
+ * show {@link RootActivateDialog} and perform the actual activation only once
17
+ * the user confirms. When the adapter is NOT dirty, this activates
18
+ * immediately — no dialog, no round trip, no race.
19
+ */
20
+
21
+ import type { AuthoringAdapter } from '@engine/adapter';
22
+
23
+ export interface RootActivationRequest {
24
+ readonly groupId: string;
25
+ readonly rootId: string;
26
+ /** Display label for the confirmation dialog — resolved by THIS module via
27
+ * `hierarchy.node(rootId)?.label` so callers never need to format one
28
+ * themselves (and never need to know the id's shape). */
29
+ readonly label: string;
30
+ }
31
+
32
+ export const ROOT_ACTIVATION_REQUEST_EVENT = 'editor:request-root-activation';
33
+
34
+ /**
35
+ * Request activating `rootId` within `groupId` on `adapter`. Dirty-gates
36
+ * through a shell-rendered confirmation dialog; otherwise activates directly.
37
+ */
38
+ export function requestRootActivation(
39
+ adapter: AuthoringAdapter,
40
+ groupId: string,
41
+ rootId: string,
42
+ ): void {
43
+ if (adapter.persistence?.isDirty()) {
44
+ const label = adapter.hierarchy.node(rootId)?.label ?? rootId;
45
+ window.dispatchEvent(
46
+ new CustomEvent(ROOT_ACTIVATION_REQUEST_EVENT, {
47
+ detail: { groupId, rootId, label } satisfies RootActivationRequest,
48
+ }),
49
+ );
50
+ return;
51
+ }
52
+ void adapter.rootGroups?.activate(groupId, rootId);
53
+ }
54
+
55
+ /** Find the `RootGroup` (of `adapter`'s own `rootGroups`) that owns `rootId`,
56
+ * or `null` if none does. Shared by the asset-browser reroute and any other
57
+ * caller that only has a root id, not the owning group id, in hand. */
58
+ export function findOwningRootGroupId(adapter: AuthoringAdapter, rootId: string): string | null {
59
+ const group = adapter.rootGroups?.groups().find((g) => g.memberRootIds.includes(rootId));
60
+ return group?.id ?? null;
61
+ }
@@ -0,0 +1,485 @@
1
+ /**
2
+ * Scene-UI design-time layer (C3, docs/unified-world-editor/26-unified-
3
+ * editor-spec.md §4 C3) — the box-model overlay + stage guides "in the
4
+ * normal viewport" the spec calls for, replacing the retired standalone
5
+ * UI-edit mode's fullscreen `#ui-edit-mode-layer`. Sibling of
6
+ * `design-time-layers.ts`, but
7
+ * mounts the scene-UI composite child (`edit-mode-authoring.ts`'s `kind:
8
+ * 'scene-ui'` child) instead of a react/pixi world: that child already
9
+ * carries a LIVE `UIAuthoringAdapter` at composite-install time (there is no
10
+ * Boundary-to-live upgrade dance here, unlike react/pixi) — this module only
11
+ * gives it a DOM layer, a pick surface (D12/B4), and keeps the shared
12
+ * `EditorStore` and the child's own `UIEditStore` in lockstep.
13
+ *
14
+ * Reuses EVERY existing building block — no new UI runtime, no fork:
15
+ * - `SceneUITree` (the exact runtime that renders at game-time) in `editMode`.
16
+ * - `SelectionOverlay` (`ui-editor/overlay.tsx`) — hover/selection/marquee/
17
+ * resize boxes + the `Measure` spacing guide — IS the box-model overlay;
18
+ * it renders boxes only while `store.selection` is non-empty, so it
19
+ * "activates contextually on UI-node selection" for free.
20
+ * - `UIAuthoringAdapter.pickable` (added by C3) backed by `hitTest`
21
+ * (`ui-editor/react-store.ts`), the same geometric primitive the overlay
22
+ * itself uses for hover/click.
23
+ */
24
+
25
+ import type { UIRoot } from '@engine/scene/schema';
26
+ import type { UIAnimationController } from '@vgai/scene-ui/animation';
27
+ import { SceneUITree } from '@vgai/scene-ui/mount';
28
+ import type { ProjectUIRegistry } from '@vgai/scene-ui/registry';
29
+ import { createElement, useEffect, useRef, useState } from 'react';
30
+ import { createRoot } from 'react-dom/client';
31
+ import {
32
+ getCachedProjectUIRegistry,
33
+ loadProjectUIRegistry,
34
+ setCachedProjectUIRegistry,
35
+ } from '../adapter-resolver';
36
+ import type { EditorStore } from '../editor-store';
37
+ import { getCurrentProject } from '../project-manager';
38
+ import { findNode } from '../ui-editor/node-ops';
39
+ import { SelectionOverlay } from '../ui-editor/overlay';
40
+ import { useUIStore } from '../ui-editor/react-store';
41
+ import type { UIEditStore } from '../ui-editor/ui-edit-store';
42
+ import type { UIAuthoringAdapter } from './ui-authoring-adapter';
43
+ import { isWorldHidden, isWorldInteractive } from './world-session-state';
44
+
45
+ /** Container-ish UI kinds (mirrors `ui-editor/palette.tsx`'s own unexported
46
+ * `isContainer` — a project-registry component insert (below) targets the
47
+ * primary selection when it's one of these, else the first root, exactly
48
+ * like the harness palette's own "add under selected container" rule. */
49
+ const CONTAINER_KINDS = new Set([
50
+ 'canvas',
51
+ 'box',
52
+ 'grid',
53
+ 'stack',
54
+ 'scroll',
55
+ 'panel',
56
+ 'center',
57
+ 'wrap',
58
+ 'aspect',
59
+ 'sizebox',
60
+ 'tab',
61
+ ]);
62
+
63
+ /** Pure (DOM-free) equality check for the R3 echo guard — exported for unit
64
+ * testing without a browser: `uiStore.onCommit -> store.setSceneUI -> store
65
+ * notify -> this module's sync sees "changed" -> uiStore.load()` would wipe
66
+ * selection + undo after every edit unless the sync SKIPS a no-op round
67
+ * trip (the just-committed content already matches). */
68
+ export function sceneUIChanged(a: readonly UIRoot[], b: readonly UIRoot[]): boolean {
69
+ return JSON.stringify(a) !== JSON.stringify(b);
70
+ }
71
+
72
+ /**
73
+ * The content-sync decision for the viewport layer's `EditorStore`
74
+ * subscription (C3 skeptic fix): reload the UI child from the committed
75
+ * document ONLY when it genuinely diverged AND no interactive gesture owns the
76
+ * preview `_roots`. During a live drag/resize `uiStore.serialize()` reflects
77
+ * uncommitted preview state, so `sceneUIChanged` is true and a naive reload
78
+ * would revert the drag and wipe selection/local-undo mid-motion (reachable
79
+ * whenever any `EditorStore` notify — e.g. the previous commit's async
80
+ * autosave — lands inside the gesture window). `endEdit()` commits through
81
+ * `setSceneUI`, after which content matches and no reload is needed. Exported
82
+ * so the wiring in `mountSceneUILayer` is unit-testable against a real
83
+ * `UIEditStore`'s gesture state (the subscription closure otherwise is not).
84
+ */
85
+ export function shouldReloadUIChild(
86
+ sceneUI: readonly UIRoot[],
87
+ uiStore: Pick<UIEditStore, 'inGesture' | 'serialize'>,
88
+ ): boolean {
89
+ return !uiStore.inGesture && sceneUIChanged(sceneUI, uiStore.serialize());
90
+ }
91
+
92
+ /** E1 "lose nothing" migration (§8 "board-state/responsive/locale preview") —
93
+ * the harness's fixed-resolution device presets (`UIEditor.tsx`'s
94
+ * `RES_PRESETS`/`I3`), ported verbatim so the unified editor's scene-UI
95
+ * chrome doesn't drop the responsive-preview affordance. `fit` (the default)
96
+ * keeps today's full-bleed `inset:0` stage untouched — only picking an
97
+ * explicit preset switches the stage to a centered, fixed-size box. */
98
+ const RES_PRESETS: Record<string, { width: number; height: number } | null> = {
99
+ fit: null,
100
+ '1920×1080': { width: 1920, height: 1080 },
101
+ '1280×720': { width: 1280, height: 720 },
102
+ '375×812 (phone)': { width: 375, height: 812 },
103
+ };
104
+
105
+ /** Fit a fixed preset's ASPECT into a capped display box (verbatim from
106
+ * `UIEditor.tsx`) so a resolution preview never clips against the
107
+ * design-time layer's own (usually much larger) bounds. */
108
+ function fitDisplaySize(res: { width: number; height: number }): { width: number; height: number } {
109
+ const maxW = 960;
110
+ const maxH = 560;
111
+ const s = Math.min(maxW / res.width, maxH / res.height, 1);
112
+ return { width: Math.round(res.width * s), height: Math.round(res.height * s) };
113
+ }
114
+
115
+ function createStageLayer(container: HTMLElement, worldId: string, zIndex: number): HTMLElement {
116
+ const layer = document.createElement('div');
117
+ layer.dataset['worldId'] = worldId;
118
+ layer.style.position = 'absolute';
119
+ layer.style.top = '0';
120
+ layer.style.left = '0';
121
+ layer.style.width = '100%';
122
+ layer.style.height = '100%';
123
+ layer.style.zIndex = String(zIndex);
124
+ layer.style.display = isWorldHidden(worldId) ? 'none' : 'block';
125
+ // B1 semantics: `pointer-events:none` at rest — plain click-select goes
126
+ // through B4 (`layered-pick.ts`, via `UIAuthoringAdapter.pickable`)
127
+ // instead, so the overlay's own drag/resize/marquee handlers only need to
128
+ // receive real pointer events while the layer's `interactive` toggle is on.
129
+ layer.style.pointerEvents = isWorldInteractive(worldId) ? 'auto' : 'none';
130
+ container.appendChild(layer);
131
+ return layer;
132
+ }
133
+
134
+ interface SceneUILayerRootProps {
135
+ uiStore: UIEditStore;
136
+ projectRoot: string | undefined;
137
+ /** E1.b — the child's ONE animation controller, owned by `UIAuthoringAdapter`
138
+ * (shared with the timeline panel, `scene-ui-mode/scene-ui-timeline-tab.tsx`)
139
+ * instead of a `useMemo`'d local instance — see that adapter field's doc
140
+ * comment for why the two mounts must share one instance. */
141
+ anim: UIAnimationController;
142
+ }
143
+
144
+ /** The stage + overlay, composed exactly like `UIEditor.tsx`'s center stage
145
+ * (`:259-283`), minus the chrome — plus a project-registry load (§3.3),
146
+ * mirroring the retired standalone UI-edit mode's own registry resolution
147
+ * (components/strings/states) without its demo-fixture fallback branch
148
+ * (dropped by design, Q3). */
149
+ function SceneUILayerRoot({ uiStore, projectRoot, anim }: SceneUILayerRootProps): React.ReactNode {
150
+ useUIStore(uiStore);
151
+ const stageRef = useRef<HTMLDivElement>(null);
152
+ const [registry, setRegistry] = useState<ProjectUIRegistry | null>(() =>
153
+ getCachedProjectUIRegistry(),
154
+ );
155
+ // I3/G5 "lose nothing" migration — resolution/locale preview (see
156
+ // `RES_PRESETS`'s doc comment above); session-local UI state (not
157
+ // persisted), same as the harness toolbar's own `useState`.
158
+ const [resKey, setResKey] = useState('fit');
159
+ const previewRes = RES_PRESETS[resKey] ?? null;
160
+ const display = previewRes ? fitDisplaySize(previewRes) : null;
161
+
162
+ // E1.R2 review follow-up: this used to also call `uiStore.setPreviewResolution
163
+ // (previewRes)` on every `previewRes` change. `UIEditStore.previewResolution`
164
+ // (the field that write feeds) has ZERO readers anywhere in the repo — the
165
+ // stage resize below is driven entirely by the LOCAL `display`/`resKey` React
166
+ // state, never by that store field — so the write was dead-ported state left
167
+ // over from the harness (`UIEditor.tsx`, which still calls it, unreviewed
168
+ // here) and has been dropped rather than pinned by a test that could never
169
+ // observe anything.
170
+
171
+ useEffect(() => {
172
+ if (!projectRoot) return;
173
+ let cancelled = false;
174
+ void loadProjectUIRegistry(projectRoot).then((r) => {
175
+ // Cache regardless of the registry's outcome (present/absent/broken) —
176
+ // the SAME cache slot the broken-registry loud warn reads from
177
+ // (`adapter-resolver.ts`), preserving 24-ui-registry's assertion.
178
+ setCachedProjectUIRegistry(r);
179
+ if (cancelled) return;
180
+ setRegistry(r);
181
+ if (r?.states) {
182
+ for (const [name, data] of Object.entries(r.states)) uiStore.defineState(name, data);
183
+ // 'default' (else the first entry) seeds the binding preview — same
184
+ // resolution order the retired standalone UI-edit mode used.
185
+ const first = r.states['default'] ? 'default' : Object.keys(r.states)[0];
186
+ if (first) uiStore.loadState(first);
187
+ }
188
+ });
189
+ return () => {
190
+ cancelled = true;
191
+ };
192
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- one-shot load per mount
193
+ }, [projectRoot]);
194
+
195
+ // Board-state switching (C12) — a minimal always-on strip (NOT the richer
196
+ // harness toolbar, `UIEditor.tsx`'s `state-${s}` buttons, which stays
197
+ // harness-only): the registry's named states need SOME surface in the
198
+ // unified editor so a binding preview is actually reachable, per §6's
199
+ // "24-ui-registry" testing note. Kept `pointer-events: auto` explicitly —
200
+ // out of the B1 `interactive` toggle's scope (that gates the SelectionOverlay's
201
+ // drag/resize/marquee, spec-27 Phase B/C/D territory; a state switcher is a
202
+ // small, always-on affordance, not that).
203
+ const stateNames = registry?.states ? Object.keys(registry.states) : [];
204
+ const stateStrip =
205
+ stateNames.length > 0
206
+ ? createElement(
207
+ 'div',
208
+ {
209
+ 'data-testid': 'scene-ui-states',
210
+ style: {
211
+ position: 'absolute',
212
+ top: 0,
213
+ left: 0,
214
+ zIndex: 20,
215
+ display: 'flex',
216
+ gap: 4,
217
+ padding: 4,
218
+ pointerEvents: 'auto',
219
+ },
220
+ },
221
+ ...stateNames.map((name) =>
222
+ createElement(
223
+ 'button',
224
+ {
225
+ key: name,
226
+ 'data-testid': `scene-ui-state-${name}`,
227
+ onClick: () => uiStore.loadState(name),
228
+ style: { fontSize: 10 },
229
+ },
230
+ name,
231
+ ),
232
+ ),
233
+ )
234
+ : null;
235
+
236
+ // Registry-component insertion — a minimal always-on strip, same rationale
237
+ // as the state strip above: `structure.creatableKinds` (the hierarchy's
238
+ // "Create ▸" menu) only knows the fixed built-in kinds (`CREATABLE_KINDS`,
239
+ // `ui-authoring-adapter.ts`), never a project's OWN named registry
240
+ // components — inserting one needs SOME surface (the harness has none
241
+ // either; both a project registry AND a component-insert UI are new to
242
+ // this task). Targets the primary selection when it's a container, else
243
+ // the first root — the same rule `ui-editor/palette.tsx`'s own `add` uses.
244
+ const componentNames = registry?.components ? Object.keys(registry.components) : [];
245
+ const componentStrip =
246
+ componentNames.length > 0
247
+ ? createElement(
248
+ 'div',
249
+ {
250
+ 'data-testid': 'scene-ui-components',
251
+ style: {
252
+ position: 'absolute',
253
+ top: 0,
254
+ right: 0,
255
+ zIndex: 20,
256
+ display: 'flex',
257
+ gap: 4,
258
+ padding: 4,
259
+ pointerEvents: 'auto',
260
+ },
261
+ },
262
+ ...componentNames.map((name) =>
263
+ createElement(
264
+ 'button',
265
+ {
266
+ key: name,
267
+ 'data-testid': `scene-ui-insert-${name}`,
268
+ onClick: () => {
269
+ const primary = uiStore.primary;
270
+ const primaryLoc = primary ? findNode(uiStore.roots, primary) : null;
271
+ const parentId =
272
+ primaryLoc && CONTAINER_KINDS.has(primaryLoc.node.kind)
273
+ ? primary!
274
+ : ((uiStore.roots[0]?.id as string | undefined) ?? null);
275
+ if (parentId) {
276
+ uiStore.addNodeValue(parentId, { kind: 'component', name, component: name });
277
+ }
278
+ },
279
+ style: { fontSize: 10 },
280
+ },
281
+ name,
282
+ ),
283
+ ),
284
+ )
285
+ : null;
286
+
287
+ // I3/G5 — a minimal always-on strip for the responsive/locale preview
288
+ // (§8's "board-state/responsive/locale preview" MIGRATE row), same
289
+ // rationale/placement discipline as `stateStrip`/`componentStrip` above:
290
+ // the harness's richer toolbar (`UIEditor.tsx`'s `tb-res`/`tb-locale`)
291
+ // stays harness-only; the unified editor needs SOME surface so the
292
+ // affordance isn't silently dropped. Locale options come from the
293
+ // registry's own `strings` tables — the same source the harness reads
294
+ // (`Object.keys(strings ?? { en: {} })`).
295
+ const localeNames = registry?.strings ? Object.keys(registry.strings) : ['en'];
296
+ const previewStrip = createElement(
297
+ 'div',
298
+ {
299
+ 'data-testid': 'scene-ui-preview-controls',
300
+ style: {
301
+ position: 'absolute',
302
+ top: 0,
303
+ left: '50%',
304
+ transform: 'translateX(-50%)',
305
+ zIndex: 20,
306
+ display: 'flex',
307
+ gap: 4,
308
+ padding: 4,
309
+ pointerEvents: 'auto',
310
+ },
311
+ },
312
+ createElement(
313
+ 'select',
314
+ {
315
+ 'data-testid': 'scene-ui-res-preset',
316
+ value: resKey,
317
+ onChange: (e: React.ChangeEvent<HTMLSelectElement>) => setResKey(e.target.value),
318
+ style: { fontSize: 10 },
319
+ },
320
+ ...Object.keys(RES_PRESETS).map((k) => createElement('option', { key: k, value: k }, k)),
321
+ ),
322
+ createElement(
323
+ 'select',
324
+ {
325
+ 'data-testid': 'scene-ui-locale',
326
+ value: uiStore.locale,
327
+ onChange: (e: React.ChangeEvent<HTMLSelectElement>) => uiStore.setLocale(e.target.value),
328
+ style: { fontSize: 10 },
329
+ },
330
+ ...localeNames.map((l) => createElement('option', { key: l, value: l }, l)),
331
+ ),
332
+ );
333
+
334
+ return createElement(
335
+ 'div',
336
+ {
337
+ // Default ('fit', `display === null`) — an unchanged full-bleed box
338
+ // (byte-identical to pre-migration layout: `position:absolute;inset:0`
339
+ // containing a 100%x100% stage, no flex). A picked preset instead
340
+ // centers the fixed-size stage box within the same full-bleed area.
341
+ style: display
342
+ ? {
343
+ position: 'absolute',
344
+ inset: 0,
345
+ display: 'flex',
346
+ alignItems: 'center',
347
+ justifyContent: 'center',
348
+ }
349
+ : { position: 'absolute', inset: 0 },
350
+ },
351
+ createElement(
352
+ 'div',
353
+ {
354
+ ref: stageRef,
355
+ 'data-ui-stage': true,
356
+ style: display
357
+ ? {
358
+ position: 'relative',
359
+ width: display.width,
360
+ height: display.height,
361
+ outline: '1px dashed #4a90d9',
362
+ }
363
+ : { position: 'relative', width: '100%', height: '100%' },
364
+ },
365
+ createElement(SceneUITree, {
366
+ roots: uiStore.roots,
367
+ data: uiStore.data,
368
+ components: registry?.components,
369
+ strings: registry?.strings,
370
+ locale: uiStore.locale,
371
+ editMode: true,
372
+ viewportOverride: display ?? undefined,
373
+ anim,
374
+ }),
375
+ createElement(SelectionOverlay, { store: uiStore, stageRef }),
376
+ ),
377
+ stateStrip,
378
+ componentStrip,
379
+ previewStrip,
380
+ );
381
+ }
382
+
383
+ /**
384
+ * Mount the scene-UI design-time layer for `worldId` (the composite's
385
+ * `scene-ui` child) into `container` (`ViewportPanel`'s own container — the
386
+ * same one `design-time-layers.ts`'s react/pixi layers use). Returns a
387
+ * disposer. Call it once per composite install; `design-time-layers.ts`'s
388
+ * `mountDesignTimeLayers` is the one production call site.
389
+ */
390
+ export function mountSceneUILayer(
391
+ container: HTMLElement,
392
+ worldId: string,
393
+ uiAdapter: UIAuthoringAdapter,
394
+ store: EditorStore,
395
+ zIndex: number,
396
+ ): () => void {
397
+ const projectRoot = getCurrentProject()?.rootPath;
398
+ const uiStore = uiAdapter.uiStore;
399
+ const layer = createStageLayer(container, worldId, zIndex);
400
+ uiAdapter.attachPickSurface(layer);
401
+
402
+ const root = createRoot(layer);
403
+ root.render(createElement(SceneUILayerRoot, { uiStore, projectRoot, anim: uiAdapter.anim }));
404
+
405
+ // R3 (echo guard) + R4-adjacent selection bridge: `uiStore.select()` can be
406
+ // reached WITHOUT going through `UIAuthoringAdapter.selection.set` (e.g.
407
+ // `SelectionOverlay`'s own click handler, `UIEditStore.addNode`'s
408
+ // auto-select) — mirroring `uiStore.selection` outward keeps the SHARED
409
+ // `EditorStore.selectedEntityIds` (what `Inspector.tsx`/`GameHierarchy`
410
+ // read) in lockstep no matter which surface drove the change. `suppressing`
411
+ // breaks the two-way ping-pong: each direction's write happens INSIDE the
412
+ // flag, so the other direction's listener (which fires synchronously,
413
+ // re-entrantly) sees it set and no-ops.
414
+ let suppressing = false;
415
+ let lastMirroredUiSelectionKey = uiStore.selection.join('\u0000');
416
+
417
+ const unsubUI = uiStore.subscribe(() => {
418
+ if (suppressing) return;
419
+ const key = uiStore.selection.join('\u0000');
420
+ if (key === lastMirroredUiSelectionKey) return;
421
+ lastMirroredUiSelectionKey = key;
422
+ suppressing = true;
423
+ try {
424
+ store.selectMultiple(uiStore.selection);
425
+ } finally {
426
+ suppressing = false;
427
+ }
428
+ });
429
+
430
+ const unsubStore = store.subscribe(() => {
431
+ // C9 content sync — external changes (undo/redo of the whole document,
432
+ // scene switch) reload the UI child; the equality check IS the R3 guard
433
+ // against the commit echo (`onCommit -> setSceneUI -> notify -> here`
434
+ // sees the just-committed content already matches, skips `load()`).
435
+ //
436
+ // The `inGesture` gate is the C3 skeptic fix: during a live drag/resize,
437
+ // `uiStore.serialize()` reflects the uncommitted preview `_roots`, so it
438
+ // differs from the committed `getSceneUI()` and this branch would call
439
+ // `load()` — reverting the drag and wiping selection/local-undo mid-motion.
440
+ // Any `EditorStore` notify can reach here mid-gesture (most reachably the
441
+ // previous commit's async autosave resolving inside the next drag's
442
+ // window). Skip the reload while a gesture owns `_roots`; `endEdit()`
443
+ // commits through `setSceneUI`, after which content matches and no reload
444
+ // is needed. (A genuine external doc change arriving mid-gesture — vanishing
445
+ // rare — is simply deferred, never allowed to destroy the active gesture.)
446
+ const sceneUI = store.getSceneUI();
447
+ if (shouldReloadUIChild(sceneUI, uiStore)) {
448
+ uiStore.load(sceneUI);
449
+ }
450
+
451
+ if (!suppressing) {
452
+ const ids = [...store.selectedEntityIds];
453
+ const uiOwnsSelection = ids.length > 0 && ids.every((id) => uiStore.find(id) != null);
454
+ if (uiOwnsSelection) {
455
+ // Already reflects (or will reflect, via the mirror above) uiStore's
456
+ // own selection — nothing to clear, and don't let a stale key here
457
+ // cause a redundant re-mirror on the next uiStore notify.
458
+ lastMirroredUiSelectionKey = ids.join('\u0000');
459
+ } else if (uiStore.selection.length > 0) {
460
+ // A non-UI (or empty) selection landed on the shared store — the UI
461
+ // child no longer owns it. Clear the UI selection so a stale
462
+ // highlighted node doesn't linger and the inspector never
463
+ // double-matches (R4's "one inspector" discipline).
464
+ lastMirroredUiSelectionKey = '';
465
+ suppressing = true;
466
+ try {
467
+ uiStore.select([]);
468
+ } finally {
469
+ suppressing = false;
470
+ }
471
+ }
472
+ }
473
+
474
+ layer.style.display = isWorldHidden(worldId) ? 'none' : 'block';
475
+ layer.style.pointerEvents = isWorldInteractive(worldId) ? 'auto' : 'none';
476
+ });
477
+
478
+ return () => {
479
+ unsubUI();
480
+ unsubStore();
481
+ uiAdapter.detachPickSurface();
482
+ root.unmount();
483
+ layer.remove();
484
+ };
485
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Story overlay (Q2 decoupling, D4, `docs/unified-world-editor/26-unified-editor-spec.md`
3
+ * §3 B2) — a module-level, per-worldId slot for an EDITOR-SIDE stories
4
+ * source that plugs into `ReactWorldAuthoringAdapter`'s `storySources`
5
+ * constructor option without the adapter (or `design-time-layers.ts`, which
6
+ * constructs it) needing to know where the extra source came from. Same
7
+ * shape as `world-session-state.ts`: a bare get/set slot, no subscribe of
8
+ * its own — a caller that mutates it re-renders/re-mounts through whatever
9
+ * already reacts to `store.notifyIngestEdit()`.
10
+ *
11
+ * This is the DECOUPLING plumbing only (the spec's Q2 requirement: "the
12
+ * provider takes stories as a constructor input; the project registry is
13
+ * merely the default source... add the parameter and a unit test, no UI for
14
+ * authoring overlays yet"). There is no file-loader or editor UI writing
15
+ * into this slot in B2 — that is future work; today it exists so an overlay
16
+ * source can be set programmatically (e.g. by a future authoring UI, or a
17
+ * test) and observed to WIN over the registry's stories on an id collision
18
+ * (`mountReactLayer` composes `storySources: [registrySource, overlaySource]`
19
+ * — later entries win, see `ReactWorldAuthoringAdapter`'s doc comment).
20
+ *
21
+ * A process-lifetime singleton (not project-scoped), matching
22
+ * `world-session-state.ts`'s own stance — nothing here needs a
23
+ * project-switch reset within a single browser tab.
24
+ */
25
+ import type { UIStory } from '@vgai/scene-ui/registry';
26
+
27
+ const _overlays = new Map<string, Record<string, UIStory>>();
28
+
29
+ /** Set (replacing wholesale) `worldId`'s overlay stories source. */
30
+ export function setStoryOverlay(worldId: string, stories: Record<string, UIStory>): void {
31
+ _overlays.set(worldId, stories);
32
+ }
33
+
34
+ /** `worldId`'s overlay stories source, or `undefined` if none was set. */
35
+ export function getStoryOverlay(worldId: string): Record<string, UIStory> | undefined {
36
+ return _overlays.get(worldId);
37
+ }
38
+
39
+ export function clearStoryOverlay(worldId: string): void {
40
+ _overlays.delete(worldId);
41
+ }
42
+
43
+ /** Test-only: drop every world's overlay so unit tests don't leak state
44
+ * across cases (this module is a process-lifetime singleton — see the
45
+ * header comment). */
46
+ export function _resetStoryOverlaysForTest(): void {
47
+ _overlays.clear();
48
+ }