@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,105 @@
1
+ import type { SceneCamera } from '@engine/scene/scene-types';
2
+ import { useEditorStore } from '../../EditorContext';
3
+ import { NumberInput } from '../primitives/NumberInput';
4
+ import { SectionHeader } from '../primitives/SectionHeader';
5
+ import { Tooltip } from '../primitives/Tooltip';
6
+ import type { SectionProps } from './multi-edit-utils';
7
+ import { commonNum, commonStr } from './multi-edit-utils';
8
+
9
+ const cameraTypes: SceneCamera['type'][] = ['perspective', 'orthographic'];
10
+
11
+ export function CameraSection({ entities, ids }: SectionProps) {
12
+ const store = useEditorStore();
13
+ if (!entities.every((e) => e.camera)) return null;
14
+
15
+ const camType = commonStr(entities, (e) => e.camera?.type);
16
+ const near = commonNum(entities, (e) => e.camera?.near ?? 0.1);
17
+ const far = commonNum(entities, (e) => e.camera?.far ?? 1000);
18
+ const width = commonNum(entities, (e) => e.camera?.width ?? 1920);
19
+ const height = commonNum(entities, (e) => e.camera?.height ?? 1080);
20
+
21
+ const update = (partial: Partial<SceneCamera>) => {
22
+ store.batchMutate(ids, (entity) => {
23
+ if (entity.camera) Object.assign(entity.camera, partial);
24
+ });
25
+ };
26
+
27
+ return (
28
+ <SectionHeader label="Camera">
29
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
30
+ <div>
31
+ <div style={{ fontSize: 10, color: '#8c8c8c', marginBottom: 2 }}>Type</div>
32
+ <select
33
+ value={camType ?? ''}
34
+ onChange={(e) => update({ type: e.target.value as SceneCamera['type'] })}
35
+ style={{
36
+ width: '100%',
37
+ background: '#1e1e1e',
38
+ color: camType === null ? '#666' : '#c5c8ce',
39
+ fontStyle: camType === null ? 'italic' : 'normal',
40
+ border: '1px solid #333',
41
+ borderRadius: 2,
42
+ padding: '3px 4px',
43
+ fontSize: 12,
44
+ }}
45
+ >
46
+ {camType === null && (
47
+ <option value="" disabled>
48
+ (mixed)
49
+ </option>
50
+ )}
51
+ {cameraTypes.map((t) => (
52
+ <option key={t} value={t}>
53
+ {t}
54
+ </option>
55
+ ))}
56
+ </select>
57
+ </div>
58
+ {camType === 'perspective' && (
59
+ <div>
60
+ <Tooltip text="Vertical field of view in degrees" position="right">
61
+ <div style={{ fontSize: 10, color: '#8c8c8c', marginBottom: 2 }}>FOV (deg)</div>
62
+ </Tooltip>
63
+ <NumberInput
64
+ value={commonNum(entities, (e) => e.camera?.fov ?? 60)}
65
+ onChange={(v) => update({ fov: v })}
66
+ step={1}
67
+ />
68
+ </div>
69
+ )}
70
+ {camType === 'orthographic' && (
71
+ <div>
72
+ <div style={{ fontSize: 10, color: '#8c8c8c', marginBottom: 2 }}>Ortho Size</div>
73
+ <NumberInput
74
+ value={commonNum(entities, (e) => e.camera?.orthoSize ?? 5)}
75
+ onChange={(v) => update({ orthoSize: v })}
76
+ step={0.5}
77
+ />
78
+ </div>
79
+ )}
80
+ <div>
81
+ <Tooltip text="Nearest visible distance" position="right">
82
+ <div style={{ fontSize: 10, color: '#8c8c8c', marginBottom: 2 }}>Near</div>
83
+ </Tooltip>
84
+ <NumberInput value={near} onChange={(v) => update({ near: v })} step={0.1} />
85
+ </div>
86
+ <div>
87
+ <Tooltip text="Farthest visible distance" position="right">
88
+ <div style={{ fontSize: 10, color: '#8c8c8c', marginBottom: 2 }}>Far</div>
89
+ </Tooltip>
90
+ <NumberInput value={far} onChange={(v) => update({ far: v })} step={10} />
91
+ </div>
92
+ <div style={{ display: 'flex', gap: 6 }}>
93
+ <div style={{ flex: 1 }}>
94
+ <div style={{ fontSize: 10, color: '#8c8c8c', marginBottom: 2 }}>Width</div>
95
+ <NumberInput value={width} onChange={(v) => update({ width: v })} step={1} />
96
+ </div>
97
+ <div style={{ flex: 1 }}>
98
+ <div style={{ fontSize: 10, color: '#8c8c8c', marginBottom: 2 }}>Height</div>
99
+ <NumberInput value={height} onChange={(v) => update({ height: v })} step={1} />
100
+ </div>
101
+ </div>
102
+ </div>
103
+ </SectionHeader>
104
+ );
105
+ }
@@ -0,0 +1,358 @@
1
+ import type { SceneEntity } from '@engine/scene/scene-types';
2
+ import { useEditorStore } from '../../EditorContext';
3
+ import { getLoadedComponents } from '../../script-loader';
4
+ import { ColorInput } from '../primitives/ColorInput';
5
+ import { JsonInput } from '../primitives/JsonInput';
6
+ import { NumberInput } from '../primitives/NumberInput';
7
+ import { SectionHeader } from '../primitives/SectionHeader';
8
+ import { Tooltip } from '../primitives/Tooltip';
9
+ import { Vec3Input } from '../primitives/Vec3Input';
10
+ import type { SectionProps } from './multi-edit-utils';
11
+ import { type FieldDescriptor, introspectSchema } from './zod-fields';
12
+
13
+ type ComponentMap = NonNullable<SceneEntity['components']>;
14
+ type ComponentData = Record<string, unknown>;
15
+
16
+ const inputStyle: React.CSSProperties = {
17
+ flex: 1,
18
+ background: '#1e1e1e',
19
+ color: '#c5c8ce',
20
+ border: '1px solid #333',
21
+ borderRadius: 2,
22
+ padding: '2px 4px',
23
+ fontSize: 12,
24
+ };
25
+
26
+ const labelStyle: React.CSSProperties = {
27
+ fontSize: 11,
28
+ color: '#8c8c8c',
29
+ width: 70,
30
+ flexShrink: 0,
31
+ };
32
+
33
+ export function ComponentsSection({ entities }: SectionProps) {
34
+ const store = useEditorStore();
35
+ if (entities.length !== 1) return null;
36
+ const entity = entities[0]!;
37
+ const id = entity.id!;
38
+ const components = (entity.components ?? {}) as Record<string, ComponentData>;
39
+ const componentNames = Object.keys(components);
40
+
41
+ // Loaded project component classes (with optional `static schema`).
42
+ const classes = getLoadedComponents();
43
+
44
+ // Component names that have a schema and could be added to this entity.
45
+ const addable = Object.keys(classes)
46
+ .filter((name) => classes[name]?.schema && !(name in components))
47
+ .sort();
48
+
49
+ if (componentNames.length === 0 && addable.length === 0) return null;
50
+
51
+ /** Write a single field value, preserving the rest of the component data. */
52
+ const setField = (compName: string, key: string, value: unknown) => {
53
+ const next: Record<string, ComponentData> = {
54
+ ...components,
55
+ [compName]: { ...components[compName], [key]: value },
56
+ };
57
+ store.updateEntity(id, { components: next as unknown as ComponentMap });
58
+ };
59
+
60
+ const addComponent = (compName: string) => {
61
+ const schema = classes[compName]?.schema;
62
+ const data: ComponentData = {};
63
+ if (schema) {
64
+ for (const f of introspectSchema(schema)) {
65
+ if (f.default !== undefined) data[f.name] = f.default;
66
+ }
67
+ }
68
+ const next: Record<string, ComponentData> = { ...components, [compName]: data };
69
+ store.updateEntity(id, { components: next as unknown as ComponentMap });
70
+ };
71
+
72
+ const removeComponent = (compName: string) => {
73
+ const next = { ...components };
74
+ delete next[compName];
75
+ store.updateEntity(id, { components: next as unknown as ComponentMap });
76
+ };
77
+
78
+ return (
79
+ <SectionHeader label="Components">
80
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
81
+ {componentNames.map((compName) => {
82
+ const data = components[compName] ?? {};
83
+ const schema = classes[compName]?.schema;
84
+ const fields = schema ? introspectSchema(schema) : null;
85
+
86
+ return (
87
+ <div key={compName} data-testid={`component-section-${compName}`}>
88
+ <div
89
+ style={{
90
+ display: 'flex',
91
+ alignItems: 'center',
92
+ justifyContent: 'space-between',
93
+ marginBottom: 4,
94
+ }}
95
+ >
96
+ <span style={{ fontSize: 11, fontWeight: 600, color: '#b0b0b0' }}>{compName}</span>
97
+ <button
98
+ type="button"
99
+ title="Remove component"
100
+ data-testid={`component-remove-${compName}`}
101
+ onClick={() => removeComponent(compName)}
102
+ style={{
103
+ background: 'transparent',
104
+ border: 'none',
105
+ color: '#777',
106
+ cursor: 'pointer',
107
+ fontSize: 12,
108
+ lineHeight: 1,
109
+ padding: 0,
110
+ }}
111
+ >
112
+ ×
113
+ </button>
114
+ </div>
115
+
116
+ {fields
117
+ ? renderSchemaFields(compName, fields, data, setField)
118
+ : renderRawFields(compName, data, setField)}
119
+ </div>
120
+ );
121
+ })}
122
+
123
+ {addable.length > 0 && (
124
+ <select
125
+ data-testid="component-add"
126
+ value=""
127
+ onChange={(e) => {
128
+ if (e.target.value) addComponent(e.target.value);
129
+ }}
130
+ style={{ ...inputStyle, cursor: 'pointer' }}
131
+ >
132
+ <option value="">+ Add component…</option>
133
+ {addable.map((name) => (
134
+ <option key={name} value={name}>
135
+ {name}
136
+ </option>
137
+ ))}
138
+ </select>
139
+ )}
140
+ </div>
141
+ </SectionHeader>
142
+ );
143
+ }
144
+
145
+ /** Schema-driven path: render a typed widget per descriptor. */
146
+ function renderSchemaFields(
147
+ compName: string,
148
+ fields: FieldDescriptor[],
149
+ data: ComponentData,
150
+ setField: (compName: string, key: string, value: unknown) => void,
151
+ ) {
152
+ const known = new Set(fields.map((f) => f.name));
153
+ // Any values present on the component but absent from the schema still get an
154
+ // editable raw row so authored data is never silently hidden.
155
+ const extras = Object.keys(data).filter((k) => !known.has(k));
156
+
157
+ return (
158
+ <>
159
+ {fields.map((f) => {
160
+ const value = f.name in data ? data[f.name] : f.default;
161
+ return (
162
+ <FieldRow key={f.name} name={f.name} description={f.description}>
163
+ {renderWidget(compName, f, value, setField)}
164
+ </FieldRow>
165
+ );
166
+ })}
167
+ {extras.map((key) => (
168
+ <FieldRow key={key} name={key}>
169
+ <RawInput compName={compName} fieldKey={key} value={data[key]} setField={setField} />
170
+ </FieldRow>
171
+ ))}
172
+ </>
173
+ );
174
+ }
175
+
176
+ /** Fallback path: the original typeof-based rendering, unchanged in spirit. */
177
+ function renderRawFields(
178
+ compName: string,
179
+ data: ComponentData,
180
+ setField: (compName: string, key: string, value: unknown) => void,
181
+ ) {
182
+ return (
183
+ <>
184
+ {Object.entries(data).map(([key, val]) => (
185
+ <FieldRow key={key} name={key}>
186
+ <RawInput compName={compName} fieldKey={key} value={val} setField={setField} />
187
+ </FieldRow>
188
+ ))}
189
+ </>
190
+ );
191
+ }
192
+
193
+ function FieldRow({
194
+ name,
195
+ description,
196
+ children,
197
+ }: {
198
+ name: string;
199
+ description?: string | undefined;
200
+ children: React.ReactNode;
201
+ }) {
202
+ const label = <span style={labelStyle}>{name}</span>;
203
+ return (
204
+ <div style={{ display: 'flex', alignItems: 'center', gap: 4, marginBottom: 3 }}>
205
+ {description ? (
206
+ <Tooltip text={description} position="right">
207
+ {label}
208
+ </Tooltip>
209
+ ) : (
210
+ label
211
+ )}
212
+ <div style={{ flex: 1, minWidth: 0 }}>{children}</div>
213
+ </div>
214
+ );
215
+ }
216
+
217
+ /**
218
+ * Stable e2e/test hook for a component field widget — `component-field-<Comp>.<field>`,
219
+ * mirroring the Data panel's `data-field-${fieldPath.join('.')}` grammar
220
+ * (DataPanel.tsx) so the two inspectors are locatable the same way.
221
+ */
222
+ function fieldTestId(compName: string, fieldName: string): string {
223
+ return `component-field-${compName}.${fieldName}`;
224
+ }
225
+
226
+ function renderWidget(
227
+ compName: string,
228
+ f: FieldDescriptor,
229
+ value: unknown,
230
+ setField: (compName: string, key: string, value: unknown) => void,
231
+ ) {
232
+ const testId = fieldTestId(compName, f.name);
233
+ switch (f.kind) {
234
+ case 'number':
235
+ return (
236
+ <NumberInput
237
+ value={typeof value === 'number' ? value : Number(value) || 0}
238
+ onChange={(v) => setField(compName, f.name, v)}
239
+ testId={testId}
240
+ />
241
+ );
242
+ case 'boolean':
243
+ return (
244
+ <input
245
+ type="checkbox"
246
+ data-testid={testId}
247
+ checked={Boolean(value)}
248
+ onChange={(e) => setField(compName, f.name, e.target.checked)}
249
+ />
250
+ );
251
+ case 'color':
252
+ return (
253
+ <div data-testid={testId}>
254
+ <ColorInput
255
+ value={typeof value === 'string' ? value : '#ffffff'}
256
+ onChange={(v) => setField(compName, f.name, v)}
257
+ />
258
+ </div>
259
+ );
260
+ case 'vec3': {
261
+ const arr = Array.isArray(value) ? value : [0, 0, 0];
262
+ return (
263
+ <Vec3Input
264
+ value={[Number(arr[0]) || 0, Number(arr[1]) || 0, Number(arr[2]) || 0]}
265
+ onChange={(v) => setField(compName, f.name, v)}
266
+ testIdPrefix={testId}
267
+ />
268
+ );
269
+ }
270
+ case 'enum':
271
+ case 'select':
272
+ return (
273
+ <select
274
+ data-testid={testId}
275
+ value={value == null ? '' : String(value)}
276
+ onChange={(e) => setField(compName, f.name, e.target.value)}
277
+ style={{ ...inputStyle, cursor: 'pointer' }}
278
+ >
279
+ {(f.options ?? []).map((opt) => (
280
+ <option key={opt} value={opt}>
281
+ {opt}
282
+ </option>
283
+ ))}
284
+ </select>
285
+ );
286
+ default:
287
+ return (
288
+ <RawInput
289
+ compName={compName}
290
+ fieldKey={f.name}
291
+ value={value}
292
+ setField={setField}
293
+ testId={testId}
294
+ />
295
+ );
296
+ }
297
+ }
298
+
299
+ /** Type-preserving text/checkbox input matching the legacy behavior. */
300
+ function RawInput({
301
+ compName,
302
+ fieldKey,
303
+ value,
304
+ setField,
305
+ testId,
306
+ }: {
307
+ compName: string;
308
+ fieldKey: string;
309
+ value: unknown;
310
+ setField: (compName: string, key: string, value: unknown) => void;
311
+ testId?: string;
312
+ }) {
313
+ const id = testId ?? fieldTestId(compName, fieldKey);
314
+ if (typeof value === 'boolean') {
315
+ return (
316
+ <input
317
+ type="checkbox"
318
+ data-testid={id}
319
+ checked={value}
320
+ onChange={(e) => setField(compName, fieldKey, e.target.checked)}
321
+ />
322
+ );
323
+ }
324
+ if (typeof value === 'object' && value !== null) {
325
+ // Object/array-valued field: parse-refuse JSON commit (never widen to a
326
+ // string — the W2 invariant, see primitives/JsonInput.tsx). Commits on
327
+ // blur/Enter, unlike the keystroke-live branches below.
328
+ // `JsonInput` hardcodes its own `data-testid="json-input"` internally
329
+ // (kept untouched) — wrap it in the same DataPanel idiom (DataPanel.tsx
330
+ // ColorInput wrapper) so this field is still locatable by the house grammar.
331
+ return (
332
+ <div data-testid={id}>
333
+ <JsonInput
334
+ value={value}
335
+ onCommit={(v) => setField(compName, fieldKey, v)}
336
+ style={inputStyle}
337
+ />
338
+ </div>
339
+ );
340
+ }
341
+ const display = value === null || value === undefined ? '' : String(value);
342
+ return (
343
+ <input
344
+ data-testid={id}
345
+ value={display}
346
+ onChange={(e) => {
347
+ const raw = e.target.value;
348
+ if (typeof value === 'number') {
349
+ const n = Number.parseFloat(raw);
350
+ setField(compName, fieldKey, Number.isFinite(n) ? n : 0);
351
+ } else {
352
+ setField(compName, fieldKey, raw);
353
+ }
354
+ }}
355
+ style={inputStyle}
356
+ />
357
+ );
358
+ }