@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,671 @@
1
+ /**
2
+ * `Inspector` — the ONE inspector shell (C2, docs/unified-world-editor/26-
3
+ * unified-editor-spec.md §4 C2). Renders straight from the active
4
+ * {@link AuthoringAdapter}'s providers (`hierarchy` / `selection` /
5
+ * `transforms` / `inspector`) — format-neutral `EditorNode`s and
6
+ * `PropertyDescriptor`s, NO `SceneEntity` — PLUS whatever
7
+ * `inspector-section-registry.ts` registrations match the current
8
+ * selection/adapter (`authoring/first-party-inspector/register.tsx` for the
9
+ * threejs adapter, `authoring/ui-inspector-sections.tsx` for the UI
10
+ * adapter). A matched registration may `suppresses` one or more of this
11
+ * file's own built-in blocks it fully replaces (e.g. the first-party
12
+ * `ComponentsSection` suppresses the generic `components` grid block below)
13
+ * so a matched selection never double-renders the same affordance — see
14
+ * `inspector-section-registry.ts`'s header comment for the full design.
15
+ *
16
+ * A2 (docs/unified-world-editor/26-unified-editor-spec.md §2 A2) deleted this
17
+ * file's HIERARCHY half (`IngestHierarchy`/`IngestRowView`/`IngestMoreRow`/
18
+ * `KIND_GLYPH`) — its row-rendering + context-menu logic moved into
19
+ * `components/GameHierarchy.tsx`, the one contract-only hierarchy panel that
20
+ * now serves every session shape (bare single-scene, ingested game,
21
+ * multi-world composite). This file keeps only the INSPECTOR half; C2 folded
22
+ * the former bespoke `InspectorPanel.tsx` into it as registered sections
23
+ * (deleting the A3 dispatch seam that used to choose between the two).
24
+ */
25
+
26
+ import type { AuthoringAdapter, EditorNode, PropertyDescriptor, Transform } from '@engine/adapter';
27
+ import { useSyncExternalStore } from 'react';
28
+ import * as THREE from 'three';
29
+ import { getActiveAuthoring } from '../authoring/active-adapter';
30
+ import { CompositeAuthoringAdapter } from '../authoring/composite-authoring-adapter';
31
+ import { ReactWorldAuthoringAdapter } from '../authoring/react-world-authoring-adapter';
32
+ import { useEditorStore } from '../EditorContext';
33
+ import { setActiveScope } from '../hotkeys';
34
+ import { isIngestActiveReact } from '../ingest-mode';
35
+ import type { InspectorBuiltin } from '../inspector-section-registry';
36
+ import {
37
+ inspectorSectionRegistryVersion,
38
+ matchedInspectorSections,
39
+ subscribeInspectorSectionRegistry,
40
+ } from '../inspector-section-registry';
41
+ import { groupProperties } from './inspector-property-grouping';
42
+ import { Panel } from './primitives/Panel';
43
+
44
+ /**
45
+ * D4 (B2) — the CHILD adapter owning `nodeId`'s synthetic world-group row
46
+ * (`world:<id>`, `CompositeAuthoringAdapter`'s own manufactured node), or
47
+ * `null` when `adapter` isn't a composite or `nodeId` isn't one of its group
48
+ * nodes. Mirrors `GameHierarchy.tsx`'s (unexported) `compositeGroupBadge` —
49
+ * same "ask the public T0 query (`childAdapters()`/`groupNodeId()`), never
50
+ * decode the reserved id yourself" discipline, this file's own use of it.
51
+ * Used ONLY to gate the "Stories" section below: a world-group row whose
52
+ * owning child adapter itself advertises `stories` (a live react world) gets
53
+ * the section (dropdown or "no stories" disclosure); any other row (an
54
+ * ordinary entity, or a world-group row for a kind that never sets
55
+ * `stories` — threejs/pixi/an un-upgraded Boundary placeholder) gets none.
56
+ */
57
+ function worldGroupChildAdapter(
58
+ adapter: AuthoringAdapter,
59
+ nodeId: string,
60
+ ): AuthoringAdapter | null {
61
+ if (!(adapter instanceof CompositeAuthoringAdapter)) return null;
62
+ for (const child of adapter.childAdapters()) {
63
+ if (adapter.groupNodeId(child.worldId) === nodeId) return child.adapter;
64
+ }
65
+ return null;
66
+ }
67
+
68
+ /**
69
+ * B3 (docs/unified-world-editor/26-unified-editor-spec.md §3 B3) — the CHILD
70
+ * adapter owning a `kind:'component'` CATALOG node (as opposed to
71
+ * `worldGroupChildAdapter`'s `world:<id>` GROUP row above). Resolved by
72
+ * ACTUAL ownership — `CompositeAuthoringAdapter.ownerOf`/`childAdapters()`,
73
+ * the same public T0 query `worldGroupChildAdapter` uses — never by decoding
74
+ * the catalog id's `catalog:` prefix here (Rule zero: this file stays
75
+ * format-neutral about adapter-minted ids; only `node?.kind === 'component'`
76
+ * is this file's own knowledge). `null` for anything that isn't a composite,
77
+ * or a component node the composite doesn't (yet) recognize.
78
+ */
79
+ function catalogNodeOwnerAdapter(
80
+ adapter: AuthoringAdapter,
81
+ node: EditorNode | null,
82
+ nodeId: string,
83
+ ): AuthoringAdapter | null {
84
+ if (!node || node.kind !== 'component') return null;
85
+ if (!(adapter instanceof CompositeAuthoringAdapter)) return null;
86
+ const worldId = adapter.ownerOf(nodeId);
87
+ if (!worldId) return null;
88
+ return adapter.childAdapters().find((c) => c.worldId === worldId)?.adapter ?? null;
89
+ }
90
+
91
+ /** B3 — `true` when `nodeId` (a catalog node) IS the owning react adapter's
92
+ * currently-isolated node — the SAME "ask the owning child" discipline as
93
+ * `worldGroupChildAdapter`'s `stories` read, never decoded from the id. */
94
+ function isCatalogNodeIsolated(storiesOwner: AuthoringAdapter | null, nodeId: string): boolean {
95
+ return (
96
+ storiesOwner instanceof ReactWorldAuthoringAdapter && storiesOwner.isolatedNodeId === nodeId
97
+ );
98
+ }
99
+
100
+ /** B3 — the Isolate toggle's click handler: isolate the catalog node against
101
+ * whichever story is currently selected (the SAME dropdown value the
102
+ * `ingest-stories-select` reuses), or exit isolation back to the entry. */
103
+ function toggleCatalogIsolation(
104
+ adapter: AuthoringAdapter,
105
+ nodeId: string,
106
+ isIsolated: boolean,
107
+ ): void {
108
+ if (isIsolated) {
109
+ adapter.stories?.isolate?.(null);
110
+ return;
111
+ }
112
+ const storyId = adapter.stories?.active(nodeId) ?? undefined;
113
+ adapter.stories?.isolate?.(nodeId, storyId);
114
+ }
115
+
116
+ const NUM_STYLE: React.CSSProperties = {
117
+ width: 64,
118
+ background: '#1e1e1e',
119
+ color: '#c5c8ce',
120
+ border: '1px solid #333',
121
+ borderRadius: 2,
122
+ padding: '3px 5px',
123
+ fontSize: 12,
124
+ };
125
+
126
+ function AxisRow({
127
+ label,
128
+ values,
129
+ testidPrefix,
130
+ onCommit,
131
+ }: {
132
+ label: string;
133
+ values: [number, number, number];
134
+ testidPrefix: string;
135
+ onCommit: (axis: 0 | 1 | 2, v: number) => void;
136
+ }) {
137
+ const axes: Array<{ a: 0 | 1 | 2; n: string }> = [
138
+ { a: 0, n: 'x' },
139
+ { a: 1, n: 'y' },
140
+ { a: 2, n: 'z' },
141
+ ];
142
+ return (
143
+ <div style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '4px 8px' }}>
144
+ <span style={{ width: 56, fontSize: 12, color: '#9aa0a6' }}>{label}</span>
145
+ {axes.map(({ a, n }) => (
146
+ <input
147
+ key={n}
148
+ type="number"
149
+ data-testid={`${testidPrefix}-${n}`}
150
+ value={Number.isFinite(values[a]) ? Math.round(values[a] * 1000) / 1000 : 0}
151
+ onChange={(e) => onCommit(a, Number.parseFloat(e.target.value))}
152
+ style={NUM_STYLE}
153
+ />
154
+ ))}
155
+ </div>
156
+ );
157
+ }
158
+
159
+ export function Inspector() {
160
+ const store = useEditorStore();
161
+ useSyncExternalStore(store.subscribe, store.getSnapshot);
162
+ // Re-render when section registrations change (W3): project inspector
163
+ // TOOLS register asynchronously (folder scan) and re-register on HMR —
164
+ // possibly while a matching selection is already showing. First-party
165
+ // registrations are import-time side effects, so this never fired for them.
166
+ useSyncExternalStore(subscribeInspectorSectionRegistry, inspectorSectionRegistryVersion);
167
+ const adapter = getActiveAuthoring(store);
168
+ const id = [...store.selectedEntityIds][0] ?? null;
169
+
170
+ // No inspector surface: either an opaque prebuilt bundle (iframe floor) or an
171
+ // ingested native-React world (no-authoring by design, D-N8) — the honest copy
172
+ // differs, so branch on which session is active.
173
+ if (!adapter.capabilities.inspectorFields) {
174
+ return (
175
+ <Panel
176
+ name="Inspector (embed-only)"
177
+ data-testid="inspector-panel"
178
+ onPointerDown={() => setActiveScope('inspector')}
179
+ >
180
+ <div
181
+ data-testid="ingest-embed-note"
182
+ style={{ padding: 12, color: '#c9a227', fontSize: 12, lineHeight: 1.5 }}
183
+ >
184
+ {isIngestActiveReact() ? (
185
+ <>
186
+ <strong>Embed-only.</strong> This is an ingested native-React game: its components
187
+ render directly as a DOM layer under the host React — there is no scene graph or
188
+ renderer to introspect, and authoring is deliberately off for react ingests (the
189
+ editor never writes JSX back into an unmodified game). The game is hosted, sized, and
190
+ disposed; play it in the Game tab.
191
+ </>
192
+ ) : (
193
+ <>
194
+ <strong>Embed-only.</strong> This bundle runs its own renderer in a sandboxed iframe
195
+ the editor cannot reach into — it is hosted and sized, but its scene is not
196
+ introspectable. (This demo's payload is a self-contained canvas app; a real opaque
197
+ three.js bundle, whose realm-local <code>three</code> is unreachable, behaves
198
+ identically.) Source-available games run under the editor's bundler get full live
199
+ editing instead.
200
+ </>
201
+ )}
202
+ </div>
203
+ </Panel>
204
+ );
205
+ }
206
+
207
+ // Nothing selected → environment / scene-settings panel. A matched
208
+ // registration (e.g. the first-party adapter's rich scene Environment
209
+ // section) fully replaces the generic ingest-only background/fog note
210
+ // below (its `suppresses: ['environment']` is exactly why `matched.length
211
+ // > 0` here implies the built-in branch would have been suppressed anyway).
212
+ //
213
+ // `matched`/`suppressed`/`panelNameOverride` are resolved SEPARATELY per
214
+ // branch (here, and again below `id` is confirmed truthy) rather than
215
+ // once up front against a shared `node = id ? adapter.hierarchy.node(id)
216
+ // : null` — collapsing "nothing selected" and "a selected id the current
217
+ // adapter's hierarchy can't resolve" into the same `node === null` would
218
+ // let a first-party composite's null-selection Environment registration
219
+ // (see `authoring/first-party-inspector/register.tsx`'s
220
+ // `isFirstPartySelection` fallback) wrongly match while a (stale/foreign)
221
+ // id is still selected.
222
+ if (!id) {
223
+ const matched = matchedInspectorSections(null, adapter);
224
+ const panelNameOverride = matched.map((reg) => reg.panelName).find((n): n is string => !!n);
225
+ if (matched.length > 0) {
226
+ return (
227
+ <Panel
228
+ name={panelNameOverride ?? 'Environment (ingested game)'}
229
+ data-testid="inspector-panel"
230
+ onPointerDown={() => setActiveScope('inspector')}
231
+ style={{ overflowY: 'auto', scrollbarWidth: 'none' }}
232
+ >
233
+ {matched.map(({ Section }, i) => (
234
+ <Section key={i} adapter={adapter} nodeId={null} />
235
+ ))}
236
+ </Panel>
237
+ );
238
+ }
239
+ const scene = store.scene;
240
+ const bg =
241
+ scene?.background instanceof THREE.Color ? `#${scene.background.getHexString()}` : '#000000';
242
+ const hasFog = !!scene?.fog;
243
+ return (
244
+ <Panel
245
+ name="Environment (ingested game)"
246
+ data-testid="inspector-panel"
247
+ onPointerDown={() => setActiveScope('inspector')}
248
+ >
249
+ <div style={{ pointerEvents: 'auto', padding: 4 }}>
250
+ <div style={{ padding: 8, color: '#777', fontSize: 12 }}>
251
+ Scene settings — nothing selected.
252
+ </div>
253
+ <div style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '6px 8px' }}>
254
+ <span style={{ width: 80, fontSize: 12, color: '#9aa0a6' }}>Background</span>
255
+ <input
256
+ type="color"
257
+ data-testid="ingest-env-background"
258
+ value={bg}
259
+ onChange={(e) => {
260
+ if (scene) {
261
+ scene.background = new THREE.Color(e.target.value);
262
+ store.notifyIngestEdit();
263
+ }
264
+ }}
265
+ />
266
+ </div>
267
+ <label
268
+ style={{
269
+ display: 'flex',
270
+ gap: 6,
271
+ alignItems: 'center',
272
+ padding: '6px 8px',
273
+ fontSize: 12,
274
+ }}
275
+ >
276
+ <input
277
+ type="checkbox"
278
+ data-testid="ingest-env-fog"
279
+ checked={hasFog}
280
+ onChange={(e) => {
281
+ if (!scene) return;
282
+ scene.fog = e.target.checked ? new THREE.Fog(0x8899aa, 1, 80) : null;
283
+ store.notifyIngestEdit();
284
+ }}
285
+ />
286
+ Fog
287
+ </label>
288
+ </div>
289
+ </Panel>
290
+ );
291
+ }
292
+
293
+ const node = adapter.hierarchy.node(id);
294
+ // A registered section only matches a REAL, resolved node — an id the
295
+ // current adapter's hierarchy can't resolve (`node === null` here, despite
296
+ // `id` being truthy) never matches (mirrors the deleted A3 dispatch's own
297
+ // `ownerOf(id) === null -> false`), so it renders the plain generic
298
+ // built-ins below exactly as it always has, never a first-party section.
299
+ const matched = node ? matchedInspectorSections(node, adapter) : [];
300
+ const suppressed = new Set<InspectorBuiltin>();
301
+ for (const reg of matched) {
302
+ for (const b of reg.suppresses ?? []) suppressed.add(b);
303
+ }
304
+ const panelNameOverride = matched.map((reg) => reg.panelName).find((n): n is string => !!n);
305
+ const t = adapter.transforms?.get(id) ?? {
306
+ position: [0, 0, 0],
307
+ rotation: [0, 0, 0, 1],
308
+ scale: [1, 1, 1],
309
+ };
310
+ // The full property set is DRIVEN BY the adapter's InspectorProvider — a generic
311
+ // PropertyDescriptor list (name/visible/material/light/camera/mesh), not a
312
+ // hardcoded panel. 'name' is rendered at the header; the rest as a generic grid.
313
+ const props = (adapter.inspector?.properties(id) ?? []).filter((p) => p.path !== 'name');
314
+
315
+ // D4 (B2) — story picker gating: the section itself only appears for a
316
+ // world-group row whose owning child adapter has a `stories` provider at
317
+ // all (see `worldGroupChildAdapter`'s doc comment); WITHIN that, the
318
+ // dropdown is gated on `storiesFor(id).length > 0` — NEVER on
319
+ // `adapter.stories` truthiness alone, since the composite's OWN `stories`
320
+ // pass-through is always present regardless of what (if anything) the
321
+ // routed child actually offers.
322
+ //
323
+ // B3 — a `kind:'component'` catalog node ALSO gets this section (its own
324
+ // owner resolution, `catalogNodeOwnerAdapter`, since it isn't a `world:<id>`
325
+ // group row) — the SAME story dropdown (reused verbatim), plus an Isolate
326
+ // toggle that renders the component ALONE against whichever story is
327
+ // currently selected.
328
+ const isCatalogNode = node?.kind === 'component';
329
+ const storiesOwner =
330
+ worldGroupChildAdapter(adapter, id) ?? catalogNodeOwnerAdapter(adapter, node, id);
331
+ const storyRefs = storiesOwner?.stories ? (adapter.stories?.storiesFor(id) ?? []) : [];
332
+ const showStoriesSection = !!storiesOwner?.stories || isCatalogNode;
333
+ const isIsolated = isCatalogNode && isCatalogNodeIsolated(storiesOwner, id);
334
+
335
+ const commitTransform = (axis: 0 | 1 | 2, key: keyof Transform, v: number) => {
336
+ if (!Number.isFinite(v)) return;
337
+ const next: Transform = {
338
+ position: [...t.position] as [number, number, number],
339
+ rotation: [...t.rotation] as [number, number, number, number],
340
+ scale: [...t.scale] as [number, number, number],
341
+ };
342
+ (next[key] as number[])[axis] = v;
343
+ adapter.transforms?.beginEdit(id);
344
+ adapter.transforms?.apply(id, next);
345
+ adapter.transforms?.endEdit(id);
346
+ store.notifyIngestEdit();
347
+ };
348
+ const setProp = (path: string, value: unknown) => {
349
+ adapter.inspector?.set(id, path, value);
350
+ store.notifyIngestEdit();
351
+ };
352
+ // Stable testid: keep the legacy ones the e2e relies on.
353
+ const testidFor = (path: string) =>
354
+ path === 'visible' ? 'ingest-visible' : `ingest-prop-${path.replace(/\./g, '-')}`;
355
+
356
+ // Per-descriptor row renderer (T3.4 slice 2, docs/ADAPTER-AUTHORING-DESIGN.md
357
+ // §1.E) — extracted so BOTH the flat (ungrouped) list and each titled group
358
+ // sub-section below render a `PropertyDescriptor` identically. Behavior is
359
+ // unchanged from before `group` existed; this is a pure refactor of the
360
+ // same per-`p.type` switch.
361
+ const renderProperty = (p: PropertyDescriptor) => {
362
+ const v = adapter.inspector?.get(id, p.path);
363
+ if (p.type === 'boolean') {
364
+ return (
365
+ <label
366
+ key={p.path}
367
+ style={{
368
+ display: 'flex',
369
+ gap: 6,
370
+ alignItems: 'center',
371
+ padding: '6px 8px',
372
+ fontSize: 12,
373
+ }}
374
+ >
375
+ <input
376
+ type="checkbox"
377
+ data-testid={testidFor(p.path)}
378
+ checked={v !== false}
379
+ onChange={(e) => setProp(p.path, e.target.checked)}
380
+ />
381
+ {p.label}
382
+ </label>
383
+ );
384
+ }
385
+ return (
386
+ <div
387
+ key={p.path}
388
+ style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '6px 8px' }}
389
+ >
390
+ <span style={{ width: 64, fontSize: 12, color: '#9aa0a6' }}>{p.label}</span>
391
+ {p.type === 'color' ? (
392
+ <input
393
+ type="color"
394
+ data-testid={p.path === 'material.color' ? 'ingest-color' : testidFor(p.path)}
395
+ value={(v as string) ?? '#ffffff'}
396
+ onChange={(e) => setProp(p.path, e.target.value)}
397
+ />
398
+ ) : p.type === 'number' ? (
399
+ <input
400
+ type="number"
401
+ data-testid={testidFor(p.path)}
402
+ disabled={p.readonly}
403
+ value={typeof v === 'number' ? Math.round(v * 1000) / 1000 : 0}
404
+ onChange={(e) => setProp(p.path, Number.parseFloat(e.target.value))}
405
+ style={NUM_STYLE}
406
+ />
407
+ ) : (
408
+ <input
409
+ type="text"
410
+ data-testid={testidFor(p.path)}
411
+ readOnly={p.readonly}
412
+ value={String(v ?? '')}
413
+ onChange={(e) => setProp(p.path, e.target.value)}
414
+ style={{ ...NUM_STYLE, width: 120 }}
415
+ />
416
+ )}
417
+ </div>
418
+ );
419
+ };
420
+
421
+ // Split into the flat (ungrouped, today's-behavior) list and named groups —
422
+ // grouped fields render together under a titled sub-section instead of the
423
+ // flat grid (§1.E: "the generic inspector panel renders grouped sections").
424
+ // Order preserved: ungrouped fields keep their original position/order
425
+ // (byte-stable when nothing is grouped); groups render in first-seen order
426
+ // AFTER the ungrouped fields. The split itself is a pure, unit-tested
427
+ // helper (`inspector-property-grouping.ts`) — this component only renders it.
428
+ const { ungrouped: ungroupedProps, groups } = groupProperties(props);
429
+
430
+ return (
431
+ <Panel
432
+ name={panelNameOverride ?? 'Inspector (ingested game)'}
433
+ data-testid="inspector-panel"
434
+ onPointerDown={() => setActiveScope('inspector')}
435
+ >
436
+ <div style={{ pointerEvents: 'auto' }}>
437
+ {!suppressed.has('header') && (
438
+ <div style={{ padding: 8, borderBottom: '1px solid #333' }}>
439
+ <input
440
+ data-testid="ingest-name"
441
+ value={node?.label ?? ''}
442
+ onChange={(e) => setProp('name', e.target.value)}
443
+ style={{ ...NUM_STYLE, width: '100%', fontWeight: 600 }}
444
+ />
445
+ <div style={{ marginTop: 6, fontSize: 11, color: '#777' }}>
446
+ {node?.kind} · runtime-only (ingested)
447
+ </div>
448
+ </div>
449
+ )}
450
+
451
+ {!suppressed.has('transform') && (
452
+ <>
453
+ <AxisRow
454
+ label="Position"
455
+ testidPrefix="ingest-position"
456
+ values={t.position}
457
+ onCommit={(a, v) => commitTransform(a, 'position', v)}
458
+ />
459
+ <AxisRow
460
+ label="Scale"
461
+ testidPrefix="ingest-scale"
462
+ values={t.scale}
463
+ onCommit={(a, v) => commitTransform(a, 'scale', v)}
464
+ />
465
+ </>
466
+ )}
467
+
468
+ {/* Generic InspectorProvider-driven property grid (per descriptor type) —
469
+ ungrouped fields first (byte-stable: identical to pre-`group` output
470
+ when no descriptor declares one), then each named group as a titled
471
+ sub-section (§1.E). */}
472
+ {!suppressed.has('properties') && (
473
+ <>
474
+ {ungroupedProps.map(renderProperty)}
475
+ {groups.map((group) => (
476
+ <div
477
+ key={group.name}
478
+ data-testid={`ingest-group-${group.groupId}`}
479
+ style={{ borderTop: '1px solid #333', marginTop: 4 }}
480
+ >
481
+ <div
482
+ style={{
483
+ fontSize: 11,
484
+ fontWeight: 600,
485
+ color: '#9aa0a6',
486
+ padding: '6px 8px 2px',
487
+ textTransform: 'uppercase',
488
+ letterSpacing: 0.4,
489
+ }}
490
+ >
491
+ {group.name}
492
+ </div>
493
+ {group.properties.map(renderProperty)}
494
+ </div>
495
+ ))}
496
+ </>
497
+ )}
498
+
499
+ {/* Components section (C1, `docs/unified-world-editor/26-unified-editor-
500
+ spec.md` §C1) — driven by `adapter.components` (a `ComponentsProvider`,
501
+ absent on every ingest/pixi/react/world-group adapter reachable from
502
+ here today, so this renders the N/A fallback in practice until C2
503
+ routes first-party nodes through this generic panel). When the
504
+ provider exists AND has something to show, render its `list()` rows
505
+ (each removable) plus an "+ Add component…" select over `available()`.
506
+ Otherwise fall back to the ORIGINAL honest-N/A copy, under the SAME
507
+ `ingest-components-na` testid, byte-identical to before this section
508
+ existed (feat-inspector-components-na / `14-ingest-feature-matrix`). */}
509
+ {!suppressed.has('components') &&
510
+ (() => {
511
+ const compList = adapter.components?.list(id) ?? [];
512
+ const compAvailable = adapter.components?.available(id) ?? [];
513
+ if (!adapter.components || (compList.length === 0 && compAvailable.length === 0)) {
514
+ return (
515
+ <div
516
+ data-testid="ingest-components-na"
517
+ style={{ padding: '8px', borderTop: '1px solid #333', marginTop: 4 }}
518
+ >
519
+ <div style={{ fontSize: 11, fontWeight: 600, color: '#9aa0a6', marginBottom: 4 }}>
520
+ Components
521
+ </div>
522
+ <div style={{ fontSize: 11, color: '#777', fontStyle: 'italic' }}>
523
+ No components — this object is owned by the unmodified game (no engine
524
+ GameComponents).
525
+ </div>
526
+ </div>
527
+ );
528
+ }
529
+ const componentsProvider = adapter.components;
530
+ return (
531
+ <div style={{ padding: '8px', borderTop: '1px solid #333', marginTop: 4 }}>
532
+ <div style={{ fontSize: 11, fontWeight: 600, color: '#9aa0a6', marginBottom: 4 }}>
533
+ Components
534
+ </div>
535
+ {compList.map((c) => (
536
+ <div
537
+ key={c.type}
538
+ data-testid="ingest-component-row"
539
+ style={{
540
+ display: 'flex',
541
+ alignItems: 'center',
542
+ justifyContent: 'space-between',
543
+ padding: '2px 0',
544
+ }}
545
+ >
546
+ <span style={{ fontSize: 11, color: '#c5c8ce' }}>{c.type}</span>
547
+ <button
548
+ type="button"
549
+ data-testid="ingest-component-remove"
550
+ title="Remove component"
551
+ onClick={() => {
552
+ componentsProvider.remove(id, c.type);
553
+ store.notifyIngestEdit();
554
+ }}
555
+ style={{
556
+ background: 'transparent',
557
+ border: 'none',
558
+ color: '#777',
559
+ cursor: 'pointer',
560
+ fontSize: 12,
561
+ lineHeight: 1,
562
+ padding: 0,
563
+ }}
564
+ >
565
+ ×
566
+ </button>
567
+ </div>
568
+ ))}
569
+ {compAvailable.length > 0 && (
570
+ <select
571
+ data-testid="ingest-component-add"
572
+ value=""
573
+ onChange={(e) => {
574
+ if (e.target.value) {
575
+ componentsProvider.add(id, e.target.value);
576
+ store.notifyIngestEdit();
577
+ }
578
+ }}
579
+ style={{ ...NUM_STYLE, width: '100%', marginTop: 4, cursor: 'pointer' }}
580
+ >
581
+ <option value="">+ Add component…</option>
582
+ {compAvailable.map((name) => (
583
+ <option key={name} value={name}>
584
+ {name}
585
+ </option>
586
+ ))}
587
+ </select>
588
+ )}
589
+ </div>
590
+ );
591
+ })()}
592
+
593
+ {/* D4 (B2) — story picker: for a world:<id> group row whose owning
594
+ child adapter itself advertises `stories` (a react world, live or
595
+ not — see `worldGroupChildAdapter`'s doc comment), OR (B3) a
596
+ `kind:'component'` catalog node (`catalogNodeOwnerAdapter`). Any
597
+ other selection (an ordinary entity, or a world kind that never
598
+ sets `stories`) omits this section entirely. */}
599
+ {!suppressed.has('stories') && showStoriesSection && (
600
+ <div style={{ padding: '8px', borderTop: '1px solid #333', marginTop: 4 }}>
601
+ <div style={{ fontSize: 11, fontWeight: 600, color: '#9aa0a6', marginBottom: 4 }}>
602
+ Stories
603
+ </div>
604
+ {storyRefs.length > 0 ? (
605
+ <select
606
+ data-testid="ingest-stories-select"
607
+ value={adapter.stories?.active(id) ?? ''}
608
+ onChange={(e) => {
609
+ const v = e.target.value;
610
+ adapter.stories?.apply(id, v === '' ? null : v);
611
+ }}
612
+ style={{ ...NUM_STYLE, width: '100%' }}
613
+ >
614
+ <option value="">(none)</option>
615
+ {storyRefs.map((s) => (
616
+ <option key={s.id} value={s.id}>
617
+ {s.label}
618
+ </option>
619
+ ))}
620
+ </select>
621
+ ) : (
622
+ <div
623
+ data-testid="ingest-stories-none"
624
+ style={{ fontSize: 11, color: '#777', fontStyle: 'italic' }}
625
+ >
626
+ No stories — this world defines no `src/ui/registry.ts` states/stories.
627
+ </div>
628
+ )}
629
+ {/* B3 — Isolate toggle: render ONLY this catalog component (alone,
630
+ against whichever story is currently selected in the dropdown
631
+ above) on the world's design-time layer; exiting restores the
632
+ entry (live preview). Session-only — mirrors the story-overlay
633
+ writes below, never persisted to the registry. */}
634
+ {isCatalogNode && (
635
+ <button
636
+ type="button"
637
+ data-testid="catalog-isolate"
638
+ onClick={() => {
639
+ toggleCatalogIsolation(adapter, id, isIsolated);
640
+ store.notifyIngestEdit();
641
+ }}
642
+ style={{
643
+ marginTop: 6,
644
+ width: '100%',
645
+ padding: '6px 8px',
646
+ fontSize: 12,
647
+ background: isIsolated ? '#3a5' : '#2a2a2a',
648
+ color: '#e6e6e6',
649
+ border: '1px solid #444',
650
+ borderRadius: 3,
651
+ cursor: 'pointer',
652
+ }}
653
+ >
654
+ {isIsolated ? 'Exit isolation' : 'Isolate'}
655
+ </button>
656
+ )}
657
+ </div>
658
+ )}
659
+
660
+ {/* C2 — matched registered sections (the first-party threejs adapter's
661
+ rich Transform/Mesh/…/Components/AddSectionMenu, the UI adapter's
662
+ Layout/Style/Props/Events, or any future spec-27 contribution) —
663
+ rendered AFTER the generic grid above, as siblings, in
664
+ registration order. */}
665
+ {matched.map(({ Section }, i) => (
666
+ <Section key={i} adapter={adapter} nodeId={id} />
667
+ ))}
668
+ </div>
669
+ </Panel>
670
+ );
671
+ }