@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,194 @@
1
+ import { connectEvents } from './editor-api';
2
+ import { getEditorMode } from './editor-mode';
3
+
4
+ export type ConsoleLevel = 'info' | 'warn' | 'error';
5
+
6
+ export interface ConsoleEntry {
7
+ id: number;
8
+ timestamp: number;
9
+ level: ConsoleLevel;
10
+ message: string;
11
+ source?: string;
12
+ subsystem?: string;
13
+ metadata?: Record<string, unknown>;
14
+ count: number;
15
+ }
16
+
17
+ const MAX_ENTRIES = 1000;
18
+
19
+ class EditorConsole {
20
+ private _entries: ConsoleEntry[] = [];
21
+ private _listeners = new Set<() => void>();
22
+ private _nextId = 0;
23
+ private _version = 0;
24
+ private _sink: ((entry: ConsoleEntry) => void) | null = null;
25
+
26
+ /** Register (or clear) a callback invoked for every console entry. */
27
+ setSink(fn: ((entry: ConsoleEntry) => void) | null): void {
28
+ this._sink = fn;
29
+ }
30
+
31
+ counts = { info: 0, warn: 0, error: 0 };
32
+
33
+ log(message: string, source?: string): void {
34
+ this._push('info', message, source);
35
+ }
36
+
37
+ warn(message: string, source?: string): void {
38
+ this._push('warn', message, source);
39
+ }
40
+
41
+ error(message: string, source?: string): void {
42
+ this._push('error', message, source);
43
+ }
44
+
45
+ /** Log with structured metadata (used by the engine logger). */
46
+ logStructured(
47
+ level: ConsoleLevel,
48
+ message: string,
49
+ source: string,
50
+ subsystem?: string,
51
+ metadata?: Record<string, unknown>,
52
+ ): void {
53
+ this._push(level, message, source, subsystem, metadata);
54
+ }
55
+
56
+ clear(): void {
57
+ this._entries = [];
58
+ this.counts = { info: 0, warn: 0, error: 0 };
59
+ this._notify();
60
+ }
61
+
62
+ getEntries(): readonly ConsoleEntry[] {
63
+ return this._entries;
64
+ }
65
+
66
+ // useSyncExternalStore compatible
67
+ subscribe = (cb: () => void): (() => void) => {
68
+ this._listeners.add(cb);
69
+ return () => this._listeners.delete(cb);
70
+ };
71
+
72
+ getSnapshot = (): number => this._version;
73
+
74
+ private _push(
75
+ level: ConsoleLevel,
76
+ message: string,
77
+ source?: string,
78
+ subsystem?: string,
79
+ metadata?: Record<string, unknown>,
80
+ ): void {
81
+ // Collapse identical consecutive messages
82
+ const last = this._entries[this._entries.length - 1];
83
+ if (last && last.level === level && last.message === message && last.source === source) {
84
+ last.count++;
85
+ last.timestamp = Date.now();
86
+ this._sink?.(last);
87
+ this._notify();
88
+ return;
89
+ }
90
+
91
+ const entry: ConsoleEntry = {
92
+ id: this._nextId++,
93
+ timestamp: Date.now(),
94
+ level,
95
+ message,
96
+ count: 1,
97
+ };
98
+ if (source) entry.source = source;
99
+ if (subsystem) entry.subsystem = subsystem;
100
+ if (metadata) entry.metadata = metadata;
101
+ this._entries.push(entry);
102
+ this.counts[level]++;
103
+ this._sink?.(entry);
104
+
105
+ // Cap at MAX_ENTRIES, drop oldest
106
+ if (this._entries.length > MAX_ENTRIES) {
107
+ const removed = this._entries.shift()!;
108
+ this.counts[removed.level] = Math.max(0, this.counts[removed.level] - 1);
109
+ }
110
+
111
+ this._notify();
112
+ }
113
+
114
+ private _notify(): void {
115
+ this._version++;
116
+ for (const fn of this._listeners) fn();
117
+ }
118
+ }
119
+
120
+ export const editorConsole = new EditorConsole();
121
+
122
+ // Test-only hook (e2e) — the window mirror `design/24-scene-ui.md` A4(f)
123
+ // anticipates: direct read access to console entries without DOM-scraping
124
+ // `ConsolePanel` (which has no per-entry testids). Same ad-hoc `window.__vgai*`
125
+ // convention as every other dev-only hook in `test-window.ts`.
126
+ (window as unknown as { __vgaiEditorConsole?: EditorConsole })['__vgaiEditorConsole'] =
127
+ editorConsole;
128
+
129
+ /**
130
+ * Listen for `console-bridge` postMessage events from game iframes (or self)
131
+ * and pipe them into the editor console.
132
+ */
133
+ const BRIDGE_LEVEL_MAP: Record<string, ConsoleLevel> = {
134
+ log: 'info',
135
+ warn: 'warn',
136
+ error: 'error',
137
+ };
138
+
139
+ /** LogLevel string → ConsoleLevel for engine-log messages. */
140
+ const ENGINE_LEVEL_MAP: Record<string, ConsoleLevel> = {
141
+ trace: 'info',
142
+ debug: 'info',
143
+ info: 'info',
144
+ warn: 'warn',
145
+ error: 'error',
146
+ fatal: 'error',
147
+ };
148
+
149
+ window.addEventListener('message', (e: MessageEvent) => {
150
+ // Structured engine-log messages (from engine logger)
151
+ if (e.data?.type === 'engine-log') {
152
+ const level = ENGINE_LEVEL_MAP[e.data.level];
153
+ if (!level) return;
154
+ editorConsole.logStructured(level, e.data.message, 'engine', e.data.subsystem, e.data.metadata);
155
+ return;
156
+ }
157
+
158
+ // Plain console-bridge messages (from game code)
159
+ if (e.data?.type === 'console-bridge') {
160
+ const level = BRIDGE_LEVEL_MAP[e.data.level];
161
+ if (!level) return;
162
+ editorConsole[level === 'info' ? 'log' : level](e.data.message, 'engine');
163
+ }
164
+ });
165
+
166
+ /**
167
+ * Connect to the editor SSE endpoint and pipe `server-log` events
168
+ * into the editor console tagged with source 'server'.
169
+ */
170
+ export function connectServerLogs(): () => void {
171
+ // Phase A cleanup: browser mode has no Node dev server, so `/__editor/events`
172
+ // does not exist — an unconditional `EventSource` here would auto-retry
173
+ // against a dead endpoint forever (the same anti-pattern A2 already fixed
174
+ // for the CLI/asset/scene SSE consumers via `getEditorMode() === 'browser'`
175
+ // guards; this one was missed). There is no browser-mode server-log source
176
+ // to bridge from, so this is a clean skip, not a routed replacement.
177
+ if (getEditorMode() === 'browser') {
178
+ return () => {};
179
+ }
180
+ const source = connectEvents();
181
+
182
+ source.addEventListener('server-log', (e: MessageEvent) => {
183
+ try {
184
+ const data = JSON.parse(e.data) as { level: string; message: string };
185
+ const level = BRIDGE_LEVEL_MAP[data.level];
186
+ if (!level) return;
187
+ editorConsole[level === 'info' ? 'log' : level](data.message, 'server');
188
+ } catch {
189
+ /* ignore malformed events */
190
+ }
191
+ });
192
+
193
+ return () => source.close();
194
+ }
@@ -0,0 +1,441 @@
1
+ import {
2
+ collectAllNodeIds,
3
+ getActiveAuthoring,
4
+ hasAuthoringOverride,
5
+ } from './authoring/active-adapter';
6
+ import { timelineRedo, timelineUndo } from './authoring/undo-timeline';
7
+ import { resetWorldPan } from './authoring/world-pan-state';
8
+ import {
9
+ boxEditForId,
10
+ computeArrowNudgePatch,
11
+ readSpacingValues,
12
+ rectForId,
13
+ } from './components/world-overlay-gestures';
14
+ import type { EditorStore } from './editor-store';
15
+ import type { EditorViewport } from './editor-viewport';
16
+ import { installHotkeys, registerHotkeys, setActiveScope } from './hotkeys';
17
+
18
+ /**
19
+ * D4 (spec27 §6 D4, §8 "Keyboard / undo / pan-zoom" row) — RECONCILED by the
20
+ * `overlay-panzoom` unit (see `docs/unified-world-editor/27-visual-react-
21
+ * editing.md` §1 D-E's own reconciliation note and `GOLDEN-GATE.md`).
22
+ *
23
+ * Space-pan is BUILT: `WorldSelectionOverlay.tsx`'s own Space-held +
24
+ * pointer-drag handling (its `spaceHeldRef`/`panDragRef`), writing the shared
25
+ * `world-pan-state.ts` singleton, which `design-time-layers.ts` applies to
26
+ * every world-layer host IN LOCKSTEP with the overlay's own wrapper — a pure
27
+ * CSS `transform: translate()` on both. Not registered here in
28
+ * `editor-hotkeys.ts`'s `registerHotkeys` array because that registry is a
29
+ * single-fire-per-keydown dispatcher with no "held" concept (see
30
+ * `hotkeys.ts`); Space-pan needs a literal hold state, so it follows the
31
+ * SAME precedent this file's own vertex-snap `V`-key handling below already
32
+ * set (a raw `window` keydown/keyup pair) — just owned by the overlay
33
+ * component instead of this file, since the gesture IS a pointer-drag over
34
+ * the overlay's own interaction layer, not a stateless action like
35
+ * duplicate/delete/nudge above.
36
+ *
37
+ * Zoom-fit/selection is a stated EXCLUDE — evidence checked (not assumed):
38
+ * the 3D viewport (`ViewportPanel.tsx`/`editor-viewport.ts`) already has a
39
+ * real zoom substrate (Three.js `OrbitControls`, numpad view presets), so
40
+ * zoom-fit there is pure duplication, not a gap. The react/DOM world
41
+ * surface's `RectProvider.rect(id)` is `getBoundingClientRect()`-derived and
42
+ * host-relative (`react-world-authoring-adapter.ts`'s `toHostRelative`) —
43
+ * UNLIKE pan (a pure translation, which cancels out of that subtraction with
44
+ * zero consumer changes, per `world-pan-state.ts`'s own soundness doc
45
+ * comment), a host `scale()` bakes the zoom factor into every rect AND every
46
+ * screen-space gesture delta (`toHostLocal`), so it would require an
47
+ * explicit zoom-aware conversion at all ~14 host-relative chrome consumers
48
+ * (selection/hover box, 8 handles, rotate, labels, spacing bands, measure
49
+ * lines, snap guides, badges, parent outline, drag-ghost, reorder marker,
50
+ * empty-container hints, align-toolbar anchor, marquee) and the 3
51
+ * screen-delta gesture patches (`computeResizePatch`/`computeMovePatch`/
52
+ * `computeSpacingPatch`) — a foundation rebase against the ~2360-test unit
53
+ * suite + the full 27x e2e series (all pinned at the implicit `scale=1`
54
+ * frame) that is disproportionate to one keyboard-row affordance the
55
+ * visual-edit reference itself only had live in its own demo host, not its
56
+ * reusable library. No dead keybinding is registered for it.
57
+ */
58
+
59
+ // The five action functions below (`duplicateSelection` … `nudgeSelection`)
60
+ // are exported for two reasons: `registerEditorHotkeys` wires them to their
61
+ // keybindings below, AND `editor-hotkeys.test.ts` unit-tests the
62
+ // capability-gate behavior (structure/boxEdit absent -> no-op) directly
63
+ // against them, without needing a full `EditorViewport`/canvas fixture.
64
+
65
+ /**
66
+ * D4 (spec27 §6 D4 "Ctrl+D duplicate, Del/Backspace delete — route through
67
+ * adapter.structure.* (capability-gated)"): the pre-D4 `'d'`/`'delete'`/
68
+ * `'backspace'` bindings below called the FIRST-PARTY-only `store.
69
+ * duplicateEntity`/`store.deleteEntities` directly — a silent no-op for a
70
+ * react-world/ingest/scene-UI selection (those ids aren't in `store`'s own
71
+ * `SceneEntity` map). This keeps the first-party path'S existing batched-
72
+ * undo behavior (`store.deleteEntities` pushes ONE undo entry for the whole
73
+ * selection; looping the contract's per-id `structure.remove` would instead
74
+ * push N) byte-identical, and ADDS the override path via `adapter.structure`
75
+ * (capability-gated — a no-op when absent, e.g. the ingested `react-dom`
76
+ * adapter, spec:308-309). `store.selectedEntityIds` is the shared,
77
+ * adapter-routed selection set either way (every adapter's own
78
+ * `selection.set` funnels into it) — only the ACTION differs by path.
79
+ */
80
+ export function duplicateSelection(store: EditorStore): void {
81
+ if (store.selectedEntityIds.size === 0) return;
82
+ if (hasAuthoringOverride()) {
83
+ const structure = getActiveAuthoring(store).structure;
84
+ if (!structure) return;
85
+ for (const id of store.selectedEntityIds) structure.duplicate(id);
86
+ } else {
87
+ for (const id of store.selectedEntityIds) store.duplicateEntity(id);
88
+ }
89
+ }
90
+
91
+ /**
92
+ * D4.R2 (spec27 §6 D4 reopen) — a multi-node override delete prefers the
93
+ * batched `structure.removeMany` (ONE undo entry for the whole selection,
94
+ * matching the first-party `store.deleteEntities` batched-undo semantics
95
+ * below) when the active adapter implements it; falls back to the per-id
96
+ * `structure.remove` loop for any adapter that doesn't (N undo entries).
97
+ * `await`ed the same way `remove` is (harmless no-op for a synchronous/
98
+ * in-memory implementer) — see `StructureProvider.removeMany`'s doc comment.
99
+ *
100
+ * SOURCE-CORRUPTION FIX (bug-panel reopen, post-D4.R2) + DELETE-ORDER-
101
+ * RESIDUAL FIX (bug-panel follow-up, post-50f90a6d): the per-id fallback loop
102
+ * used to fire `structure.remove(id)` for every id in RAW selection order —
103
+ * document/top-first, the same order a marquee/DFS walk produces — with NO
104
+ * `await` between iterations. For an adapter whose per-op target offset is
105
+ * resolved from an index that is only refreshed by a real reparse (never
106
+ * between same-file writes — `ReactWorldAuthoringAdapter`'s `OidStore.index`),
107
+ * that is a real, default-gesture-reachable source-corruption hazard:
108
+ * deleting an EARLIER element first shifts every LATER element's still-stale
109
+ * `{line, col}` out from under it, and firing every write in the same tick
110
+ * can race the underlying network request, letting a later write silently
111
+ * clobber an earlier one. Both mechanisms are fixed for the fallback loop,
112
+ * together (see the CORRECTNESS INVARIANT comment on
113
+ * `ReactWorldAuthoringAdapter.structure`'s per-id `remove` for the full
114
+ * soundness argument, including its own remaining caveat):
115
+ * 1. sort `ids` into REVERSE DOCUMENT ORDER (bottom-most/deepest first) —
116
+ * derived from the SAME DFS pre-order `collectAllNodeIds` walks (the
117
+ * marquee/select-all candidate order), reversed, so a selected
118
+ * descendant is always deleted before a selected ancestor;
119
+ * 2. `await` each `structure.remove(id)` before starting the next, so
120
+ * write N only ever begins once write N-1 has landed on disk.
121
+ * A synchronous/in-memory `remove` (e.g. `UIAuthoringAdapter`'s) is
122
+ * unaffected — awaiting a non-promise return is a harmless no-op, and its own
123
+ * `removeMany` batches instead of ever reaching this loop. This fallback loop
124
+ * is sound ONLY when `collectAllNodeIds`' walk order matches true SOURCE
125
+ * order — `ReactWorldAuthoringAdapter` now implements `removeMany` (a single
126
+ * shared-snapshot batched delete, order-independent by construction) exactly
127
+ * so THIS loop is never reached for it; it stays here, documented and tested,
128
+ * as the honest degrade for any override adapter that has no batched delete.
129
+ */
130
+ export async function deleteSelection(store: EditorStore): Promise<void> {
131
+ if (store.selectedEntityIds.size === 0) return;
132
+ if (hasAuthoringOverride()) {
133
+ const adapter = getActiveAuthoring(store);
134
+ const structure = adapter.structure;
135
+ if (!structure) return;
136
+ const ids = [...store.selectedEntityIds];
137
+ if (structure.removeMany) {
138
+ await structure.removeMany(ids);
139
+ } else {
140
+ // Reverse DOCUMENT order (bottom-most/deepest first) — see this
141
+ // function's own doc comment. `collectAllNodeIds` walks the adapter's
142
+ // hierarchy in the same DFS pre-order (parent-before-child, top-first)
143
+ // a marquee/select-all uses; an id outside that walk (should not
144
+ // happen — every selected id came FROM this adapter's own hierarchy)
145
+ // sorts last (rank -1) as a defensive fallback, never a throw.
146
+ const rank = new Map(collectAllNodeIds(adapter).map((id, i) => [id, i]));
147
+ const ordered = [...ids].sort((a, b) => (rank.get(b) ?? -1) - (rank.get(a) ?? -1));
148
+ for (const id of ordered) await structure.remove(id);
149
+ }
150
+ } else {
151
+ store.deleteEntities([...store.selectedEntityIds]);
152
+ }
153
+ }
154
+
155
+ /** D4 — `Ctrl+Alt+G` wrap / `Ctrl+Shift+G` unwrap (spec §6 D4, §5 "wrap/
156
+ * unwrap ... via `structure.*`"): no first-party equivalent exists (a 3D
157
+ * entity can't be "wrapped"), so this is override-only, capability-gated on
158
+ * the OPTIONAL `structure.wrap`/`structure.unwrap` (absent for the
159
+ * ingested `react-dom` adapter and the first-party threejs adapter alike —
160
+ * both correctly no-op). Applies to every selected id, same multi-select
161
+ * loop `duplicateSelection`/`deleteSelection` use. */
162
+ export function wrapSelection(store: EditorStore): void {
163
+ if (store.selectedEntityIds.size === 0) return;
164
+ const structure = getActiveAuthoring(store).structure;
165
+ if (!structure?.wrap) return;
166
+ for (const id of store.selectedEntityIds) structure.wrap(id);
167
+ }
168
+
169
+ export function unwrapSelection(store: EditorStore): void {
170
+ if (store.selectedEntityIds.size === 0) return;
171
+ const structure = getActiveAuthoring(store).structure;
172
+ if (!structure?.unwrap) return;
173
+ for (const id of store.selectedEntityIds) structure.unwrap(id);
174
+ }
175
+
176
+ /**
177
+ * D4.c (spec §6 D4 "arrow-nudge (↑↓←→, Shift=×10) ... writes margins/
178
+ * offsets"): nudges every currently-selected id whose owner exposes
179
+ * `boxEdit` (capability-gated — silently skips ids that don't, e.g. a 3D
180
+ * entity or an ingested-world node with no spatial write path). Position/
181
+ * margin math is the pure, independently-tested `computeArrowNudgePatch`
182
+ * (`world-overlay-gestures.ts`); this function only resolves the per-id
183
+ * rect/position/margin inputs it needs and brackets the write in a single
184
+ * `begin`/`apply`/`end` (one undo step per nudged node, per B1/D-1).
185
+ */
186
+ export function nudgeSelection(store: EditorStore, dx: number, dy: number): void {
187
+ if (store.selectedEntityIds.size === 0) return;
188
+ const adapter = getActiveAuthoring(store);
189
+ for (const id of store.selectedEntityIds) {
190
+ const boxEdit = boxEditForId(adapter, id);
191
+ if (!boxEdit) continue;
192
+ const rect = rectForId(adapter, id);
193
+ if (!rect) continue;
194
+ const position = adapter.inspector?.get(id, 'style.position');
195
+ const isPositioned = position === 'absolute' || position === 'fixed';
196
+ const spacing = readSpacingValues(adapter, id);
197
+ const patch = computeArrowNudgePatch(dx, dy, isPositioned, rect, {
198
+ left: spacing.marginLeft,
199
+ top: spacing.marginTop,
200
+ });
201
+ if (Object.keys(patch).length === 0) continue;
202
+ boxEdit.begin(id);
203
+ boxEdit.apply(id, patch);
204
+ boxEdit.end(id);
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Register all standard editor hotkeys. Returns a dispose function that removes them.
210
+ * Call once when the viewport is created.
211
+ */
212
+ export function registerEditorHotkeys(
213
+ store: EditorStore,
214
+ viewport: EditorViewport,
215
+ canvas: HTMLCanvasElement,
216
+ ): () => void {
217
+ const unregister = registerHotkeys([
218
+ // --- Global (fire regardless of active panel) ---
219
+ // Undo/redo (T3.2 slice 2): dispatch through the editor-global undo TIMELINE,
220
+ // NOT `hasAuthoringOverride() ? adapter : store` — the timeline pops whichever
221
+ // adapter (first-party store / ingest / UI) recorded the MOST RECENT op,
222
+ // globally, regardless of which one is currently active (docs/
223
+ // PERSISTENCE-PROVIDER-DESIGN.md §3). Ctrl+Y mirrors Ctrl+Shift+Z (Windows
224
+ // convention) — see `undo-timeline.ts`.
225
+ {
226
+ key: 'z',
227
+ mod: true,
228
+ shift: true,
229
+ scope: 'global',
230
+ action: () => timelineRedo(),
231
+ },
232
+ {
233
+ key: 'y',
234
+ mod: true,
235
+ scope: 'global',
236
+ action: () => timelineRedo(),
237
+ },
238
+ {
239
+ key: 'z',
240
+ mod: true,
241
+ scope: 'global',
242
+ action: () => timelineUndo(),
243
+ },
244
+ {
245
+ key: 'c',
246
+ mod: true,
247
+ scope: 'global',
248
+ action: () => {
249
+ if (store.selectedEntityIds.size > 0) store.copyEntities();
250
+ },
251
+ },
252
+ {
253
+ key: 'x',
254
+ mod: true,
255
+ scope: 'global',
256
+ action: () => {
257
+ if (store.selectedEntityIds.size > 0) store.cutEntities();
258
+ },
259
+ },
260
+ {
261
+ key: 'v',
262
+ mod: true,
263
+ scope: 'global',
264
+ action: () => {
265
+ if (store.hasClipboard) store.pasteEntities();
266
+ },
267
+ },
268
+ {
269
+ key: 'd',
270
+ mod: true,
271
+ scope: 'global',
272
+ action: () => duplicateSelection(store),
273
+ },
274
+ // D4 — wrap/unwrap (override-only, capability-gated — see
275
+ // `wrapSelection`/`unwrapSelection`'s doc comments).
276
+ {
277
+ key: 'g',
278
+ mod: true,
279
+ alt: true,
280
+ scope: 'global',
281
+ action: () => wrapSelection(store),
282
+ },
283
+ {
284
+ key: 'g',
285
+ mod: true,
286
+ shift: true,
287
+ scope: 'global',
288
+ action: () => unwrapSelection(store),
289
+ },
290
+ // D4 — arrow-nudge (↑↓←→, Shift=×10 — see `nudgeSelection`'s doc comment).
291
+ { key: 'arrowup', scope: 'global', action: () => nudgeSelection(store, 0, -1) },
292
+ { key: 'arrowup', shift: true, scope: 'global', action: () => nudgeSelection(store, 0, -10) },
293
+ { key: 'arrowdown', scope: 'global', action: () => nudgeSelection(store, 0, 1) },
294
+ { key: 'arrowdown', shift: true, scope: 'global', action: () => nudgeSelection(store, 0, 10) },
295
+ { key: 'arrowleft', scope: 'global', action: () => nudgeSelection(store, -1, 0) },
296
+ { key: 'arrowleft', shift: true, scope: 'global', action: () => nudgeSelection(store, -10, 0) },
297
+ { key: 'arrowright', scope: 'global', action: () => nudgeSelection(store, 1, 0) },
298
+ {
299
+ key: 'arrowright',
300
+ shift: true,
301
+ scope: 'global',
302
+ action: () => nudgeSelection(store, 10, 0),
303
+ },
304
+ {
305
+ key: 'a',
306
+ mod: true,
307
+ scope: 'global',
308
+ action: () => {
309
+ if (hasAuthoringOverride()) {
310
+ const a = getActiveAuthoring(store);
311
+ a.selection?.set(collectAllNodeIds(a)); // select all ingested nodes
312
+ } else store.selectAll();
313
+ },
314
+ },
315
+ { key: 's', mod: true, scope: 'global', action: () => void store.saveNow() },
316
+ // D4 (spec27 §8 "space-pan" row) — Ctrl+0, the conventional "reset
317
+ // zoom/pan to 100%" shortcut (browsers/design tools alike), resets the
318
+ // DOM/react overlay's shared pan translate (`world-pan-state.ts`) back to
319
+ // `(0, 0)`. A no-op when already at rest or when the active surface has
320
+ // no pan-capable world (there is nothing for it to affect either way —
321
+ // `resetWorldPan()` itself no-ops at `(0,0)`).
322
+ { key: '0', mod: true, scope: 'global', action: () => resetWorldPan() },
323
+ {
324
+ key: 'delete',
325
+ scope: 'global',
326
+ action: () => void deleteSelection(store),
327
+ },
328
+ {
329
+ key: 'backspace',
330
+ scope: 'global',
331
+ action: () => void deleteSelection(store),
332
+ },
333
+ {
334
+ key: 'escape',
335
+ scope: 'global',
336
+ // While playing, Escape is owned by play-mode (stop on Escape) — don't also
337
+ // clear the editor selection out from under the running game.
338
+ action: () => {
339
+ if (store.playState === 'stopped') store.select(null);
340
+ },
341
+ },
342
+ {
343
+ key: 'k',
344
+ mod: true,
345
+ scope: 'global',
346
+ action: () => store.toggleCommandPalette(),
347
+ },
348
+ {
349
+ key: '`',
350
+ scope: 'global',
351
+ action: () => window.dispatchEvent(new CustomEvent('editor:toggle-console')),
352
+ },
353
+
354
+ // --- Viewport-scoped (only when viewport/canvas is active) ---
355
+ {
356
+ key: 'w',
357
+ scope: 'viewport',
358
+ action: () => {
359
+ if (!viewport.isFlying) store.setTransformMode('translate');
360
+ },
361
+ },
362
+ {
363
+ key: 'e',
364
+ scope: 'viewport',
365
+ action: () => {
366
+ if (!viewport.isFlying) store.setTransformMode('rotate');
367
+ },
368
+ },
369
+ {
370
+ key: 'r',
371
+ scope: 'viewport',
372
+ action: () => {
373
+ if (!viewport.isFlying) store.setTransformMode('scale');
374
+ },
375
+ },
376
+ {
377
+ key: 'f',
378
+ scope: 'viewport',
379
+ action: () => store.focusOnSelection(),
380
+ },
381
+ {
382
+ key: 'l',
383
+ scope: 'viewport',
384
+ action: () => {
385
+ if (store.selectedEntityIds.size > 0)
386
+ for (const id of store.selectedEntityIds) store.toggleLocked(id);
387
+ },
388
+ },
389
+ {
390
+ key: '.',
391
+ scope: 'viewport',
392
+ action: () => {
393
+ const pivotModes = ['active-element', 'median-point', 'individual-origins'] as const;
394
+ const idx = pivotModes.indexOf(store.pivotMode);
395
+ store.setPivotMode(pivotModes[(idx + 1) % pivotModes.length]!);
396
+ },
397
+ },
398
+ { code: 'Numpad7', key: '', scope: 'viewport', action: () => viewport.setViewPreset('top') },
399
+ { code: 'Numpad1', key: '', scope: 'viewport', action: () => viewport.setViewPreset('front') },
400
+ {
401
+ code: 'Numpad3',
402
+ key: '',
403
+ scope: 'viewport',
404
+ action: () => viewport.setViewPreset('right'),
405
+ },
406
+ {
407
+ code: 'Numpad5',
408
+ key: '',
409
+ scope: 'viewport',
410
+ action: () => viewport.setViewPreset('perspective'),
411
+ },
412
+ ]);
413
+
414
+ const onPointerDown = () => setActiveScope('viewport');
415
+ canvas.addEventListener('pointerdown', onPointerDown);
416
+ installHotkeys();
417
+
418
+ // Vertex snap: hold V to activate
419
+ const onKeyDown = (e: KeyboardEvent) => {
420
+ if (e.key === 'v' && !e.metaKey && !e.ctrlKey && !e.repeat) {
421
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
422
+ store.setVertexSnapActive(true);
423
+ viewport.activateVertexSnap();
424
+ }
425
+ };
426
+ const onKeyUp = (e: KeyboardEvent) => {
427
+ if (e.key === 'v' && !e.metaKey && !e.ctrlKey) {
428
+ store.setVertexSnapActive(false);
429
+ viewport.deactivateVertexSnap();
430
+ }
431
+ };
432
+ window.addEventListener('keydown', onKeyDown);
433
+ window.addEventListener('keyup', onKeyUp);
434
+
435
+ return () => {
436
+ unregister();
437
+ canvas.removeEventListener('pointerdown', onPointerDown);
438
+ window.removeEventListener('keydown', onKeyDown);
439
+ window.removeEventListener('keyup', onKeyUp);
440
+ };
441
+ }
@@ -0,0 +1,2 @@
1
+ /** Layer used for editor-only infrastructure (grid, gizmos, helpers, editor lights). */
2
+ export const EDITOR_LAYER = 31;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Build mode — server-first vs browser-first.
3
+ *
4
+ * Two first-class build profiles, distinguished by Vite's `import.meta.env.MODE`
5
+ * (reliably injected in BOTH dev and build — unlike custom `define`s, which are
6
+ * not statically replaced by the dev middleware server):
7
+ * - 'browser' — the static build (`npm run build:web` → `--mode web`, e.g.
8
+ * GitHub Pages). No server; storage defaults to the in-browser OPFS VFS,
9
+ * with a seeded Starter and an opt-in "Open folder" (File System Access).
10
+ * - 'server' — everything else: the installed app + `npm run dev`. Assumes a
11
+ * Node backend; storage defaults to `HttpStorage` (edits the on-disk project).
12
+ *
13
+ * Both share one codebase; only the default backend + first-run behavior differ.
14
+ */
15
+
16
+ declare const __VGAI_PROJECT_PATH__: string;
17
+
18
+ export type EditorMode = 'server' | 'browser';
19
+
20
+ /** The active build mode. `--mode web` → browser-first; dev/prod → server-first. */
21
+ export function getEditorMode(): EditorMode {
22
+ return import.meta.env.MODE === 'web' ? 'browser' : 'server';
23
+ }
24
+
25
+ /** Absolute project root injected by the dev/prod server (empty in browser mode). */
26
+ export function getProjectDefinePath(): string {
27
+ return typeof __VGAI_PROJECT_PATH__ !== 'undefined' ? __VGAI_PROJECT_PATH__ : '';
28
+ }