@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,369 @@
1
+ import { type RenderScope, resolveRenderSettings } from '@engine/render/render-settings';
2
+ import { applyRendererSettings, applyScenePostProcessing } from '@engine/setup/setup-renderer';
3
+ import { EffectComposer } from 'postprocessing';
4
+ import { useEffect, useRef, useSyncExternalStore } from 'react';
5
+ import * as THREE from 'three';
6
+ import type { CompositeAuthoringAdapter } from '../authoring/composite-authoring-adapter';
7
+ import { mountDesignTimeLayers } from '../authoring/design-time-layers';
8
+ import {
9
+ EDIT_MODE_REBUILD_EVENT,
10
+ exitEditModeAuthoring,
11
+ installEditModeAuthoringForProject,
12
+ } from '../authoring/edit-mode-authoring';
13
+ import { collectState } from '../command-listener';
14
+ import { useEditorStats, useEditorStore } from '../EditorContext';
15
+ import { reportEditorState } from '../editor-api';
16
+ import { connectServerLogs, editorConsole } from '../editor-console';
17
+ import { registerEditorHotkeys } from '../editor-hotkeys';
18
+ import { getEditorMode } from '../editor-mode';
19
+ import type { EditorStore } from '../editor-store';
20
+ import { EditorViewport } from '../editor-viewport';
21
+ import { autoLaunchIngestFromURL, bindIngestMode } from '../ingest-mode';
22
+ import { loadInitialScene } from '../initial-scene';
23
+ import { setupNavMeshHandlers } from '../navmesh-handler';
24
+ import { bindPlayMode } from '../play-mode';
25
+ import { resolvePreviewBoneAttachments } from '../scene-sync';
26
+ import { CameraInfo } from './CameraInfo';
27
+ import { CommandPalette } from './CommandPalette';
28
+ import { NetworkSection } from './NetworkSection';
29
+ import { PlayBar } from './PlayBar';
30
+ import { StatsOverlay } from './StatsOverlay';
31
+ import { ToolStrip } from './Toolbar';
32
+ import { ViewportOverlay } from './ViewportOverlay';
33
+ import { WorldSelectionOverlay } from './WorldSelectionOverlay';
34
+
35
+ function tickEditorAnimations(objectMap: Map<string, THREE.Object3D>, dt: number): void {
36
+ resolvePreviewBoneAttachments(objectMap);
37
+ for (const obj of objectMap.values()) {
38
+ const mixer = obj.userData['_animMixer'] as THREE.AnimationMixer | undefined;
39
+ if (mixer) mixer.update(dt);
40
+ }
41
+ }
42
+
43
+ function buildEntityTagMap(store: EditorStore): Map<string, string[]> {
44
+ const tagMap = new Map<string, string[]>();
45
+ for (const e of store.allEntities()) {
46
+ if (e.id && e.tags && e.tags.length > 0) tagMap.set(e.id, e.tags);
47
+ }
48
+ return tagMap;
49
+ }
50
+
51
+ export function ViewportPanel() {
52
+ const store = useEditorStore();
53
+ const stats = useEditorStats();
54
+ const containerRef = useRef<HTMLDivElement>(null);
55
+ const canvasRef = useRef<HTMLCanvasElement>(null);
56
+
57
+ useEffect(() => {
58
+ const canvas = canvasRef.current!;
59
+ const container = containerRef.current!;
60
+
61
+ // --- Renderer ---
62
+ // antialias is reload-only (renderer construction); shadows/resolutionScale
63
+ // are live and re-applied in rebuildComposer when the environment changes.
64
+ const rendering = store.environment?.rendering as RenderScope | undefined;
65
+ const renderer = new THREE.WebGLRenderer({
66
+ canvas,
67
+ antialias: Boolean(resolveRenderSettings(rendering)['antialias']),
68
+ });
69
+ applyRendererSettings(renderer, rendering, window.devicePixelRatio);
70
+ renderer.setSize(Math.max(1, container.clientWidth), Math.max(1, container.clientHeight));
71
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
72
+ renderer.toneMapping = THREE.NoToneMapping;
73
+
74
+ // --- Post-processing ---
75
+ const composer = new EffectComposer(renderer);
76
+
77
+ // --- Scene ---
78
+ const scene = new THREE.Scene();
79
+ scene.background = new THREE.Color(0xaaaaaa);
80
+
81
+ // --- Bind store to scene ---
82
+ store.bindScene(scene, renderer);
83
+
84
+ // --- Viewport ---
85
+ const viewport = new EditorViewport(canvas, scene, store);
86
+ // Bind the REAL viewport camera + orbit target so store.cameraPose (the
87
+ // `editor.viewport.camera.get` facet, see command-listener.ts's
88
+ // collectState) and the thumbnail-capture path read live data instead of
89
+ // null — previously bindScene's `camera` param was never passed here, so
90
+ // `store.cameraPose` (and _captureThumbnail's camera read) stayed dark.
91
+ store.bindScene(scene, renderer, viewport.batchedRenderer, viewport.camera);
92
+ store.setOrbitTarget(viewport.orbitControls.target);
93
+ // DEV/e2e hook: lets the per-game feature matrix assert the viewport camera
94
+ // reframes (view presets / focus / orbit) on an ingested scene.
95
+ if (import.meta.env.DEV) {
96
+ (window as unknown as Record<string, unknown>)['__vgaiViewport'] = viewport;
97
+ }
98
+
99
+ // --- Composer rebuild ---
100
+ function rebuildComposer(): void {
101
+ // Re-apply live renderer settings (shadows, resolutionScale) so env edits
102
+ // take effect; the post-processing master switch is handled inside
103
+ // applyScenePostProcessing via env.rendering.
104
+ applyRendererSettings(
105
+ renderer,
106
+ store.environment?.rendering as RenderScope | undefined,
107
+ window.devicePixelRatio,
108
+ );
109
+ applyScenePostProcessing(
110
+ composer,
111
+ renderer,
112
+ store.scene ?? scene,
113
+ viewport.camera,
114
+ store.environment,
115
+ store.objectMap,
116
+ buildEntityTagMap(store),
117
+ );
118
+ }
119
+
120
+ // --- Store subscription → sync viewport ---
121
+ // Rebuild the post-processing chain only when its inputs actually change
122
+ // (environment / scene structure / play-stop swap), tracked by composerVersion.
123
+ // Previously this ran on EVERY notify, so a gizmo drag — which notifies once
124
+ // per pointer-move — reconstructed every EffectPass and recompiled shaders
125
+ // 60×/s, causing severe stutter on any scene with post effects enabled.
126
+ let lastComposerVersion = store.composerVersion;
127
+ const unsubStore = store.subscribe(() => {
128
+ // Swap viewport scene if the store's scene changed (play mode enter/exit)
129
+ const storeScene = store.scene;
130
+ if (storeScene && storeScene !== viewport.currentScene) {
131
+ viewport.setScene(storeScene);
132
+ }
133
+ viewport.objectMap = store.objectMap;
134
+ viewport.syncFromStore();
135
+ if (store.composerVersion !== lastComposerVersion) {
136
+ lastComposerVersion = store.composerVersion;
137
+ rebuildComposer();
138
+ }
139
+ });
140
+
141
+ // --- Viewport action subscription ---
142
+ const unsubActions = store.onViewportAction((action) => {
143
+ switch (action.type) {
144
+ case 'focus-entity': {
145
+ const obj = store.objectMap.get(action.id);
146
+ if (obj) viewport.focusOn(obj);
147
+ break;
148
+ }
149
+ case 'focus-selection': {
150
+ const objects = [...store.selectedEntityIds]
151
+ .map((id) => store.objectMap.get(id))
152
+ .filter((o): o is THREE.Object3D => o !== undefined);
153
+ viewport.focusOnMultiple(objects);
154
+ break;
155
+ }
156
+ case 'set-view-preset':
157
+ viewport.setViewPreset(action.preset);
158
+ break;
159
+ case 'set-camera-pose':
160
+ viewport.setPose(action.position, action.target, action.fov);
161
+ break;
162
+ }
163
+ });
164
+
165
+ // --- Hotkeys ---
166
+ const unregisterHotkeys = registerEditorHotkeys(store, viewport, canvas);
167
+
168
+ // --- NavMesh ---
169
+ const disposeNavMesh = setupNavMeshHandlers(store, viewport, scene);
170
+
171
+ // --- Play-mode ---
172
+ bindPlayMode({ store, getViewportCamera: () => viewport.camera });
173
+ bindIngestMode(store);
174
+
175
+ // --- Error capture → editor console ---
176
+ const onError = (e: ErrorEvent) => {
177
+ editorConsole.error(e.message || String(e.error), 'runtime');
178
+ };
179
+ const onUnhandledRejection = (e: PromiseRejectionEvent) => {
180
+ editorConsole.error(`Unhandled promise rejection: ${e.reason}`, 'runtime');
181
+ };
182
+ window.addEventListener('error', onError);
183
+ window.addEventListener('unhandledrejection', onUnhandledRejection);
184
+
185
+ // --- Server logs ---
186
+ connectServerLogs();
187
+
188
+ // --- Resize ---
189
+ const resizeObserver = new ResizeObserver(() => {
190
+ // Docking, HMR, and tab switches can briefly collapse the panel to 0x0.
191
+ // Three's EffectComposer allocates incomplete render targets at that
192
+ // size, then floods the console when the shared RAF renders the frame.
193
+ const w = Math.max(1, container.clientWidth);
194
+ const h = Math.max(1, container.clientHeight);
195
+ renderer.setSize(w, h);
196
+ composer.setSize(w, h);
197
+ viewport.resize(w, h);
198
+ });
199
+ resizeObserver.observe(container);
200
+
201
+ // --- RAF loop ---
202
+ let lastTime = performance.now();
203
+ let rafId = 0;
204
+
205
+ function animate(): void {
206
+ rafId = requestAnimationFrame(animate);
207
+ const now = performance.now();
208
+ stats.frameTime = now - lastTime;
209
+ stats.fps = 1000 / stats.frameTime;
210
+ const dt = stats.frameTime / 1000;
211
+ lastTime = now;
212
+
213
+ viewport.update(dt);
214
+ if (store.playState === 'stopped') {
215
+ tickEditorAnimations(store.objectMap, dt);
216
+ }
217
+ viewport.batchedRenderer.update(dt);
218
+ composer.render(dt);
219
+ viewport.renderViewCube(renderer);
220
+ viewport.renderCameraPreview(renderer);
221
+
222
+ stats.drawCalls = renderer.info.render.calls;
223
+ stats.triangles = renderer.info.render.triangles;
224
+
225
+ // Update camera info for CameraInfo panel
226
+ const cam = viewport.camera.position;
227
+ const tgt = viewport.orbitControls.target;
228
+ stats.cameraPosition.x = cam.x;
229
+ stats.cameraPosition.y = cam.y;
230
+ stats.cameraPosition.z = cam.z;
231
+ stats.cameraTarget.x = tgt.x;
232
+ stats.cameraTarget.y = tgt.y;
233
+ stats.cameraTarget.z = tgt.z;
234
+ }
235
+
236
+ // --- Start rendering, then load the initial scene ---
237
+ // loadInitialScene must run after bindScene so store._scene is set
238
+ // and _rebuildScene can populate the entity tree.
239
+ viewport.objectMap = store.objectMap;
240
+ viewport.syncFromStore();
241
+ rebuildComposer();
242
+ animate();
243
+ // `disposed` guards the unmount-during-fetch race: if cleanup runs before
244
+ // the manifest fetch resolves, tear the just-installed composite down
245
+ // immediately (and never let a late-resolving install leave a stale
246
+ // override behind).
247
+ let disposed = false;
248
+ let editModeComposite: CompositeAuthoringAdapter | undefined;
249
+ let disposeDesignTimeLayers: (() => void) | undefined;
250
+
251
+ /**
252
+ * (A1) fetch `vgai.game.json` and install the edit-mode composite over
253
+ * every declared world, then (B1) composite every OTHER declared world's
254
+ * design-time layer into THIS SAME container (the threejs canvas above
255
+ * keeps its existing slot, untouched). Callable more than once: the
256
+ * initial mount (below) AND (C3) the `'editor:edit-mode-rebuild'`
257
+ * listener — e.g. after the "Add UI Canvas" hierarchy-menu action seeds
258
+ * the scene's first `ui` root, the composite needs rebuilding so its new
259
+ * scene-UI child appears. Tears down any PREVIOUS edit-mode session
260
+ * first, so a rebuild never leaks the old composite/layers.
261
+ */
262
+ async function installAll(): Promise<void> {
263
+ disposeDesignTimeLayers?.();
264
+ disposeDesignTimeLayers = undefined;
265
+ if (editModeComposite) {
266
+ exitEditModeAuthoring(editModeComposite);
267
+ editModeComposite = undefined;
268
+ }
269
+ const composite = await installEditModeAuthoringForProject(store);
270
+ editModeComposite = composite;
271
+ if (disposed) {
272
+ exitEditModeAuthoring(composite);
273
+ editModeComposite = undefined;
274
+ return;
275
+ }
276
+ disposeDesignTimeLayers = mountDesignTimeLayers(container, composite, store);
277
+ }
278
+
279
+ function onEditModeRebuild(): void {
280
+ void installAll();
281
+ }
282
+ window.addEventListener(EDIT_MODE_REBUILD_EVENT, onEditModeRebuild);
283
+
284
+ // Load the scene, then install the edit-mode composite — BEFORE the
285
+ // ?ingest= auto-launch below, so a later ingest override always wins the
286
+ // shared active-authoring slot deterministically (never raced by this
287
+ // fetch). Then (if the URL carries ?ingest=<id>) auto-launch that
288
+ // unmodified game so the ingest flow is drivable from a plain hosted link.
289
+ loadInitialScene(store)
290
+ .then(() => {
291
+ // B3-followup fix: `connectCommandListener`'s ONE-TIME initial
292
+ // `reportEditorState` (EditorContext.tsx's mount effect) fires before
293
+ // this scene finishes loading — command-listener.ts otherwise only
294
+ // re-reports state after a RELAYED COMMAND, so `editor.hierarchy.
295
+ // inspect`/`editor.viewport.camera.get` would see a stale, empty
296
+ // entityCount/no-camera snapshot forever if no command happens to be
297
+ // sent first. Report once more right after the scene (and thus the
298
+ // camera binding just above) is actually ready — a real state
299
+ // transition, not a fabricated poke. No-ops harmlessly in browser
300
+ // mode (no server to receive it — reportEditorState is best-effort).
301
+ if (getEditorMode() !== 'browser') reportEditorState(collectState(store));
302
+ })
303
+ .then(() => installAll())
304
+ .then(() => autoLaunchIngestFromURL(store));
305
+
306
+ // --- Cleanup ---
307
+ return () => {
308
+ disposed = true;
309
+ cancelAnimationFrame(rafId);
310
+ resizeObserver.disconnect();
311
+ unsubStore();
312
+ unsubActions();
313
+ unregisterHotkeys();
314
+ disposeNavMesh();
315
+ window.removeEventListener(EDIT_MODE_REBUILD_EVENT, onEditModeRebuild);
316
+ disposeDesignTimeLayers?.();
317
+ exitEditModeAuthoring(editModeComposite);
318
+ window.removeEventListener('error', onError);
319
+ window.removeEventListener('unhandledrejection', onUnhandledRejection);
320
+ viewport.dispose();
321
+ // EffectComposer owns input/output WebGLRenderTargets + per-pass resources
322
+ // that renderer.dispose() does NOT free — dispose it to avoid leaking GPU
323
+ // render targets on unmount (incl. React StrictMode's mount/unmount/remount).
324
+ composer.dispose();
325
+ renderer.dispose();
326
+ };
327
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps -- runs once on mount
328
+
329
+ // Subscribe to store for overlay visibility
330
+ useSyncExternalStore(store.subscribe, store.getSnapshot);
331
+
332
+ return (
333
+ <div
334
+ ref={containerRef}
335
+ style={{
336
+ position: 'absolute',
337
+ top: 0,
338
+ left: 0,
339
+ width: '100%',
340
+ height: '100%',
341
+ overflow: 'hidden',
342
+ pointerEvents: 'auto',
343
+ }}
344
+ >
345
+ <canvas
346
+ ref={canvasRef}
347
+ data-testid="editor-canvas"
348
+ aria-hidden="true"
349
+ tabIndex={-1}
350
+ style={{ width: '100%', height: '100%', display: 'block', pointerEvents: 'auto' }}
351
+ />
352
+ {/* Spec 27 §3 A3 — mounted as a CHILD of THIS SAME containerRef div, the exact
353
+ DOM parent `mountDesignTimeLayers(container, …)` above appends each react/pixi
354
+ world's own `position:absolute; inset:0` layer into, so `RectProvider.rect(id)`'s
355
+ host-relative geometry lines up with this overlay's own coordinate space with no
356
+ correction (see WorldSelectionOverlay.tsx's own doc comment). Self-gates to a
357
+ no-op when the active composite has no rect-capable (DOM) child, so a 3D-only
358
+ project renders nothing extra here. */}
359
+ <WorldSelectionOverlay />
360
+ <PlayBar />
361
+ <ToolStrip />
362
+ <ViewportOverlay />
363
+ {store.showStats && <StatsOverlay />}
364
+ {store.playState !== 'stopped' && <NetworkSection />}
365
+ <CameraInfo />
366
+ {store.commandPaletteOpen && <CommandPalette onClose={() => store.closeCommandPalette()} />}
367
+ </div>
368
+ );
369
+ }
@@ -0,0 +1,257 @@
1
+ /**
2
+ * WorldContextMenu — spec 27 §6 D3.a (docs/unified-world-editor/
3
+ * 27-visual-react-editing.md) right-click canvas context menu: Duplicate /
4
+ * Wrap / Unwrap / Delete / Select-Parent, plus an Insert-child ▸ submenu
5
+ * (D3.e) when the owning adapter reports `structure.creatableKinds`. Styled
6
+ * after (but a SEPARATE component from) `GameHierarchy.tsx`'s hierarchy-row
7
+ * `ContextMenu` — that one opens from a right-click on a HIERARCHY ROW; this
8
+ * one opens from a right-click on the CANVAS overlay, resolving its target
9
+ * node via `adapter.pickable` (see `WorldSelectionOverlay`'s `onContextMenu`
10
+ * handler) rather than a row's own known id. Both ultimately drive the SAME
11
+ * `AuthoringAdapter.structure` contract, so the two menus intentionally look
12
+ * alike.
13
+ *
14
+ * Rule zero: this file talks ONLY to the `AuthoringAdapter` contract (via the
15
+ * owner-routing helpers in `world-overlay-gestures.ts` — `structureForId`,
16
+ * not a concrete adapter cast) + `hierarchy`/`selection`, which ARE
17
+ * merged/forwarded on `CompositeAuthoringAdapter` (unlike `rects`/`boxEdit`/
18
+ * `text`/`structure`'s OWNER-specific reads here). No `UIEditStore`/
19
+ * `SceneEntity`/`THREE.`/`elementFromPoint`.
20
+ */
21
+ import type { AuthoringAdapter } from '@engine/adapter';
22
+ import { useEffect, useRef, useState } from 'react';
23
+ import { createPortal } from 'react-dom';
24
+ import { structureForId } from './world-overlay-gestures';
25
+
26
+ export interface WorldContextMenuState {
27
+ x: number;
28
+ y: number;
29
+ nodeId: string;
30
+ }
31
+
32
+ export interface WorldContextMenuProps {
33
+ adapter: AuthoringAdapter;
34
+ state: WorldContextMenuState;
35
+ onClose: () => void;
36
+ }
37
+
38
+ const MENU_BG = '#2c2c2c';
39
+ const MENU_BORDER = '#444';
40
+ const ITEM_HOVER = '#3a3a3a';
41
+
42
+ function menuItemStyle(color?: string): React.CSSProperties {
43
+ return { padding: '5px 12px', cursor: 'pointer', color: color ?? '#c5c8ce' };
44
+ }
45
+
46
+ function hoverHandlers(): {
47
+ onMouseEnter: (e: React.MouseEvent<HTMLDivElement>) => void;
48
+ onMouseLeave: (e: React.MouseEvent<HTMLDivElement>) => void;
49
+ } {
50
+ return {
51
+ onMouseEnter: (e) => {
52
+ (e.currentTarget as HTMLDivElement).style.background = ITEM_HOVER;
53
+ },
54
+ onMouseLeave: (e) => {
55
+ (e.currentTarget as HTMLDivElement).style.background = 'transparent';
56
+ },
57
+ };
58
+ }
59
+
60
+ /** The canvas right-click menu (D3.a) — a pickable node's Duplicate/Wrap/
61
+ * Unwrap/Delete/Select-Parent, capability-gated on the owning adapter's
62
+ * `structure` provider (absent for an ingested `react-dom` world — no
63
+ * structural items render at all, only Select-Parent, which needs only
64
+ * `hierarchy`/`selection`). */
65
+ export function WorldContextMenu({
66
+ adapter,
67
+ state,
68
+ onClose,
69
+ }: WorldContextMenuProps): React.ReactNode {
70
+ const menuRef = useRef<HTMLDivElement>(null);
71
+ const [insertOpen, setInsertOpen] = useState(false);
72
+
73
+ useEffect(() => {
74
+ const handler = (e: MouseEvent): void => {
75
+ if (menuRef.current && !menuRef.current.contains(e.target as Node)) onClose();
76
+ };
77
+ const keyHandler = (e: KeyboardEvent): void => {
78
+ if (e.key === 'Escape') onClose();
79
+ };
80
+ document.addEventListener('mousedown', handler);
81
+ document.addEventListener('keydown', keyHandler);
82
+ return () => {
83
+ document.removeEventListener('mousedown', handler);
84
+ document.removeEventListener('keydown', keyHandler);
85
+ };
86
+ }, [onClose]);
87
+
88
+ const { nodeId } = state;
89
+ const node = adapter.hierarchy.node(nodeId);
90
+ if (!node) return null;
91
+
92
+ const structure = structureForId(adapter, nodeId);
93
+ const creatableKinds = structure?.creatableKinds?.(nodeId) ?? [];
94
+ const parentId = node.parentId;
95
+
96
+ const items: { testId: string; label: string; action: () => void; color?: string }[] = [];
97
+ if (parentId !== null) {
98
+ items.push({
99
+ testId: 'world-context-menu-select-parent',
100
+ label: 'Select Parent',
101
+ action: () => {
102
+ adapter.selection?.set([parentId]);
103
+ onClose();
104
+ },
105
+ });
106
+ }
107
+ if (structure) {
108
+ items.push({
109
+ testId: 'world-context-menu-duplicate',
110
+ label: 'Duplicate',
111
+ action: () => {
112
+ structure.duplicate(nodeId);
113
+ onClose();
114
+ },
115
+ });
116
+ if (structure.wrap) {
117
+ items.push({
118
+ testId: 'world-context-menu-wrap',
119
+ label: 'Wrap',
120
+ action: () => {
121
+ structure.wrap!(nodeId);
122
+ onClose();
123
+ },
124
+ });
125
+ }
126
+ if (structure.unwrap) {
127
+ items.push({
128
+ testId: 'world-context-menu-unwrap',
129
+ label: 'Unwrap',
130
+ action: () => {
131
+ structure.unwrap!(nodeId);
132
+ onClose();
133
+ },
134
+ });
135
+ }
136
+ }
137
+
138
+ // Portaled straight to `document.body` (spec-27 §8 pan-seam fix): this
139
+ // component renders as a DOM descendant of `WorldSelectionOverlay`'s outer
140
+ // wrapper, which space-pan gives a live `transform` when pan≠0. A non-`none`
141
+ // transform on an ancestor becomes the CSS containing block for a
142
+ // `position: fixed` descendant, so — un-portaled — this menu's `left`/`top`
143
+ // (raw `clientX`/`clientY` from the triggering right-click, see
144
+ // `WorldSelectionOverlay`'s `onContextMenu`) would resolve against the
145
+ // transformed wrapper instead of the viewport once panned, displacing the
146
+ // menu away from the cursor. Portaling to `document.body` — which never
147
+ // carries that transform — restores `position: fixed`'s normal
148
+ // viewport-relative meaning regardless of pan. `menuRef` still points at
149
+ // the REAL DOM node either way (a portal only changes WHERE React inserts
150
+ // the subtree, not the node itself), so the outside-click/Escape-to-close
151
+ // handlers below — native `document` listeners doing DOM `.contains()`
152
+ // checks — are unaffected by the portal.
153
+ return createPortal(
154
+ <div
155
+ ref={menuRef}
156
+ data-testid="world-context-menu"
157
+ style={{
158
+ position: 'fixed',
159
+ left: state.x,
160
+ top: state.y,
161
+ background: MENU_BG,
162
+ border: `1px solid ${MENU_BORDER}`,
163
+ borderRadius: 4,
164
+ padding: '4px 0',
165
+ zIndex: 1000,
166
+ minWidth: 150,
167
+ maxWidth: 260,
168
+ boxShadow: '0 4px 12px rgba(0,0,0,0.5)',
169
+ fontSize: 12,
170
+ color: '#c5c8ce',
171
+ }}
172
+ >
173
+ {items.map((item) => (
174
+ <div
175
+ key={item.testId}
176
+ data-testid={item.testId}
177
+ role="menuitem"
178
+ tabIndex={0}
179
+ onClick={item.action}
180
+ onKeyDown={(e) => {
181
+ if (e.key === 'Enter') item.action();
182
+ }}
183
+ style={menuItemStyle(item.color)}
184
+ {...hoverHandlers()}
185
+ >
186
+ {item.label}
187
+ </div>
188
+ ))}
189
+ {/* D3.e — Insert-child submenu, gated on the owning adapter's
190
+ `structure.creatableKinds` (react-world only today; the ingested
191
+ react-dom adapter has no `structure` at all, so `structure` is
192
+ `null` here and this whole block is skipped). */}
193
+ {structure && creatableKinds.length > 0 && (
194
+ <div>
195
+ <div
196
+ data-testid="world-context-menu-insert-child"
197
+ role="menuitem"
198
+ tabIndex={0}
199
+ onClick={() => setInsertOpen((v) => !v)}
200
+ onKeyDown={(e) => {
201
+ if (e.key === 'Enter') setInsertOpen((v) => !v);
202
+ }}
203
+ style={menuItemStyle()}
204
+ {...hoverHandlers()}
205
+ >
206
+ Insert child ▸
207
+ </div>
208
+ {insertOpen &&
209
+ creatableKinds.map((k) => (
210
+ <div
211
+ key={k.kind}
212
+ data-testid={`world-context-menu-insert-child-${k.kind}`}
213
+ role="menuitem"
214
+ tabIndex={0}
215
+ onClick={() => {
216
+ structure.create(k.kind, nodeId);
217
+ onClose();
218
+ }}
219
+ onKeyDown={(e) => {
220
+ if (e.key === 'Enter') {
221
+ structure.create(k.kind, nodeId);
222
+ onClose();
223
+ }
224
+ }}
225
+ style={{ padding: '5px 12px 5px 24px', cursor: 'pointer' }}
226
+ {...hoverHandlers()}
227
+ >
228
+ {k.label}
229
+ </div>
230
+ ))}
231
+ </div>
232
+ )}
233
+ {structure && (
234
+ <div
235
+ data-testid="world-context-menu-delete"
236
+ role="menuitem"
237
+ tabIndex={0}
238
+ onClick={() => {
239
+ structure.remove(nodeId);
240
+ onClose();
241
+ }}
242
+ onKeyDown={(e) => {
243
+ if (e.key === 'Enter') {
244
+ structure.remove(nodeId);
245
+ onClose();
246
+ }
247
+ }}
248
+ style={menuItemStyle('#ff6b6b')}
249
+ {...hoverHandlers()}
250
+ >
251
+ Delete
252
+ </div>
253
+ )}
254
+ </div>,
255
+ document.body,
256
+ );
257
+ }