@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,281 @@
1
+ /**
2
+ * JSON Schema → `FieldDescriptor` conversion for DATA ASSETS (W2,
3
+ * docs/DATA-TOOLS-DESIGN.md §2).
4
+ *
5
+ * ARCHITECTURE RULE (spec §0/§2.1): the editor consumes the EMITTED JSON
6
+ * Schema (`*.schema.json`, the interchange artifact produced by
7
+ * `toDataJsonSchema`/`z.toJSONSchema`) — it never imports or executes the
8
+ * project's `*.schema.ts`. That keeps the Data panel a pure consumer of the
9
+ * contract format ("the contract is the format, not the library") and means
10
+ * anything that can emit JSON Schema gets an editor for free.
11
+ *
12
+ * This is the JSON Schema sibling of `zod-fields.ts`'s `introspectSchema`
13
+ * (which walks live Zod internals for component schemas). Both funnel into the
14
+ * same `FieldDescriptor` IR so the Data panel renders with the exact widgets
15
+ * the component inspector uses. Like `zod-fields.ts`, this module is pure (no
16
+ * React, no DOM) so it can be unit-tested in Node.
17
+ */
18
+
19
+ import type { FieldDescriptor, FieldKind } from './zod-fields';
20
+
21
+ /**
22
+ * A `FieldDescriptor` extended with the numeric bounds JSON Schema carries
23
+ * (`minimum`/`maximum`). The Zod introspection path never surfaced these, but
24
+ * emitted data-asset schemas do (e.g. `z.number().min(0).max(10)` emits
25
+ * `"minimum": 0, "maximum": 10`) and the Data panel clamps number edits to
26
+ * them.
27
+ *
28
+ * Widened locally (W2 hardening, spec §2.1 field-depth policy) with three
29
+ * kinds `zod-fields.ts`'s component-inspector `FieldKind` has no use for, so
30
+ * they're added here rather than on the shared type:
31
+ * - `'group'` — an object-typed node whose `properties` the panel can recurse
32
+ * into as a nested sub-form (`children` holds the nested descriptors).
33
+ * Singleton data assets only; a depth cap degrades deep nesting to `'json'`.
34
+ * - `'json'` — anything the panel can't type (arrays, unions, a group past
35
+ * the depth cap): a raw-JSON cell that parses on commit and REFUSES an
36
+ * invalid edit rather than ever writing a string in place of the typed
37
+ * value (the invariant this hardening exists to hold — see `JsonCell` in
38
+ * `DataPanel.tsx`).
39
+ * - `'ref'` — a declared `dataRef(target)` field (`@engine/data/data-ref`):
40
+ * the emitted schema carries `"x-vgai-ref": target` (a Zod `.meta()` that
41
+ * survives `toDataJsonSchema`), so the panel renders a `stem#key` PICKER
42
+ * sourced from the target file's own keys instead of a free-text cell.
43
+ */
44
+ export interface DataFieldDescriptor extends Omit<FieldDescriptor, 'kind'> {
45
+ kind: FieldKind | 'group' | 'json' | 'ref';
46
+ /** Nested field descriptors, present only when `kind === 'group'`. */
47
+ children?: DataFieldDescriptor[];
48
+ min?: number;
49
+ max?: number;
50
+ /** Target asset stem for `kind === 'ref'` — the `dataRef` call's argument. */
51
+ refTarget?: string;
52
+ }
53
+
54
+ /**
55
+ * Defensive recursion cap on nested `group` descriptors (spec §2.1: "1-2
56
+ * levels" is the blessed shape; this is the hard backstop, not the taught
57
+ * limit). Past this depth a would-be group degrades to `'json'` — safe,
58
+ * never a corruption vector, because `JsonCell` never widens a typed value
59
+ * into a string on commit.
60
+ */
61
+ const MAX_GROUP_DEPTH = 4;
62
+
63
+ /**
64
+ * The two blessed data-asset shapes (spec §2.1), detected from the emitted
65
+ * schema's top level:
66
+ * - **singleton** — one config object; `properties` describes its fields.
67
+ * Renders as a form.
68
+ * - **table** — `z.record(rowSchema)`; the emitted schema carries the row
69
+ * schema as an object-valued `additionalProperties`. Renders as a grid,
70
+ * one row per key in the data file.
71
+ */
72
+ export type DataAssetShape =
73
+ | { shape: 'singleton'; fields: DataFieldDescriptor[] }
74
+ | { shape: 'table'; rowFields: DataFieldDescriptor[] };
75
+
76
+ /** Loosely-typed view of a JSON Schema node (we only read what we render). */
77
+ interface SchemaNode {
78
+ type?: string | string[];
79
+ properties?: Record<string, unknown>;
80
+ additionalProperties?: unknown;
81
+ enum?: unknown[];
82
+ default?: unknown;
83
+ description?: string;
84
+ minimum?: number;
85
+ maximum?: number;
86
+ anyOf?: unknown[];
87
+ /** `dataRef(target)`'s marker (`@engine/data/data-ref`) — present iff this is a declared ref field. */
88
+ 'x-vgai-ref'?: string;
89
+ }
90
+
91
+ function asNode(value: unknown): SchemaNode | null {
92
+ return value && typeof value === 'object' && !Array.isArray(value) ? (value as SchemaNode) : null;
93
+ }
94
+
95
+ /**
96
+ * Heuristic (same idea as `zod-fields.ts`'s `looksLikeColor`): a string field
97
+ * edits as a color swatch when its name says color, its default is a
98
+ * `#rrggbb`-style value, or its description mentions color.
99
+ */
100
+ function looksLikeColor(name: string, def: unknown, description: string | undefined): boolean {
101
+ if (/colou?r/i.test(name)) return true;
102
+ if (typeof def === 'string' && /^#/.test(def)) return true;
103
+ if (typeof description === 'string' && /\bcolou?r\b/i.test(description)) return true;
104
+ return false;
105
+ }
106
+
107
+ /** First declared `type` string, unwrapping the `["number","null"]` array form. */
108
+ function nodeType(node: SchemaNode): string | undefined {
109
+ if (typeof node.type === 'string') return node.type;
110
+ if (Array.isArray(node.type)) return node.type.find((t) => t !== 'null');
111
+ return undefined;
112
+ }
113
+
114
+ /**
115
+ * Optional/nullable Zod fields can emit as `anyOf` branches; adopt the first
116
+ * branch that declares a type. Defensive, like zod-fields' fallbacks —
117
+ * anything unrecognized degrades to a raw text field, never a crash.
118
+ */
119
+ function unwrapAnyOf(node: SchemaNode): SchemaNode {
120
+ if (nodeType(node) || node.enum || !Array.isArray(node.anyOf)) return node;
121
+ const branch = node.anyOf.map(asNode).find((b) => b && (nodeType(b) || b.enum));
122
+ if (!branch) return node;
123
+ // Branch supplies the type/enum; the outer node's default/description/
124
+ // bounds (where present) win, since Zod hoists those to the wrapper.
125
+ return {
126
+ ...branch,
127
+ ...node,
128
+ ...(branch.type ? { type: branch.type } : {}),
129
+ ...(branch.enum ? { enum: branch.enum } : {}),
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Map one (unwrapped) schema node to the widget kind + enum options.
135
+ *
136
+ * `depth` is the nesting depth of THIS node (0 = a direct field of the
137
+ * top-level singleton/row schema) — it gates the `'group'` case so recursion
138
+ * can't run away on a pathological schema; see `MAX_GROUP_DEPTH`.
139
+ */
140
+ function detectKind(
141
+ node: SchemaNode,
142
+ name: string,
143
+ depth: number,
144
+ ): { kind: DataFieldDescriptor['kind']; options?: string[]; refTarget?: string } {
145
+ // Ref first: a declared `dataRef(target)` field is a plain `{ type:
146
+ // "string", pattern: ..., "x-vgai-ref": target }` — it must win over both
147
+ // the enum and plain-string branches below, which would otherwise render
148
+ // it as ordinary free text.
149
+ if (typeof node['x-vgai-ref'] === 'string') {
150
+ return { kind: 'ref', refTarget: node['x-vgai-ref'] };
151
+ }
152
+ // Enum first: emitted enums are `{ type: "string", enum: [...] }`, so the
153
+ // enum check must win over the plain-string branch.
154
+ if (Array.isArray(node.enum) && node.enum.length > 0) {
155
+ return { kind: 'enum', options: node.enum.map(String) };
156
+ }
157
+ switch (nodeType(node)) {
158
+ case 'number':
159
+ case 'integer':
160
+ return { kind: 'number' };
161
+ case 'boolean':
162
+ return { kind: 'boolean' };
163
+ case 'string':
164
+ return looksLikeColor(name, node.default, node.description)
165
+ ? { kind: 'color' }
166
+ : { kind: 'string' };
167
+ default:
168
+ // `type: 'object'` (or absent — Zod can omit `type` on some wrapper
169
+ // shapes) with an object-valued `properties`: a nested group the panel
170
+ // can recurse into as a sub-form, UNLESS the depth cap is hit. Arrays,
171
+ // unions, and anything else with no `properties` to walk (including a
172
+ // capped-out group) fall through to `'json'` — a raw-JSON cell that
173
+ // parses on commit and refuses invalid input, never a widened string
174
+ // (W2 hardening: the prior fallback here was `'string'`, which is
175
+ // exactly the corruption this module exists to prevent — see
176
+ // `DataFieldDescriptor`'s doc comment).
177
+ if (asNode(node.properties) && depth < MAX_GROUP_DEPTH) {
178
+ return { kind: 'group' };
179
+ }
180
+ return { kind: 'json' };
181
+ }
182
+ }
183
+
184
+ /** Convert ONE JSON Schema property node into a field descriptor. */
185
+ function toDescriptor(name: string, raw: unknown, depth: number): DataFieldDescriptor {
186
+ const node = unwrapAnyOf(asNode(raw) ?? {});
187
+ const { kind, options, refTarget } = detectKind(node, name, depth);
188
+
189
+ const fd: DataFieldDescriptor = { name, kind };
190
+ if (options !== undefined) fd.options = options;
191
+ if (refTarget !== undefined) fd.refTarget = refTarget;
192
+ if (node.default !== undefined) fd.default = node.default;
193
+ if (node.description !== undefined) fd.description = node.description;
194
+ if (typeof node.minimum === 'number') fd.min = node.minimum;
195
+ if (typeof node.maximum === 'number') fd.max = node.maximum;
196
+ if (kind === 'group') fd.children = jsonSchemaToFieldDescriptors(node, depth + 1);
197
+ return fd;
198
+ }
199
+
200
+ /**
201
+ * Convert a JSON Schema OBJECT node's `properties` into field descriptors.
202
+ *
203
+ * The `"$schema"` property is skipped: `toDataJsonSchema` deliberately allows
204
+ * a `"$schema"` string on emitted object schemas so VS Code doesn't flag the
205
+ * declaration line in data files (spec §9.1) — it is plumbing, not a field.
206
+ *
207
+ * `depth` is the nesting depth of the fields THIS call produces (0 at the
208
+ * top-level call) — threaded through so a nested `group`'s own children are
209
+ * classified one level deeper; see `MAX_GROUP_DEPTH`.
210
+ */
211
+ export function jsonSchemaToFieldDescriptors(schema: unknown, depth = 0): DataFieldDescriptor[] {
212
+ const node = asNode(schema);
213
+ const props = node ? asNode(node.properties) : null;
214
+ if (!props) return [];
215
+
216
+ const fields: DataFieldDescriptor[] = [];
217
+ for (const [name, propSchema] of Object.entries(props)) {
218
+ if (name === '$schema') continue;
219
+ fields.push(toDescriptor(name, propSchema, depth));
220
+ }
221
+ return fields;
222
+ }
223
+
224
+ /**
225
+ * Classify an emitted data-asset schema as one of the two blessed shapes
226
+ * (spec §2.1) and return the descriptors the Data panel renders.
227
+ *
228
+ * Table detection MUST run first: a table's emitted schema also carries a
229
+ * top-level `properties: { "$schema": … }` (the §9.1 allowance), so
230
+ * "has properties" alone would misread every table as a singleton. The
231
+ * discriminator is an OBJECT-VALUED `additionalProperties` — the row schema
232
+ * `z.record(rowSchema)` emits (a bare boolean `additionalProperties` is just
233
+ * an open/closed-object flag, not a row schema).
234
+ *
235
+ * Returns `null` for anything that is neither (the panel shows the file as
236
+ * unsupported rather than guessing).
237
+ */
238
+ export function describeDataSchema(schema: unknown): DataAssetShape | null {
239
+ const node = asNode(schema);
240
+ if (!node) return null;
241
+
242
+ const rowSchema = asNode(node.additionalProperties);
243
+ if (rowSchema && asNode(rowSchema.properties)) {
244
+ return { shape: 'table', rowFields: jsonSchemaToFieldDescriptors(rowSchema) };
245
+ }
246
+
247
+ const fields = jsonSchemaToFieldDescriptors(node);
248
+ if (fields.length > 0) return { shape: 'singleton', fields };
249
+
250
+ return null;
251
+ }
252
+
253
+ /**
254
+ * A fresh value for one row-schema field (W6b, docs/DATA-TOOLS-DESIGN.md §7):
255
+ * the schema's own declared `default` when present (cloned — never the same
256
+ * reference, so two new rows never share a mutable nested object), else the
257
+ * kind's zero value. Used to build a brand-new table row (add-row) and to
258
+ * fill in a cell a CSV import left blank/missing (W6c).
259
+ */
260
+ export function defaultValueForDescriptor(f: DataFieldDescriptor): unknown {
261
+ if (f.default !== undefined) return structuredClone(f.default);
262
+ switch (f.kind) {
263
+ case 'number':
264
+ return f.min ?? 0;
265
+ case 'boolean':
266
+ return false;
267
+ case 'color':
268
+ return '#ffffff';
269
+ case 'enum':
270
+ return f.options?.[0] ?? '';
271
+ case 'string':
272
+ case 'ref':
273
+ return '';
274
+ case 'group':
275
+ return Object.fromEntries(
276
+ (f.children ?? []).map((c) => [c.name, defaultValueForDescriptor(c)]),
277
+ );
278
+ default:
279
+ return null; // 'json' — JsonCell renders/edits it safely
280
+ }
281
+ }
@@ -0,0 +1,49 @@
1
+ import type { SceneEntity } from '@engine/scene/scene-types';
2
+
3
+ /** Shared props for all inspector sections. */
4
+ export interface SectionProps {
5
+ entities: SceneEntity[];
6
+ ids: string[];
7
+ }
8
+
9
+ /** Return the common numeric value across entities, or null if mixed. */
10
+ export function commonNum(
11
+ entities: SceneEntity[],
12
+ getter: (e: SceneEntity) => number,
13
+ ): number | null {
14
+ if (entities.length === 0) return null;
15
+ const first = getter(entities[0]!);
16
+ return entities.every((e) => Math.abs(getter(e) - first) < 0.0005) ? first : null;
17
+ }
18
+
19
+ /** Return the common string value across entities, or null if mixed. */
20
+ export function commonStr(
21
+ entities: SceneEntity[],
22
+ getter: (e: SceneEntity) => string | undefined,
23
+ ): string | null {
24
+ if (entities.length === 0) return null;
25
+ const first = getter(entities[0]!);
26
+ return entities.every((e) => getter(e) === first) ? (first ?? null) : null;
27
+ }
28
+
29
+ /** Return the common boolean value across entities, or null if mixed. */
30
+ export function commonBool(
31
+ entities: SceneEntity[],
32
+ getter: (e: SceneEntity) => boolean,
33
+ ): boolean | null {
34
+ if (entities.length === 0) return null;
35
+ const first = getter(entities[0]!);
36
+ return entities.every((e) => getter(e) === first) ? first : null;
37
+ }
38
+
39
+ /** Return the common Vec3 across entities, with per-axis null for mixed. */
40
+ export function commonVec3(
41
+ entities: SceneEntity[],
42
+ getter: (e: SceneEntity) => [number, number, number],
43
+ ): [number | null, number | null, number | null] {
44
+ return [
45
+ commonNum(entities, (e) => getter(e)[0]),
46
+ commonNum(entities, (e) => getter(e)[1]),
47
+ commonNum(entities, (e) => getter(e)[2]),
48
+ ];
49
+ }
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Schema introspection for the component inspector.
3
+ *
4
+ * `introspectSchema` walks a Zod object schema (zod 4, the version the engine
5
+ * uses via `z.toJSONSchema`) and produces a flat list of `FieldDescriptor`s that
6
+ * the inspector renders as typed widgets. It reads the zod internal `_def`
7
+ * directly because zod offers no public reflection API. The walk is defensive:
8
+ * it also understands the zod 3 `_def.typeName` shape and falls back to a plain
9
+ * string/text field for anything it does not recognise.
10
+ *
11
+ * This module is pure (no React, no DOM) so it can be unit-tested in Node.
12
+ */
13
+
14
+ import type { z } from 'zod';
15
+
16
+ /** Widget kind the inspector should render for a field. */
17
+ export type FieldKind = 'number' | 'boolean' | 'string' | 'color' | 'vec3' | 'enum' | 'select';
18
+
19
+ /** One editable field discovered on a component schema. */
20
+ export interface FieldDescriptor {
21
+ /** Property name on the component data object. */
22
+ name: string;
23
+ /** Which widget to render. */
24
+ kind: FieldKind;
25
+ /** Default value from `.default(...)`, if any. */
26
+ default?: unknown;
27
+ /** Human description from `.describe(...)`, if any. */
28
+ description?: string;
29
+ /** Allowed string values for `enum`/`select` kinds. */
30
+ options?: string[];
31
+ }
32
+
33
+ /** Loosely-typed view of a zod node's internal definition. */
34
+ interface ZodDef {
35
+ type?: string; // zod 4 discriminator: 'number' | 'default' | 'enum' | ...
36
+ typeName?: string; // zod 3 discriminator: 'ZodNumber' | 'ZodDefault' | ...
37
+ innerType?: AnyZod; // wrapper (default/optional/nullable/readonly) payload
38
+ defaultValue?: unknown | (() => unknown); // value (zod 4) or thunk (zod 3)
39
+ description?: string;
40
+ entries?: Record<string, string>; // zod 4 enum
41
+ values?: unknown[]; // zod 3 enum
42
+ items?: AnyZod[]; // tuple element schemas
43
+ element?: AnyZod; // array element schema
44
+ }
45
+
46
+ interface AnyZod {
47
+ _def?: ZodDef;
48
+ description?: string;
49
+ options?: unknown[];
50
+ }
51
+
52
+ /** Wrapper node types that hold the real schema in `_def.innerType`. */
53
+ const WRAPPERS = new Set(['default', 'optional', 'nullable', 'readonly', 'nonoptional']);
54
+
55
+ /** Normalised node type string (zod 4 `type` or de-prefixed zod 3 `typeName`). */
56
+ function defType(node: AnyZod | undefined): string {
57
+ const d = node?._def;
58
+ if (!d) return 'unknown';
59
+ if (typeof d.type === 'string') return d.type;
60
+ if (typeof d.typeName === 'string') return d.typeName.replace(/^Zod/, '').toLowerCase();
61
+ return 'unknown';
62
+ }
63
+
64
+ interface Unwrapped {
65
+ inner: AnyZod;
66
+ default: unknown;
67
+ description: string | undefined;
68
+ }
69
+
70
+ /** Peel wrapper nodes, collecting the default value and description along the way. */
71
+ function unwrap(field: AnyZod): Unwrapped {
72
+ let cur: AnyZod = field;
73
+ let def: unknown;
74
+ let description: string | undefined = field.description ?? field._def?.description;
75
+
76
+ while (cur && WRAPPERS.has(defType(cur))) {
77
+ if (defType(cur) === 'default') {
78
+ const dv = cur._def?.defaultValue;
79
+ def = typeof dv === 'function' ? (dv as () => unknown)() : dv;
80
+ }
81
+ if (!description) description = cur._def?.description;
82
+ const inner = cur._def?.innerType;
83
+ if (!inner) break;
84
+ cur = inner;
85
+ }
86
+ if (!description) description = cur?.description ?? cur?._def?.description;
87
+ return { inner: cur, default: def, description };
88
+ }
89
+
90
+ /** Extract the allowed string values of a zod enum (zod 3 or 4 shape). */
91
+ function enumOptions(inner: AnyZod): string[] {
92
+ const d = inner._def;
93
+ if (d?.entries) return Object.values(d.entries).map(String);
94
+ if (Array.isArray(d?.values)) return d.values.map(String);
95
+ if (Array.isArray(inner.options)) return inner.options.map(String);
96
+ return [];
97
+ }
98
+
99
+ /** Heuristic: should this string field be edited as a color swatch? */
100
+ function looksLikeColor(name: string, def: unknown, description: string | undefined): boolean {
101
+ if (/colou?r/i.test(name)) return true;
102
+ if (typeof def === 'string' && /^#/.test(def)) return true;
103
+ if (typeof description === 'string' && /\bcolou?r\b/i.test(description)) return true;
104
+ return false;
105
+ }
106
+
107
+ interface KindResult {
108
+ kind: FieldKind;
109
+ options?: string[];
110
+ }
111
+
112
+ function detectKind(
113
+ inner: AnyZod,
114
+ name: string,
115
+ def: unknown,
116
+ description: string | undefined,
117
+ ): KindResult {
118
+ switch (defType(inner)) {
119
+ case 'number':
120
+ case 'int':
121
+ return { kind: 'number' };
122
+ case 'boolean':
123
+ return { kind: 'boolean' };
124
+ case 'enum':
125
+ case 'nativeenum': {
126
+ const options = enumOptions(inner);
127
+ return options.length ? { kind: 'enum', options } : { kind: 'string' };
128
+ }
129
+ case 'string':
130
+ return looksLikeColor(name, def, description) ? { kind: 'color' } : { kind: 'string' };
131
+ case 'tuple': {
132
+ const items = inner._def?.items ?? [];
133
+ if (items.length === 3 && items.every((it) => defType(it) === 'number')) {
134
+ return { kind: 'vec3' };
135
+ }
136
+ return { kind: 'string' }; // unsupported tuple shape -> text/JSON fallback
137
+ }
138
+ case 'array': {
139
+ const el = inner._def?.element;
140
+ // Arrays have no fixed length in the schema; only treat a 3-number array
141
+ // with a length-3 default as a vec3, otherwise fall back to text.
142
+ if (el && defType(el) === 'number' && Array.isArray(def) && def.length === 3) {
143
+ return { kind: 'vec3' };
144
+ }
145
+ return { kind: 'string' };
146
+ }
147
+ default:
148
+ // Unknown / unsupported zod types: edit as raw text (JSON for non-strings).
149
+ return { kind: 'string' };
150
+ }
151
+ }
152
+
153
+ /** Read the object shape from a zod object schema (zod 3 or 4). */
154
+ function getShape(schema: z.ZodObject<z.ZodRawShape>): Record<string, AnyZod> | undefined {
155
+ const anySchema = schema as unknown as {
156
+ shape?: Record<string, AnyZod> | (() => Record<string, AnyZod>);
157
+ _def?: { shape?: Record<string, AnyZod> | (() => Record<string, AnyZod>) };
158
+ };
159
+ const raw = anySchema.shape ?? anySchema._def?.shape;
160
+ const resolved = typeof raw === 'function' ? raw() : raw;
161
+ return resolved && typeof resolved === 'object' ? resolved : undefined;
162
+ }
163
+
164
+ /**
165
+ * Introspect a Zod object schema into a flat list of editable field descriptors.
166
+ * Returns `[]` for anything that is not a usable object schema.
167
+ */
168
+ export function introspectSchema(schema: z.ZodObject<z.ZodRawShape>): FieldDescriptor[] {
169
+ const shape = getShape(schema);
170
+ if (!shape) return [];
171
+
172
+ const fields: FieldDescriptor[] = [];
173
+ for (const [name, field] of Object.entries(shape)) {
174
+ if (!field || typeof field !== 'object') continue;
175
+ const { inner, default: def, description } = unwrap(field);
176
+ const { kind, options } = detectKind(inner, name, def, description);
177
+ const fd: FieldDescriptor = { name, kind };
178
+ if (def !== undefined) fd.default = def;
179
+ if (description) fd.description = description;
180
+ if (options?.length) fd.options = options;
181
+ fields.push(fd);
182
+ }
183
+ return fields;
184
+ }
@@ -0,0 +1,109 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { useInteractiveEditScope } from '../../interactive-edit-scope';
3
+
4
+ interface ColorInputProps {
5
+ value: string | null;
6
+ onChange: (value: string) => void;
7
+ }
8
+
9
+ export function ColorInput({ value, onChange }: ColorInputProps) {
10
+ // Local draft state for the text input — only commits on blur or Enter.
11
+ const [draft, setDraft] = useState(value ?? '');
12
+ const editScope = useInteractiveEditScope();
13
+
14
+ // Sync draft when the external value changes (e.g. undo, selection change).
15
+ useEffect(() => {
16
+ setDraft(value ?? '');
17
+ }, [value]);
18
+
19
+ const commit = () => {
20
+ const trimmed = draft.trim();
21
+ if (trimmed && trimmed !== value) {
22
+ onChange(trimmed);
23
+ }
24
+ };
25
+
26
+ if (value === null) {
27
+ return (
28
+ <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
29
+ <div
30
+ style={{
31
+ width: 28,
32
+ height: 22,
33
+ border: '1px solid #333',
34
+ borderRadius: 2,
35
+ background:
36
+ 'linear-gradient(45deg, #666 25%, transparent 25%, transparent 75%, #666 75%), linear-gradient(45deg, #666 25%, transparent 25%, transparent 75%, #666 75%)',
37
+ backgroundSize: '8px 8px',
38
+ backgroundPosition: '0 0, 4px 4px',
39
+ }}
40
+ />
41
+ <input
42
+ placeholder="mixed"
43
+ onChange={(e) => {
44
+ if (e.target.value) onChange(e.target.value);
45
+ }}
46
+ style={{
47
+ flex: 1,
48
+ background: '#1e1e1e',
49
+ color: '#666',
50
+ fontStyle: 'italic',
51
+ border: '1px solid #333',
52
+ borderRadius: 2,
53
+ padding: '2px 4px',
54
+ fontSize: 12,
55
+ }}
56
+ />
57
+ </div>
58
+ );
59
+ }
60
+
61
+ return (
62
+ <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
63
+ <input
64
+ type="color"
65
+ value={value}
66
+ // The native picker fires onChange continuously while dragging. Coalesce
67
+ // the whole drag into one undo step (beginInteractiveEdit is idempotent);
68
+ // the gesture closes on blur when the picker is dismissed.
69
+ onChange={(e) => {
70
+ editScope?.begin();
71
+ onChange(e.target.value);
72
+ }}
73
+ onBlur={() => editScope?.end()}
74
+ style={{
75
+ width: 28,
76
+ height: 22,
77
+ border: '1px solid #333',
78
+ borderRadius: 2,
79
+ padding: 0,
80
+ cursor: 'pointer',
81
+ background: 'transparent',
82
+ }}
83
+ />
84
+ <input
85
+ value={draft}
86
+ onChange={(e) => setDraft(e.target.value)}
87
+ onBlur={commit}
88
+ onKeyDown={(e) => {
89
+ if (e.key === 'Enter') {
90
+ commit();
91
+ (e.target as HTMLInputElement).blur();
92
+ } else if (e.key === 'Escape') {
93
+ setDraft(value);
94
+ (e.target as HTMLInputElement).blur();
95
+ }
96
+ }}
97
+ style={{
98
+ flex: 1,
99
+ background: '#1e1e1e',
100
+ color: '#c5c8ce',
101
+ border: '1px solid #333',
102
+ borderRadius: 2,
103
+ padding: '2px 4px',
104
+ fontSize: 12,
105
+ }}
106
+ />
107
+ </div>
108
+ );
109
+ }