@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,420 @@
1
+ /**
2
+ * ArenaRoom — multiplayer tag game with game phases and NPC bots.
3
+ *
4
+ * Server-authoritative: the server owns tag state, validates proximity,
5
+ * runs the phase state machine, and makes NPC behavior decisions.
6
+ * Clients send position input; server broadcasts everything via Schema sync.
7
+ *
8
+ * Phase flow:
9
+ * lobby → countdown (3s) → playing (60s) → scoreboard (5s) → countdown …
10
+ *
11
+ * Tag rules:
12
+ * - One player is "it" (taggerId). Server checks proximity each tick.
13
+ * - Tagged player becomes "it". 1.5s cooldown prevents instant re-tag.
14
+ * - Non-taggers accumulate score each tick.
15
+ *
16
+ * NPCs:
17
+ * - Configurable bot count (0–6), spawned on create, stored in the same players MapSchema.
18
+ * - Server picks behavior (idle/patrol/chase/flee) + target position.
19
+ * - Client handles smooth navmesh pathfinding.
20
+ */
21
+
22
+ import { Room, type Client } from 'colyseus';
23
+ import { Schema, MapSchema, type } from '@colyseus/schema';
24
+
25
+ // --- Constants ---
26
+
27
+ const COLORS = [0xff6644, 0x44ff99, 0x4499ff, 0xffee44, 0xff44bb, 0x44ffee, 0xaa44ff, 0xff4488];
28
+ const NAMES = ['Coral', 'Mint', 'Sky', 'Gold', 'Rose', 'Teal', 'Violet', 'Crimson'];
29
+ const NPC_COLOR = 0x888888;
30
+ const MAX_NPCS = 6;
31
+ const SPAWN_RADIUS = 5;
32
+
33
+ const MIN_PLAYERS = 2; // humans + NPCs
34
+ const COUNTDOWN_SECS = 3;
35
+ const ROUND_SECS = 60;
36
+ const SCOREBOARD_SECS = 5;
37
+
38
+ const TAG_RADIUS_SQ = 2.25; // 1.5 meters squared
39
+ const TAG_COOLDOWN_SECS = 1.5;
40
+ const FLEE_RADIUS_SQ = 100; // 10 meters squared
41
+
42
+ const SIM_INTERVAL_MS = 50; // 20 Hz
43
+
44
+ // NPC patrol waypoint routes
45
+ const PATROL_ROUTES = [
46
+ [
47
+ { x: -12, z: -12 },
48
+ { x: 12, z: -12 },
49
+ { x: 12, z: 12 },
50
+ { x: -12, z: 12 },
51
+ ],
52
+ [
53
+ { x: -8, z: 0 },
54
+ { x: 0, z: -8 },
55
+ { x: 8, z: 0 },
56
+ { x: 0, z: 8 },
57
+ ],
58
+ ];
59
+
60
+ // --- Schema definitions ---
61
+
62
+ export class ArenaPlayerSchema extends Schema {
63
+ @type('string') sessionId = '';
64
+ @type('string') name = '';
65
+ @type('uint32') color = 0;
66
+ @type('float32') x = 0;
67
+ @type('float32') y = 0;
68
+ @type('float32') z = 0;
69
+ @type('float32') rotY = 0;
70
+ @type('float32') speed = 0;
71
+ @type('uint8') isGrounded = 1;
72
+ @type('boolean') isNPC = false;
73
+ @type('uint16') score = 0;
74
+ @type('string') npcBehavior = 'idle';
75
+ }
76
+
77
+ export class ArenaState extends Schema {
78
+ @type({ map: ArenaPlayerSchema }) players = new MapSchema<ArenaPlayerSchema>();
79
+ @type('string') taggerId = '';
80
+ @type('float32') tagCooldown = 0;
81
+ @type('string') phase = 'lobby';
82
+ @type('float32') phaseTimer = 0;
83
+ @type('uint8') roundNumber = 0;
84
+ @type('string') hostId = '';
85
+ }
86
+
87
+ // --- NPC brain (server-side only, not synced) ---
88
+
89
+ interface NPCBrain {
90
+ behavior: 'idle' | 'patrol' | 'chase' | 'flee';
91
+ waypointIndex: number;
92
+ routeIndex: number;
93
+ targetX: number;
94
+ targetZ: number;
95
+ }
96
+
97
+ // --- Helpers ---
98
+
99
+ function randomSpawnPosition(): { x: number; y: number; z: number } {
100
+ const angle = Math.random() * Math.PI * 2;
101
+ return {
102
+ x: Math.cos(angle) * SPAWN_RADIUS,
103
+ y: 0,
104
+ z: Math.sin(angle) * SPAWN_RADIUS,
105
+ };
106
+ }
107
+
108
+ function randomTagger(state: ArenaState): string {
109
+ const ids = [...state.players.keys()];
110
+ if (ids.length === 0) return '';
111
+ return ids[Math.floor(Math.random() * ids.length)]!;
112
+ }
113
+
114
+ // --- Room ---
115
+
116
+ export class ArenaRoom extends Room {
117
+ state = new ArenaState();
118
+ patchRate = 50;
119
+
120
+ private colorIndex = 0;
121
+ private npcBrains = new Map<string, NPCBrain>();
122
+
123
+ onCreate(options?: { botCount?: number }) {
124
+ // Spawn NPC bots (count from room creator, default 2, max 6)
125
+ const npcCount = Math.max(0, Math.min(MAX_NPCS, options?.botCount ?? 2));
126
+ this.spawnNPCs(npcCount);
127
+
128
+ // Handle start_game from host
129
+ this.onMessage('start_game', (client) => {
130
+ if (client.sessionId !== this.state.hostId) return;
131
+ if (this.state.phase !== 'lobby') return;
132
+ this.state.phase = 'countdown';
133
+ this.state.phaseTimer = COUNTDOWN_SECS;
134
+ });
135
+
136
+ // Handle client input messages
137
+ this.onMessage('input', (client, data: {
138
+ x: number; y: number; z: number;
139
+ rotY: number; speed: number; isGrounded: number;
140
+ }) => {
141
+ const player = this.state.players.get(client.sessionId);
142
+ if (player) {
143
+ player.x = data.x;
144
+ player.y = data.y;
145
+ player.z = data.z;
146
+ player.rotY = data.rotY;
147
+ player.speed = data.speed;
148
+ player.isGrounded = data.isGrounded;
149
+ }
150
+ });
151
+
152
+ // Server simulation loop (20 Hz)
153
+ const dt = SIM_INTERVAL_MS / 1000;
154
+ this.setSimulationInterval(() => {
155
+ this.updatePhase(dt);
156
+ this.updateTag(dt);
157
+ this.updateNPCs();
158
+ this.moveNPCsTowardTargets(dt);
159
+ }, SIM_INTERVAL_MS);
160
+ }
161
+
162
+ onJoin(client: Client, options?: { color?: number; name?: string }) {
163
+ const idx = this.colorIndex % COLORS.length;
164
+ const color = (options?.color != null) ? options.color : COLORS[idx]!;
165
+ const name = options?.name ?? NAMES[idx]!;
166
+ this.colorIndex++;
167
+
168
+ const pos = randomSpawnPosition();
169
+ const player = new ArenaPlayerSchema();
170
+ player.sessionId = client.sessionId;
171
+ player.name = name;
172
+ player.color = color;
173
+ player.x = pos.x;
174
+ player.y = pos.y;
175
+ player.z = pos.z;
176
+
177
+ this.state.players.set(client.sessionId, player);
178
+
179
+ // First human player becomes the host
180
+ if (!this.state.hostId) {
181
+ this.state.hostId = client.sessionId;
182
+ }
183
+ }
184
+
185
+ onLeave(client: Client, _code?: number) {
186
+ this.state.players.delete(client.sessionId);
187
+
188
+ // If tagger left, reassign
189
+ if (this.state.taggerId === client.sessionId && this.state.phase === 'playing') {
190
+ this.state.taggerId = randomTagger(this.state);
191
+ this.state.tagCooldown = TAG_COOLDOWN_SECS;
192
+ }
193
+
194
+ // If host left, reassign to next human player
195
+ if (this.state.hostId === client.sessionId) {
196
+ this.state.hostId = '';
197
+ for (const [id, p] of this.state.players) {
198
+ if (!p.isNPC) {
199
+ this.state.hostId = id;
200
+ break;
201
+ }
202
+ }
203
+ }
204
+ }
205
+
206
+ onDispose() {
207
+ // Room cleanup
208
+ }
209
+
210
+ // --- NPC spawning ---
211
+
212
+ private spawnNPCs(count: number) {
213
+ for (let i = 0; i < count; i++) {
214
+ const npcId = `npc_${i}`;
215
+ const npc = new ArenaPlayerSchema();
216
+ npc.sessionId = npcId;
217
+ npc.name = `Bot-${String.fromCharCode(65 + i)}`; // Bot-A, Bot-B, Bot-C, ...
218
+ npc.color = NPC_COLOR;
219
+ npc.isNPC = true;
220
+ const pos = randomSpawnPosition();
221
+ npc.x = pos.x;
222
+ npc.y = pos.y;
223
+ npc.z = pos.z;
224
+ this.state.players.set(npcId, npc);
225
+
226
+ const route = PATROL_ROUTES[i % PATROL_ROUTES.length]!;
227
+ this.npcBrains.set(npcId, {
228
+ behavior: 'idle',
229
+ waypointIndex: 0,
230
+ routeIndex: i % PATROL_ROUTES.length,
231
+ targetX: route[0]!.x,
232
+ targetZ: route[0]!.z,
233
+ });
234
+ }
235
+ }
236
+
237
+ // --- Phase state machine ---
238
+
239
+ private updatePhase(dt: number) {
240
+ switch (this.state.phase) {
241
+ case 'lobby':
242
+ // Wait for host to send 'start_game'
243
+ break;
244
+
245
+ case 'countdown':
246
+ this.state.phaseTimer -= dt;
247
+ if (this.state.phaseTimer <= 0) {
248
+ this.state.phase = 'playing';
249
+ this.state.phaseTimer = ROUND_SECS;
250
+ this.state.roundNumber++;
251
+ this.state.taggerId = randomTagger(this.state);
252
+ this.state.tagCooldown = TAG_COOLDOWN_SECS;
253
+ for (const player of this.state.players.values()) {
254
+ player.score = 0;
255
+ const pos = randomSpawnPosition();
256
+ player.x = pos.x;
257
+ player.y = pos.y;
258
+ player.z = pos.z;
259
+ }
260
+ }
261
+ break;
262
+
263
+ case 'playing':
264
+ this.state.phaseTimer -= dt;
265
+ if (this.state.phaseTimer <= 0 || this.state.players.size < MIN_PLAYERS) {
266
+ this.state.phase = 'scoreboard';
267
+ this.state.phaseTimer = SCOREBOARD_SECS;
268
+ }
269
+ break;
270
+
271
+ case 'scoreboard':
272
+ this.state.phaseTimer -= dt;
273
+ if (this.state.phaseTimer <= 0) {
274
+ if (this.state.players.size >= MIN_PLAYERS) {
275
+ this.state.phase = 'countdown';
276
+ this.state.phaseTimer = COUNTDOWN_SECS;
277
+ } else {
278
+ this.state.phase = 'lobby';
279
+ this.state.phaseTimer = 0;
280
+ }
281
+ }
282
+ break;
283
+ }
284
+ }
285
+
286
+ // --- Tag proximity check ---
287
+
288
+ private updateTag(dt: number) {
289
+ if (this.state.phase !== 'playing') return;
290
+
291
+ if (this.state.tagCooldown > 0) this.state.tagCooldown -= dt;
292
+
293
+ // Increment scores for non-taggers
294
+ for (const player of this.state.players.values()) {
295
+ if (player.sessionId !== this.state.taggerId) {
296
+ player.score++;
297
+ }
298
+ }
299
+
300
+ // Check tag proximity
301
+ if (this.state.tagCooldown > 0) return;
302
+
303
+ const tagger = this.state.players.get(this.state.taggerId);
304
+ if (!tagger) return;
305
+
306
+ for (const [id, player] of this.state.players) {
307
+ if (id === this.state.taggerId) continue;
308
+ const dx = tagger.x - player.x;
309
+ const dz = tagger.z - player.z;
310
+ if (dx * dx + dz * dz < TAG_RADIUS_SQ) {
311
+ this.state.taggerId = id;
312
+ this.state.tagCooldown = TAG_COOLDOWN_SECS;
313
+ break;
314
+ }
315
+ }
316
+ }
317
+
318
+ // --- NPC behavior decisions ---
319
+
320
+ private updateNPCs() {
321
+ for (const [id, npc] of this.state.players) {
322
+ if (!npc.isNPC) continue;
323
+ const brain = this.npcBrains.get(id);
324
+ if (!brain) continue;
325
+
326
+ if (this.state.phase !== 'playing') {
327
+ brain.behavior = 'idle';
328
+ npc.npcBehavior = 'idle';
329
+ npc.speed = 0;
330
+ continue;
331
+ }
332
+
333
+ const isTagged = this.state.taggerId === id;
334
+
335
+ if (isTagged) {
336
+ // CHASE: find nearest non-tagger
337
+ brain.behavior = 'chase';
338
+ let nearestDistSq = Infinity;
339
+ let nearest: ArenaPlayerSchema | null = null;
340
+ for (const [pid, p] of this.state.players) {
341
+ if (pid === id) continue;
342
+ const dx = npc.x - p.x;
343
+ const dz = npc.z - p.z;
344
+ const dSq = dx * dx + dz * dz;
345
+ if (dSq < nearestDistSq) {
346
+ nearestDistSq = dSq;
347
+ nearest = p;
348
+ }
349
+ }
350
+ if (nearest) {
351
+ brain.targetX = nearest.x;
352
+ brain.targetZ = nearest.z;
353
+ }
354
+ npc.speed = 6.25;
355
+ } else {
356
+ const tagger = this.state.players.get(this.state.taggerId);
357
+ if (tagger) {
358
+ const dx = npc.x - tagger.x;
359
+ const dz = npc.z - tagger.z;
360
+ const dSq = dx * dx + dz * dz;
361
+
362
+ if (dSq < FLEE_RADIUS_SQ) {
363
+ // FLEE
364
+ brain.behavior = 'flee';
365
+ const dist = Math.sqrt(dSq) || 1;
366
+ brain.targetX = Math.max(-18, Math.min(18, npc.x + (dx / dist) * 8));
367
+ brain.targetZ = Math.max(-18, Math.min(18, npc.z + (dz / dist) * 8));
368
+ npc.speed = 5.6;
369
+ } else {
370
+ // PATROL
371
+ brain.behavior = 'patrol';
372
+ const route = PATROL_ROUTES[brain.routeIndex]!;
373
+ const wp = route[brain.waypointIndex]!;
374
+ const dwx = npc.x - wp.x;
375
+ const dwz = npc.z - wp.z;
376
+ if (dwx * dwx + dwz * dwz < 4.0) {
377
+ brain.waypointIndex = (brain.waypointIndex + 1) % route.length;
378
+ const next = route[brain.waypointIndex]!;
379
+ brain.targetX = next.x;
380
+ brain.targetZ = next.z;
381
+ } else {
382
+ brain.targetX = wp.x;
383
+ brain.targetZ = wp.z;
384
+ }
385
+ npc.speed = 3.1;
386
+ }
387
+ } else {
388
+ brain.behavior = 'patrol';
389
+ npc.speed = 3.1;
390
+ }
391
+ }
392
+
393
+ npc.npcBehavior = brain.behavior;
394
+ }
395
+ }
396
+
397
+ // --- Server-side NPC movement (straight-line toward target) ---
398
+
399
+ private moveNPCsTowardTargets(dt: number) {
400
+ for (const [id, npc] of this.state.players) {
401
+ if (!npc.isNPC) continue;
402
+ const brain = this.npcBrains.get(id);
403
+ if (!brain || brain.behavior === 'idle') continue;
404
+
405
+ const dx = brain.targetX - npc.x;
406
+ const dz = brain.targetZ - npc.z;
407
+ const dist = Math.sqrt(dx * dx + dz * dz);
408
+ if (dist < 0.5) continue;
409
+
410
+ const moveSpeed = brain.behavior === 'chase' ? 5.0
411
+ : brain.behavior === 'flee' ? 4.5
412
+ : 2.5;
413
+
414
+ const step = Math.min(moveSpeed * dt, dist);
415
+ npc.x += (dx / dist) * step;
416
+ npc.z += (dz / dist) * step;
417
+ npc.rotY = Math.atan2(-dx, -dz);
418
+ }
419
+ }
420
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * GameRoom — multiplayer arena with collectible orbs.
3
+ *
4
+ * Uses @colyseus/schema state sync (the idiomatic Colyseus pattern).
5
+ * Server mutates Schema state directly; Colyseus encodes deltas and
6
+ * syncs them to all clients automatically.
7
+ *
8
+ * Messages (client → server only):
9
+ * position { x, y, z } — client movement input
10
+ *
11
+ * Everything else (players, orbs, scores) is synchronized via Schema
12
+ * state: new joiners receive the full state automatically, and ongoing
13
+ * changes are delta-encoded at the patch rate (~20 Hz).
14
+ */
15
+
16
+ import { Room, type Client } from 'colyseus';
17
+ import { Schema, MapSchema, type } from '@colyseus/schema';
18
+
19
+ // --- Constants ---
20
+
21
+ const COLORS = [0xff6644, 0x44ff99, 0x4499ff, 0xffee44, 0xff44bb, 0x44ffee, 0xaa44ff, 0xff4488];
22
+ const NAMES = ['Coral', 'Mint', 'Sky', 'Gold', 'Rose', 'Teal', 'Violet', 'Crimson'];
23
+ const ARENA_HALF = 7.5;
24
+ const SPAWN_RADIUS = 5;
25
+ const MAX_ORBS = 6;
26
+ const COLLECT_RADIUS_SQ = 1.0;
27
+ const ORB_RESPAWN_MS = 3_000;
28
+ const SIM_INTERVAL_MS = 50;
29
+
30
+ // --- Schema definitions ---
31
+
32
+ class PlayerSchema extends Schema {
33
+ @type('string') sessionId = '';
34
+ @type('string') name = '';
35
+ @type('uint32') color = 0;
36
+ @type('float32') x = 0;
37
+ @type('float32') y = 0;
38
+ @type('float32') z = 0;
39
+ @type('uint16') score = 0;
40
+ }
41
+
42
+ class OrbSchema extends Schema {
43
+ @type('string') id = '';
44
+ @type('float32') x = 0;
45
+ @type('float32') z = 0;
46
+ }
47
+
48
+ class GameState extends Schema {
49
+ @type({ map: PlayerSchema }) players = new MapSchema<PlayerSchema>();
50
+ @type({ map: OrbSchema }) orbs = new MapSchema<OrbSchema>();
51
+ }
52
+
53
+ // --- Helpers ---
54
+
55
+ let nextOrbId = 0;
56
+
57
+ function randomSpawnPosition(): { x: number; y: number; z: number } {
58
+ const angle = Math.random() * Math.PI * 2;
59
+ return {
60
+ x: Math.cos(angle) * SPAWN_RADIUS,
61
+ y: 0,
62
+ z: Math.sin(angle) * SPAWN_RADIUS,
63
+ };
64
+ }
65
+
66
+ function randomOrbPosition(): { x: number; z: number } {
67
+ return {
68
+ x: (Math.random() - 0.5) * ARENA_HALF * 1.6,
69
+ z: (Math.random() - 0.5) * ARENA_HALF * 1.6,
70
+ };
71
+ }
72
+
73
+ // --- Room ---
74
+
75
+ export class GameRoom extends Room {
76
+ state = new GameState();
77
+ patchRate = 50;
78
+
79
+ private colorIndex = 0;
80
+ // Orb respawns are counted in simulation ticks, not `this.clock` timers:
81
+ // this room must also run browser-hosted under @vgai/p2p-colyseus
82
+ // (VGAI_P2P builds), whose compat `Room` has no `clock`. Drive ALL timing
83
+ // from `setSimulationInterval` — it exists in both runtimes.
84
+ private orbRespawnTicks: number[] = [];
85
+
86
+ onCreate() {
87
+ // Handle client messages
88
+ this.onMessage('position', (client, position: { x: number; y: number; z: number }) => {
89
+ const player = this.state.players.get(client.sessionId);
90
+ if (player) {
91
+ player.x = Math.max(-ARENA_HALF, Math.min(ARENA_HALF, position.x));
92
+ player.y = position.y;
93
+ player.z = Math.max(-ARENA_HALF, Math.min(ARENA_HALF, position.z));
94
+ }
95
+ });
96
+
97
+ // Spawn initial orbs
98
+ for (let i = 0; i < MAX_ORBS; i++) {
99
+ this.spawnOrb();
100
+ }
101
+
102
+ // Server game loop at 20 Hz — check orb collection, tick pending respawns
103
+ this.setSimulationInterval(() => {
104
+ for (let i = this.orbRespawnTicks.length - 1; i >= 0; i--) {
105
+ if (--this.orbRespawnTicks[i]! <= 0) {
106
+ this.orbRespawnTicks.splice(i, 1);
107
+ this.spawnOrb();
108
+ }
109
+ }
110
+
111
+ for (const [orbId, orb] of this.state.orbs) {
112
+ for (const player of this.state.players.values()) {
113
+ const dx = player.x - orb.x;
114
+ const dz = player.z - orb.z;
115
+ if (dx * dx + dz * dz < COLLECT_RADIUS_SQ) {
116
+ player.score++;
117
+ this.state.orbs.delete(orbId);
118
+ this.orbRespawnTicks.push(Math.ceil(ORB_RESPAWN_MS / SIM_INTERVAL_MS));
119
+ break; // one collection per orb per tick
120
+ }
121
+ }
122
+ }
123
+ }, SIM_INTERVAL_MS);
124
+ }
125
+
126
+ onJoin(client: Client, options?: { color?: number; name?: string }) {
127
+ const idx = this.colorIndex % COLORS.length;
128
+ const color = (options?.color != null) ? options.color : COLORS[idx]!;
129
+ const name = options?.name ?? NAMES[idx]!;
130
+ this.colorIndex++;
131
+
132
+ const pos = randomSpawnPosition();
133
+ const player = new PlayerSchema();
134
+ player.sessionId = client.sessionId;
135
+ player.name = name;
136
+ player.color = color;
137
+ player.x = pos.x;
138
+ player.y = pos.y;
139
+ player.z = pos.z;
140
+ player.score = 0;
141
+
142
+ this.state.players.set(client.sessionId, player);
143
+ }
144
+
145
+ onLeave(client: Client, _code?: number) {
146
+ this.state.players.delete(client.sessionId);
147
+ }
148
+
149
+ onDispose() {
150
+ // Room cleanup
151
+ }
152
+
153
+ private spawnOrb() {
154
+ const id = `orb_${nextOrbId++}`;
155
+ const pos = randomOrbPosition();
156
+ const orb = new OrbSchema();
157
+ orb.id = id;
158
+ orb.x = pos.x;
159
+ orb.z = pos.z;
160
+ this.state.orbs.set(id, orb);
161
+ }
162
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Room registry — defines all available Colyseus rooms, passed to
3
+ * `startColyseus({ rooms, ... })` (colyseus-setup.ts) to call server.define()
4
+ * for each. Imported by the e2e showcase harness
5
+ * (packages/editor/e2e/helpers/colyseus.ts) and the unit-test in-process
6
+ * loopback server (packages/editor/test/helpers/colyseus-loopback.ts).
7
+ */
8
+
9
+ import { GameRoom } from './rooms/game-room.js';
10
+ import { ArenaRoom } from './rooms/arena-room.js';
11
+
12
+ export const rooms = [
13
+ { name: 'game_room', handler: GameRoom },
14
+ { name: 'arena_room', handler: ArenaRoom },
15
+ ];
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2021",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+
7
+ "experimentalDecorators": true,
8
+ "useDefineForClassFields": false,
9
+
10
+ "strict": true,
11
+ "esModuleInterop": true,
12
+ "verbatimModuleSyntax": true,
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "skipLibCheck": true,
16
+ "noEmit": true
17
+ },
18
+ "include": ["."]
19
+ }
@@ -0,0 +1,40 @@
1
+ # src/data — data assets
2
+
3
+ Designer-tunable values live here: `<name>.data.json` (the values, with a
4
+ `"$schema"` line) governed by `<name>.schema.ts` (Zod, engineer-owned) and its
5
+ emitted `<name>.schema.json` (`npm run emit-schemas`, committed). `tuning.*` +
6
+ `tuning.ts` (the engine `defineData` handle) are the shipped worked example —
7
+ copy that trio for each new asset and register it in `assets.ts` (one list,
8
+ feeding both `npm run emit-schemas` and the build-time check that fails
9
+ `npm run build` loud on invalid data or a dangling `"file#key"` ref; `vgai
10
+ doctor <folder>` runs the same checks). Never hardcode a tunable as a constant in
11
+ game code — see "Tunable values → a data asset" in this project's CLAUDE.md,
12
+ and `docs/DATA-TOOLS-DESIGN.md` in the engine checkout for the design.
13
+
14
+ Two blessed shapes, and only these (they're what the editor's DATA tab can
15
+ render as real widgets): a **singleton** — one `z.object` config whose fields
16
+ are scalars (number/string/boolean/enum), optionally grouped one or two
17
+ levels deep in nested `z.object`s (the tab renders groups as sub-forms) — or
18
+ a **table** — `z.record(z.string(), rowSchema)` with a flat row schema,
19
+ rendered as a grid (this Zod version's `z.record` needs a key schema AND a
20
+ value schema — the single-arg `z.record(rowSchema)` shorthand is Zod-3-only
21
+ and throws here). Anything the tab can't type (arrays, deep nesting, unions)
22
+ degrades to a raw-JSON cell that refuses invalid commits — prefer
23
+ flattening, or a table, when a designer should tune it.
24
+
25
+ Table assets also get a spreadsheet-friendly bulk-edit path in the DATA tab:
26
+ **Export CSV** downloads the whole table (`key,<row fields>` header, one row
27
+ per data key); **Import CSV** is a WHOLE-TABLE REPLACE — any row the sheet
28
+ doesn't mention is deleted — and ATOMIC: if any cell fails to coerce to its
29
+ column's field kind (e.g. non-numeric text in a `number` column), nothing is
30
+ written; fix the sheet and re-import. `"$schema"` is never a CSV column —
31
+ it's carried over from the file automatically.
32
+
33
+ Cross-asset fields (one table's row pointing at another asset — an enemy's
34
+ drop pointing at an item) should use `dataRef('<target-stem>')`
35
+ (`@engine/data/data-ref`) in the schema instead of a bare `z.string()`: the
36
+ DATA tab then renders that field as a `stem#key` PICKER (listing the target
37
+ file's own keys) instead of free text, and `vgai doctor`/`npm run build`
38
+ catch it if the target file is ever renamed or deleted — not just if the key
39
+ inside it goes missing. Read it back with `getRef(targetHandle, row.field)`
40
+ (pass the target's own `defineData` handle, not a string) from game code.
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Data-asset registry — the ONE list of this project's data assets
3
+ * (docs/DATA-TOOLS-DESIGN.md §6.7). Two consumers, one list:
4
+ *
5
+ * - `emit-schemas.ts` (root): emits each `<name>.schema.json` and
6
+ * validates each `<name>.data.json` (`npm run emit-schemas`).
7
+ * - `vite.config.ts`: feeds the engine's `vgaiDataCheck` build plugin, so
8
+ * `npm run build` FAILS LOUD on a data file that no longer matches its
9
+ * schema or a dangling `"file#key"` ref — drift is caught at build time,
10
+ * not by a player.
11
+ *
12
+ * Adding a data asset: copy the `tuning.*` trio (see ./README.md), then add
13
+ * one line here. An unregistered `.data.json` still loads and hot-reloads,
14
+ * but gets no emitted schema and no build-time check (the build prints a
15
+ * warning naming this file).
16
+ *
17
+ * KEEP THIS MODULE SIDE-EFFECT-FREE and dependency-light (schemas + zod
18
+ * only): `vite.config.ts` imports it at config-load time, before any dev
19
+ * server or build exists.
20
+ */
21
+ import type { z } from 'zod';
22
+ import { TuningSchema } from './tuning.schema';
23
+
24
+ export interface ProjectDataAsset {
25
+ /** Asset stem: the files are `src/data/<name>.data.json` / `<name>.schema.ts` / emitted `<name>.schema.json`. */
26
+ readonly name: string;
27
+ /** The governing Zod schema (the `.schema.ts` export). */
28
+ readonly schema: z.ZodType;
29
+ }
30
+
31
+ export const dataAssets: readonly ProjectDataAsset[] = [{ name: 'tuning', schema: TuningSchema }];
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "./tuning.schema.json",
3
+ "boxSpinSpeed": 0.8,
4
+ "boxColor": "#4488ff"
5
+ }