@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,221 @@
1
+ import type { SceneEntity } from '@engine/scene/scene-types';
2
+ import * as THREE from 'three';
3
+ import { EDITOR_LAYER } from './editor-layers';
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Handle definition
7
+ // ---------------------------------------------------------------------------
8
+
9
+ export interface HandleDef {
10
+ /** Unique handle ID within the entity, e.g. 'light-distance' */
11
+ id: string;
12
+ /** The entity ID this handle belongs to */
13
+ entityId: string;
14
+ /** Compute the handle's world position from the current entity state */
15
+ getWorldPosition: (entityObj: THREE.Object3D, entity: SceneEntity) => THREE.Vector3;
16
+ /** Called during drag — mutates the entity descriptor directly. */
17
+ onDrag: (newWorldPos: THREE.Vector3, entityObj: THREE.Object3D, entity: SceneEntity) => void;
18
+ /** Handle color (default yellow) */
19
+ color?: number;
20
+ }
21
+
22
+ export interface HandleMesh extends THREE.Mesh {
23
+ userData: {
24
+ editorHandle: true;
25
+ handleDef: HandleDef;
26
+ };
27
+ }
28
+
29
+ const HANDLE_COLOR = 0xffaa00;
30
+ const HANDLE_HOVER_COLOR = 0xffffff;
31
+
32
+ const handleGeo = new THREE.SphereGeometry(1, 10, 10);
33
+
34
+ /** Create a handle mesh for a given definition. */
35
+ export function createHandleMesh(def: HandleDef): HandleMesh {
36
+ const mat = new THREE.MeshBasicMaterial({
37
+ color: def.color ?? HANDLE_COLOR,
38
+ depthTest: false,
39
+ transparent: true,
40
+ opacity: 0.85,
41
+ });
42
+ const mesh = new THREE.Mesh(handleGeo, mat) as unknown as HandleMesh;
43
+ mesh.renderOrder = 998;
44
+ mesh.layers.set(EDITOR_LAYER);
45
+ mesh.userData = { editorHandle: true, handleDef: def };
46
+ return mesh;
47
+ }
48
+
49
+ /** Set a handle mesh to its hovered state. */
50
+ export function setHandleHovered(mesh: HandleMesh, hovered: boolean): void {
51
+ const mat = mesh.material as THREE.MeshBasicMaterial;
52
+ mat.color.setHex(hovered ? HANDLE_HOVER_COLOR : (mesh.userData.handleDef.color ?? HANDLE_COLOR));
53
+ }
54
+
55
+ /** Scale a handle to maintain constant screen size. */
56
+ export function scaleHandle(mesh: THREE.Mesh, camera: THREE.Camera): void {
57
+ const dist = mesh.position.distanceTo(camera.position);
58
+ const s = dist * 0.012;
59
+ mesh.scale.setScalar(s);
60
+ }
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Handle definitions per entity type
64
+ // ---------------------------------------------------------------------------
65
+
66
+ /** Create handle definitions for an entity. Returns empty array if no handles apply. */
67
+ export function createHandleDefs(entity: SceneEntity): HandleDef[] {
68
+ const defs: HandleDef[] = [];
69
+ const eid = entity.id!;
70
+
71
+ // -- Point light distance --
72
+ if (entity.light?.type === 'point' && entity.light.distance) {
73
+ defs.push({
74
+ id: 'light-distance',
75
+ entityId: eid,
76
+ getWorldPosition: (obj) => {
77
+ const local = new THREE.Vector3(entity.light!.distance ?? 5, 0, 0);
78
+ return local.applyMatrix4(obj.matrixWorld);
79
+ },
80
+ onDrag: (newWorld, obj, ent) => {
81
+ const inv = obj.matrixWorld.clone().invert();
82
+ const local = newWorld.clone().applyMatrix4(inv);
83
+ const dist = Math.max(0.1, local.length());
84
+ ent.light!.distance = Math.round(dist * 100) / 100;
85
+ },
86
+ });
87
+ }
88
+
89
+ // -- Spot light distance + angle --
90
+ if (entity.light?.type === 'spot') {
91
+ const dist = entity.light.distance ?? 10;
92
+ if (dist > 0) {
93
+ defs.push({
94
+ id: 'spot-distance',
95
+ entityId: eid,
96
+ getWorldPosition: (obj) => {
97
+ const local = new THREE.Vector3(0, 0, -(entity.light!.distance ?? 10));
98
+ return local.applyMatrix4(obj.matrixWorld);
99
+ },
100
+ onDrag: (newWorld, obj, ent) => {
101
+ const inv = obj.matrixWorld.clone().invert();
102
+ const local = newWorld.clone().applyMatrix4(inv);
103
+ ent.light!.distance = Math.max(0.1, Math.round(Math.abs(local.z) * 100) / 100);
104
+ },
105
+ });
106
+ }
107
+ defs.push({
108
+ id: 'spot-angle',
109
+ entityId: eid,
110
+ color: 0xff8844,
111
+ getWorldPosition: (obj) => {
112
+ const angle = entity.light!.angle ?? Math.PI / 6;
113
+ const d = entity.light!.distance ?? 10;
114
+ const r = Math.tan(angle) * d;
115
+ const local = new THREE.Vector3(r, 0, -d);
116
+ return local.applyMatrix4(obj.matrixWorld);
117
+ },
118
+ onDrag: (newWorld, obj, ent) => {
119
+ const inv = obj.matrixWorld.clone().invert();
120
+ const local = newWorld.clone().applyMatrix4(inv);
121
+ const d = Math.abs(local.z) || 0.1;
122
+ const r = Math.abs(local.x);
123
+ const angle = Math.atan2(r, d);
124
+ ent.light!.angle = Math.max(
125
+ 0.01,
126
+ Math.min(Math.PI / 2 - 0.01, Math.round(angle * 1000) / 1000),
127
+ );
128
+ },
129
+ });
130
+ }
131
+
132
+ // -- Audio source ref/max distance --
133
+ if (entity.audio?.spatial !== false && entity.audio) {
134
+ defs.push({
135
+ id: 'audio-refDistance',
136
+ entityId: eid,
137
+ color: 0x44bbff,
138
+ getWorldPosition: (obj) => {
139
+ const local = new THREE.Vector3(entity.audio!.refDistance ?? 1, 0, 0);
140
+ return local.applyMatrix4(obj.matrixWorld);
141
+ },
142
+ onDrag: (newWorld, obj, ent) => {
143
+ const inv = obj.matrixWorld.clone().invert();
144
+ const local = newWorld.clone().applyMatrix4(inv);
145
+ ent.audio!.refDistance = Math.max(0.01, Math.round(local.length() * 100) / 100);
146
+ },
147
+ });
148
+ if ((entity.audio.maxDistance ?? 10000) < 10000) {
149
+ defs.push({
150
+ id: 'audio-maxDistance',
151
+ entityId: eid,
152
+ color: 0x2277aa,
153
+ getWorldPosition: (obj) => {
154
+ const local = new THREE.Vector3(entity.audio!.maxDistance ?? 100, 0, 0);
155
+ return local.applyMatrix4(obj.matrixWorld);
156
+ },
157
+ onDrag: (newWorld, obj, ent) => {
158
+ const inv = obj.matrixWorld.clone().invert();
159
+ const local = newWorld.clone().applyMatrix4(inv);
160
+ ent.audio!.maxDistance = Math.max(
161
+ (ent.audio!.refDistance ?? 1) + 0.1,
162
+ Math.round(local.length() * 100) / 100,
163
+ );
164
+ },
165
+ });
166
+ }
167
+ }
168
+
169
+ // -- Camera FOV --
170
+ if (entity.camera?.type === 'perspective') {
171
+ defs.push({
172
+ id: 'camera-fov',
173
+ entityId: eid,
174
+ color: 0xcccc44,
175
+ getWorldPosition: (obj) => {
176
+ const fov = entity.camera!.fov ?? 60;
177
+ const halfRad = THREE.MathUtils.degToRad(fov / 2);
178
+ const d = 3; // fixed forward distance for the handle
179
+ const y = Math.tan(halfRad) * d;
180
+ const local = new THREE.Vector3(0, y, -d);
181
+ return local.applyMatrix4(obj.matrixWorld);
182
+ },
183
+ onDrag: (newWorld, obj, ent) => {
184
+ const inv = obj.matrixWorld.clone().invert();
185
+ const local = newWorld.clone().applyMatrix4(inv);
186
+ const d = Math.abs(local.z) || 0.1;
187
+ const y = Math.abs(local.y);
188
+ const halfRad = Math.atan2(y, d);
189
+ const fov = THREE.MathUtils.radToDeg(halfRad) * 2;
190
+ ent.camera!.fov = Math.max(5, Math.min(170, Math.round(fov * 10) / 10));
191
+ },
192
+ });
193
+ }
194
+
195
+ // -- Spline control points --
196
+ if (entity.spline && entity.spline.points.length >= 2) {
197
+ for (let i = 0; i < entity.spline.points.length; i++) {
198
+ defs.push({
199
+ id: `spline-point-${i}`,
200
+ entityId: eid,
201
+ color: 0x00ccff,
202
+ getWorldPosition: (obj) => {
203
+ const p = entity.spline!.points[i]!;
204
+ const local = new THREE.Vector3(p[0], p[1], p[2]);
205
+ return local.applyMatrix4(obj.matrixWorld);
206
+ },
207
+ onDrag: (newWorld, obj, ent) => {
208
+ const inv = obj.matrixWorld.clone().invert();
209
+ const local = newWorld.clone().applyMatrix4(inv);
210
+ ent.spline!.points[i] = [
211
+ Math.round(local.x * 1000) / 1000,
212
+ Math.round(local.y * 1000) / 1000,
213
+ Math.round(local.z * 1000) / 1000,
214
+ ];
215
+ },
216
+ });
217
+ }
218
+ }
219
+
220
+ return defs;
221
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Hierarchy context-menu contribution registry (A2,
3
+ * docs/unified-world-editor/26-unified-editor-spec.md §2 A2) — the escape
4
+ * hatch for first-party-only row actions that are NOT YET contract-expressible
5
+ * through `StructureProvider`/`InspectorProvider` (prefab unpack/open/save-as
6
+ * today; a future tag-search or similar joins the same seam). `GameHierarchy`
7
+ * (the contract-only shell) calls {@link getHierarchyMenuItems} and appends
8
+ * whatever comes back to its own Rename/Duplicate/Show-Hide/Lock/Delete items
9
+ * — it never branches on WHICH registration fired, keeping Rule Zero intact
10
+ * (the shell still speaks only the `AuthoringAdapter` contract; only the
11
+ * REGISTRATION module, e.g. `authoring/first-party-hierarchy-menu.ts`, is
12
+ * allowed to reach into store internals).
13
+ *
14
+ * PERMANENT adapter-module contribution seam (blessed by C2, docs/unified-
15
+ * world-editor/26-unified-editor-spec.md §4 C2 — the same status as C2's own
16
+ * `inspector-section-registry.ts`): first-party affordances that aren't (yet,
17
+ * or ever) contract-expressible register through here instead of the
18
+ * contract-only shell branching on adapter identity. If a given affordance
19
+ * later becomes contract-expressible, its OWN registration is removed at
20
+ * that point — the registry mechanism itself is not temporary.
21
+ */
22
+
23
+ import type { EditorStore } from './editor-store';
24
+
25
+ export interface HierarchyMenuContext {
26
+ /** The row's node id (an `EditorNode.id` — format-neutral, not a `SceneEntity` id). */
27
+ readonly nodeId: string;
28
+ readonly store: EditorStore;
29
+ }
30
+
31
+ export interface HierarchyMenuItem {
32
+ readonly label: string;
33
+ readonly action: () => void;
34
+ readonly color?: string;
35
+ }
36
+
37
+ type Matcher = (ctx: HierarchyMenuContext) => boolean;
38
+ type ItemsFactory = (ctx: HierarchyMenuContext) => HierarchyMenuItem[];
39
+
40
+ interface Registration {
41
+ readonly match: Matcher;
42
+ readonly items: ItemsFactory;
43
+ }
44
+
45
+ const registrations: Registration[] = [];
46
+
47
+ /**
48
+ * Register a set of first-party (or other non-contract) hierarchy context-menu
49
+ * items. `match` decides whether this registration applies to a given row;
50
+ * `items` builds the actual entries when it does (called only when `match`
51
+ * returns true). Returns an unregister function.
52
+ */
53
+ export function registerHierarchyMenuItems(match: Matcher, items: ItemsFactory): () => void {
54
+ const reg: Registration = { match, items };
55
+ registrations.push(reg);
56
+ return () => {
57
+ const idx = registrations.indexOf(reg);
58
+ if (idx >= 0) registrations.splice(idx, 1);
59
+ };
60
+ }
61
+
62
+ /** Every registered item that applies to `ctx`, in registration order. */
63
+ export function getHierarchyMenuItems(ctx: HierarchyMenuContext): HierarchyMenuItem[] {
64
+ return registrations.filter((r) => r.match(ctx)).flatMap((r) => r.items(ctx));
65
+ }
66
+
67
+ /** Test-only reset (mirrors `undo-timeline.ts`'s `__resetUndoTimelineForTest`). */
68
+ export function __resetHierarchyMenuRegistryForTest(): void {
69
+ registrations.length = 0;
70
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Pure row-flattening for the hierarchy tree (no React) — extracted so the
3
+ * child-cap logic is unit-testable without rendering the panel.
4
+ */
5
+
6
+ import type { SceneEntity } from '@engine/scene/scene-types';
7
+
8
+ /** Max children shown per parent before a "… N more" row (when capping is on). */
9
+ export const CHILD_CAP = 20;
10
+ export const MORE_ID_PREFIX = '__more__:';
11
+
12
+ export interface TreeRow {
13
+ entity: SceneEntity;
14
+ depth: number;
15
+ parentId: string | null;
16
+ childIndex: number;
17
+ hasChildren: boolean;
18
+ isOpen: boolean;
19
+ /** Present on a synthetic "… N more" row that reveals capped siblings. */
20
+ more?: { parentKey: string; hidden: number };
21
+ }
22
+
23
+ /** Stable key for a parent's child-limit bookkeeping (root uses a sentinel). */
24
+ export function parentKeyOf(parentId: string | null): string {
25
+ return parentId ?? '__root__';
26
+ }
27
+
28
+ /**
29
+ * Flatten a `SceneEntity` tree into display rows, honoring collapsed nodes and
30
+ * (when `capEnabled`) a per-parent child cap with a synthetic "… N more" row.
31
+ * `expanded` maps a parent key → how many children it has been asked to reveal.
32
+ */
33
+ export function flattenVisibleRows(
34
+ entities: SceneEntity[],
35
+ closedIds: Set<string>,
36
+ expanded: Map<string, number>,
37
+ capEnabled: boolean,
38
+ parentId: string | null = null,
39
+ depth = 0,
40
+ ): TreeRow[] {
41
+ const rows: TreeRow[] = [];
42
+ const parentKey = parentKeyOf(parentId);
43
+ // Cap each sibling list so a huge live scene graph (play mode) doesn't dump
44
+ // thousands of rows at once. A "… N more" row reveals the next page on click.
45
+ const limit = capEnabled
46
+ ? Math.max(CHILD_CAP, expanded.get(parentKey) ?? CHILD_CAP)
47
+ : Number.POSITIVE_INFINITY;
48
+ const shown = Math.min(entities.length, limit);
49
+ for (let i = 0; i < shown; i++) {
50
+ const entity = entities[i]!;
51
+ const id = entity.id!;
52
+ const hasChildren = (entity.children?.length ?? 0) > 0;
53
+ const isOpen = hasChildren && !closedIds.has(id);
54
+ rows.push({ entity, depth, parentId, childIndex: i, hasChildren, isOpen });
55
+ if (isOpen && entity.children) {
56
+ rows.push(
57
+ ...flattenVisibleRows(entity.children, closedIds, expanded, capEnabled, id, depth + 1),
58
+ );
59
+ }
60
+ }
61
+ if (entities.length > shown) {
62
+ rows.push({
63
+ entity: { id: `${MORE_ID_PREFIX}${parentKey}`, name: '' } as SceneEntity,
64
+ depth,
65
+ parentId,
66
+ childIndex: shown,
67
+ hasChildren: false,
68
+ isOpen: false,
69
+ more: { parentKey, hidden: entities.length - shown },
70
+ });
71
+ }
72
+ return rows;
73
+ }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Hosted-example routing — the static/browser build's `?project=<exampleId>`
3
+ * deep-link scheme (docs/EXAMPLES-AS-PROJECTS-DESIGN.md §H, Track H).
4
+ *
5
+ * In the hosted editor every `examples/<id>/` project is staged at
6
+ * `/examples/<id>/` by the web build (`vite-plugin-example-assets.ts` copies
7
+ * `public/*` plus the `vgai.game.json` manifest) and its code ships inside the
8
+ * pre-bundled project scripts, dispatched by `EditorPreview.exampleId`
9
+ * (`browser-play-entry.ts`). This module supplies the routing glue between
10
+ * those two existing seams:
11
+ *
12
+ * - `openExampleProject(id)` — resolve `?project=<id>` to an ActiveProject by
13
+ * fetching the staged manifest; namespaces ALL relative asset resolution
14
+ * (editor-side prefab/material/scene fetches included) via `setAssetPrefix`.
15
+ * - `exampleDispatchFor(...)` — the play-mode decision: dispatch the bundled
16
+ * example setup, or play the OPEN scene generically but keep the example's
17
+ * asset prefix (`?scene=` override within the project).
18
+ * - `legacyPoolSearch(...)` — rewrite pre-Track-H `?scene=/examples/scenes/…`
19
+ * pool links (the deleted flat scene pool) onto the project scheme, so
20
+ * published lesson URLs and bookmarks keep working.
21
+ *
22
+ * Example projects open READ-ONLY: their scenes load with no save path, which
23
+ * makes autosave an inert no-op (see `editor-store.ts`) — edits are in-memory
24
+ * for the session. To keep changes, scaffold the example into a real project
25
+ * (`create-vgai-project <name> --example <id>`).
26
+ */
27
+
28
+ import { setAssetPrefix } from '@engine/loader';
29
+ import { GameManifestSchema } from '@engine/manifest/schema';
30
+ import type { ActiveProject } from './project-manager';
31
+
32
+ /** Folder-name shape shared by every `examples/<id>/` — also a path-safety gate. */
33
+ const EXAMPLE_ID_RE = /^[a-z0-9][a-z0-9-]*$/;
34
+
35
+ /** Fetch + validate the staged manifest for a bundled example and derive the
36
+ * ActiveProject the rest of the editor consumes. Throws on unknown id /
37
+ * missing staging / invalid manifest — the caller decides the fallback. */
38
+ export async function openExampleProject(exampleId: string): Promise<ActiveProject> {
39
+ if (!EXAMPLE_ID_RE.test(exampleId)) {
40
+ throw new Error(`Invalid example id "${exampleId}"`);
41
+ }
42
+ const manifestUrl = `/examples/${exampleId}/vgai.game.json`;
43
+ const res = await fetch(manifestUrl);
44
+ if (!res.ok) {
45
+ throw new Error(`HTTP ${res.status} fetching ${manifestUrl} — not a bundled example?`);
46
+ }
47
+ const manifest = GameManifestSchema.parse(await res.json());
48
+
49
+ // Namespace relative asset resolution to this example's staged public/ —
50
+ // the same prefix `browser-play-entry.ts` sets at play dispatch, applied
51
+ // here so EDITOR-side fetches (scenes, prefabs, .mat files via
52
+ // `resolveUrl`) resolve against the project too.
53
+ setAssetPrefix(`/examples/${exampleId}/`);
54
+
55
+ return {
56
+ rootPath: `/examples/${exampleId}`,
57
+ config: {
58
+ name: manifest.name,
59
+ defaultScene: manifest.worlds[0]?.scene ?? null,
60
+ ...(manifest.resolution ? { resolution: manifest.resolution } : {}),
61
+ },
62
+ exampleId,
63
+ };
64
+ }
65
+
66
+ /**
67
+ * The play-mode routing decision for a hosted example project — pure, so it is
68
+ * unit-testable without booting play mode. Returns the EditorPreview fields to
69
+ * spread into the preview object:
70
+ *
71
+ * - `{ exampleId }` — the project's shipped game: dispatch the bundled setup
72
+ * (its own registry + gameplay systems; it loads its own manifest scene).
73
+ * - `{ assetPrefix }` — a `?scene=` override inside the project (e.g.
74
+ * feature-scenes' unmounted extras): play the editor's OPEN scene through
75
+ * the generic loader, but keep asset refs resolving into the example.
76
+ * - `{}` — not an example project, or the user opened a VFS scene of their
77
+ * own (savePath set): today's generic behavior, root prefix.
78
+ */
79
+ export function exampleDispatchFor(
80
+ exampleId: string | undefined,
81
+ savePath: string | null,
82
+ hasSceneOverride: boolean,
83
+ ): { exampleId?: string; assetPrefix?: string } {
84
+ if (!exampleId || savePath !== null) return {};
85
+ if (hasSceneOverride) return { assetPrefix: `/examples/${exampleId}/` };
86
+ return { exampleId };
87
+ }
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // Legacy flat-pool links (`?scene=/examples/scenes/<name>.vscn.json`)
91
+ // ---------------------------------------------------------------------------
92
+
93
+ /** Pool scenes that live in the scene-only `feature-scenes` project now. */
94
+ const FEATURE_SCENES_POOL = new Set([
95
+ 'instanced-grid', // feature-scenes' manifest default — no scene override needed
96
+ 'learn-skybox',
97
+ 'ocean',
98
+ 'particles',
99
+ 'terrain',
100
+ 'water-shader',
101
+ ]);
102
+
103
+ /** Pool scenes whose example folder name differs from the scene file name. */
104
+ const RENAMED_POOL: Record<string, string> = {
105
+ arena: 'third-person-arena',
106
+ };
107
+
108
+ /** Pool scenes whose name IS their example id (verified on disk by
109
+ * `hosted-example-routing.test.ts` — every target's manifest + scene exist). */
110
+ const IDENTITY_POOL = new Set([
111
+ 'editor-tutorial',
112
+ 'fps',
113
+ 'ik',
114
+ 'learn-joints',
115
+ 'learn-matfiles',
116
+ 'learn-splines',
117
+ 'learn-triggers',
118
+ 'littlest-tokyo',
119
+ 'rts',
120
+ 'third-person',
121
+ 'vehicle',
122
+ ]);
123
+
124
+ /** Map a legacy pool scene name to its `?project=` route, or null if unknown. */
125
+ export function legacyPoolRoute(name: string): { project: string; scene?: string } | null {
126
+ if (FEATURE_SCENES_POOL.has(name)) {
127
+ return name === 'instanced-grid'
128
+ ? { project: 'feature-scenes' }
129
+ : { project: 'feature-scenes', scene: `scenes/${name}.vscn.json` };
130
+ }
131
+ const project = RENAMED_POOL[name] ?? (IDENTITY_POOL.has(name) ? name : null);
132
+ return project ? { project } : null;
133
+ }
134
+
135
+ /**
136
+ * Rewrite a legacy pool deep link's search string onto the project scheme.
137
+ * Returns the new search string (other params preserved), or null when the
138
+ * URL is not a known legacy pool link (leave it alone).
139
+ */
140
+ export function legacyPoolSearch(search: string): string | null {
141
+ const params = new URLSearchParams(search);
142
+ const scene = params.get('scene');
143
+ if (!scene || params.get('project')) return null;
144
+ const m = /^\/examples\/scenes\/([a-z0-9-]+)\.vscn\.json$/.exec(scene);
145
+ if (!m || m[1] === undefined) return null;
146
+ const route = legacyPoolRoute(m[1]);
147
+ if (!route) return null;
148
+ params.delete('scene');
149
+ params.set('project', route.project);
150
+ if (route.scene) params.set('scene', route.scene);
151
+ return `?${params.toString()}`;
152
+ }
153
+
154
+ /** Apply the legacy rewrite to the live URL (history.replaceState, no reload). */
155
+ export function applyLegacyPoolRedirect(): void {
156
+ const rewritten = legacyPoolSearch(window.location.search);
157
+ if (rewritten !== null) {
158
+ window.history.replaceState(null, '', `${window.location.pathname}${rewritten}`);
159
+ }
160
+ }
package/src/hotkeys.ts ADDED
@@ -0,0 +1,142 @@
1
+ import { useEffect } from 'react';
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Types
5
+ // ---------------------------------------------------------------------------
6
+
7
+ export type HotkeyScope = 'viewport' | 'hierarchy' | 'inspector' | 'asset-browser' | 'global';
8
+
9
+ export interface HotkeyBinding {
10
+ /** The key to match (`e.key`, lowercase). Ignored when `code` is set. */
11
+ key: string;
12
+ /** Match on `e.code` instead of `e.key` (for numpad keys). */
13
+ code?: string;
14
+ /** Require Ctrl (Win/Linux) or Cmd (Mac). */
15
+ mod?: boolean;
16
+ /** Require Shift. */
17
+ shift?: boolean;
18
+ /** Require Alt/Option (D4 — `wrap`'s `Ctrl+Alt+G`). */
19
+ alt?: boolean;
20
+ /** Scope this binding belongs to. `'global'` bindings fire regardless of panel focus. */
21
+ scope: HotkeyScope;
22
+ /** Action to execute when the binding matches. */
23
+ action: (e: KeyboardEvent) => void;
24
+ /** Human-readable label for menus / tooltips. */
25
+ label?: string;
26
+ }
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Active scope (which panel was last interacted with)
30
+ // ---------------------------------------------------------------------------
31
+
32
+ let _activeScope: HotkeyScope = 'viewport';
33
+
34
+ export function getActiveScope(): HotkeyScope {
35
+ return _activeScope;
36
+ }
37
+
38
+ export function setActiveScope(scope: HotkeyScope): void {
39
+ _activeScope = scope;
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Binding registry
44
+ // ---------------------------------------------------------------------------
45
+
46
+ const _bindings: HotkeyBinding[] = [];
47
+
48
+ /**
49
+ * Register hotkey bindings. Returns a dispose function that removes them.
50
+ */
51
+ export function registerHotkeys(bindings: HotkeyBinding[]): () => void {
52
+ _bindings.push(...bindings);
53
+ return () => {
54
+ for (const b of bindings) {
55
+ const idx = _bindings.indexOf(b);
56
+ if (idx >= 0) _bindings.splice(idx, 1);
57
+ }
58
+ };
59
+ }
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Dispatch
63
+ // ---------------------------------------------------------------------------
64
+
65
+ function handleKeyDown(e: KeyboardEvent): void {
66
+ // Skip when typing in form inputs — D4 (spec27 §6 D4 "each shortcut fires
67
+ // only when not typing") widens this to `[contenteditable]` too (no
68
+ // in-repo editor uses one today — `WorldTextEditor`/`GameHierarchy`'s
69
+ // rename both use a real `<textarea>`/`<input>`, already covered above —
70
+ // but the guard should hold for a future contentEditable surface without
71
+ // needing another fix here).
72
+ if (
73
+ e.target instanceof HTMLInputElement ||
74
+ e.target instanceof HTMLTextAreaElement ||
75
+ e.target instanceof HTMLSelectElement ||
76
+ (e.target instanceof HTMLElement && e.target.isContentEditable)
77
+ )
78
+ return;
79
+
80
+ const mod = e.metaKey || e.ctrlKey;
81
+ const key = e.key.toLowerCase();
82
+ const code = e.code;
83
+ const scope = _activeScope;
84
+
85
+ // Check scoped bindings first, then global
86
+ const passes: HotkeyScope[] = scope === 'global' ? ['global'] : [scope, 'global'];
87
+
88
+ for (const pass of passes) {
89
+ for (const b of _bindings) {
90
+ if (b.scope !== pass) continue;
91
+
92
+ const keyMatch = b.code ? b.code === code : b.key === key;
93
+ if (!keyMatch) continue;
94
+ if ((b.mod ?? false) !== mod) continue;
95
+ if ((b.shift ?? false) !== e.shiftKey) continue;
96
+ if ((b.alt ?? false) !== e.altKey) continue;
97
+
98
+ e.preventDefault();
99
+ b.action(e);
100
+ return;
101
+ }
102
+ }
103
+ }
104
+
105
+ let _installed = false;
106
+
107
+ /** Attach the global keydown listener. Call once at startup. */
108
+ export function installHotkeys(): void {
109
+ if (_installed) return;
110
+ window.addEventListener('keydown', handleKeyDown);
111
+ _installed = true;
112
+ }
113
+
114
+ /** Detach the global keydown listener. */
115
+ export function uninstallHotkeys(): void {
116
+ window.removeEventListener('keydown', handleKeyDown);
117
+ _installed = false;
118
+ }
119
+
120
+ /**
121
+ * True once this shared hotkey system is installed (the real editor's
122
+ * `registerEditorHotkeys`/`installHotkeys`, called when the viewport mounts).
123
+ * `false` in a standalone surface that never mounts a viewport (e.g. the UI-editor
124
+ * harness) — used so a panel that has its OWN local keydown handling (T3.2 slice
125
+ * 2: `UIEditor.tsx`'s undo/redo) can tell whether THIS system will already
126
+ * dispatch the key, and skip acting itself rather than double-dispatching.
127
+ */
128
+ export function isHotkeysInstalled(): boolean {
129
+ return _installed;
130
+ }
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // React hook for panel-local shortcuts
134
+ // ---------------------------------------------------------------------------
135
+
136
+ /**
137
+ * Register hotkey bindings for the lifetime of a component.
138
+ * Pass a stable array (defined outside render or wrapped in useMemo).
139
+ */
140
+ export function useHotkeys(bindings: HotkeyBinding[]): void {
141
+ useEffect(() => registerHotkeys(bindings), [bindings]);
142
+ }