@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.
- package/LICENSE +661 -0
- package/README.md +35 -0
- package/dist/a/b/c/test-nested.json +1 -0
- package/dist/assets/CCDIKSolver-Dz-ysxZF.js +1 -0
- package/dist/assets/SelectionBox-BXsv_6aH.js +1 -0
- package/dist/assets/browser-project-scripts-DOru4L1x.js +2 -0
- package/dist/assets/browserAll-CEkWYlFk.js +1 -0
- package/dist/assets/collision-world-DniMB0qq.glb +0 -0
- package/dist/assets/create-runtime-DYIWr3va.js +1 -0
- package/dist/assets/data-asset-Bb9sIM7i.js +5 -0
- package/dist/assets/esbuild-BHljloGq.wasm +0 -0
- package/dist/assets/fake-three-BMTQ9rl_.js +1 -0
- package/dist/assets/game-8SDZPPgH.js +1 -0
- package/dist/assets/game-BI111o5f.js +10 -0
- package/dist/assets/game-CevorxyV.js +1 -0
- package/dist/assets/game-Di1H0gFj.js +1 -0
- package/dist/assets/game-Di7XlywD.js +1 -0
- package/dist/assets/game-DjnCaw8R.js +1 -0
- package/dist/assets/game-Q9A0aXSV.js +1 -0
- package/dist/assets/game-pt_1lQLv.js +1 -0
- package/dist/assets/game-source-CvidN7AB.js +1 -0
- package/dist/assets/game-xsOho_wg.js +83 -0
- package/dist/assets/index-Bp5cecD6.js +12 -0
- package/dist/assets/index-CPVV6pjw.js +1 -0
- package/dist/assets/index-CpOnYyeD.js +213 -0
- package/dist/assets/index-Cs8xwv3e.js +1 -0
- package/dist/assets/index-DL7RcQ0n.js +27 -0
- package/dist/assets/index-Dr8H_6Rl.js +1 -0
- package/dist/assets/index-_bCaipUk.js +13650 -0
- package/dist/assets/ingest-siblings-Dz8eEqrV.js +1 -0
- package/dist/assets/mount-manifest-Bhk0ohsl.js +1 -0
- package/dist/assets/recast-navigation.wasm-compat-CvP4UGK6.js +1 -0
- package/dist/assets/scene-query-BivAHhJu.js +1 -0
- package/dist/assets/stats.module--VATS4Kh.js +1 -0
- package/dist/assets/webworkerAll-BLrH0F6F.js +1 -0
- package/dist/assets/xstate-animation-binding-1NGLmw6y.js +1 -0
- package/dist/assets/xstate.esm-BgUD33nl.js +3 -0
- package/dist/index.html +37 -0
- package/dist/ingest/racing-game/UPSTREAM.md +68 -0
- package/dist/ingest/racing-game/a/Inter-Black.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Black.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-BlackItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-BlackItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Bold.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Bold.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-BoldItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-BoldItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBold.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBold.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBoldItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBoldItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLight.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLight.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLightItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLightItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Italic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Italic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Light.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Light.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-LightItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-LightItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Medium.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Medium.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-MediumItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-MediumItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Regular.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Regular.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBold.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBold.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBoldItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBoldItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Thin.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Thin.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ThinItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ThinItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-italic.var.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-roman.var.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter.var.woff2 +0 -0
- package/dist/ingest/racing-game/a/index.css +1 -0
- package/dist/ingest/racing-game/images/bronze.png +0 -0
- package/dist/ingest/racing-game/images/gold.png +0 -0
- package/dist/ingest/racing-game/images/silver.png +0 -0
- package/dist/ingest/racing-game/index.html +15 -0
- package/dist/ingest/racing-game/models/chassis-draco.glb +0 -0
- package/dist/ingest/racing-game/models/track-draco.glb +0 -0
- package/dist/ingest/racing-game/models/wheel-draco.glb +0 -0
- package/dist/ingest/racing-game/rg-entry.js +1 -0
- package/dist/ingest/racing-game/sounds/accelerate.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/boost.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/crash.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/engine.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/honk.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/tire-brake.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/train.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/water.mp3 +0 -0
- package/dist/ingest/racing-game/textures/cursor.svg +3 -0
- package/dist/ingest/racing-game/textures/dikhololo_night_1k.hdr +0 -0
- package/dist/ingest/racing-game/textures/heightmap_1024.png +0 -0
- package/dist/ingest/racing-game/textures/mask.svg +11 -0
- package/dist/ingest/racing-game/textures/no_sound.png +0 -0
- package/dist/ingest/racing-game/three-r139.module.js +50617 -0
- package/dist/ingest/racing-game/vendor.js +179 -0
- package/dist/ingest/racing-game/vgai.game.json +20 -0
- package/dist/ingest/simcity/UPSTREAM.md +65 -0
- package/dist/ingest/simcity/favicon.ico +0 -0
- package/dist/ingest/simcity/fonts/Bebas-Regular.otf +0 -0
- package/dist/ingest/simcity/fonts/Digitalt.otf +0 -0
- package/dist/ingest/simcity/fonts/Stanberry.ttf +0 -0
- package/dist/ingest/simcity/icons/bulldozer-color.png +0 -0
- package/dist/ingest/simcity/icons/bulldozer.png +0 -0
- package/dist/ingest/simcity/icons/calendar.png +0 -0
- package/dist/ingest/simcity/icons/factory-color.png +0 -0
- package/dist/ingest/simcity/icons/factory.png +0 -0
- package/dist/ingest/simcity/icons/house-color.png +0 -0
- package/dist/ingest/simcity/icons/house.png +0 -0
- package/dist/ingest/simcity/icons/job.png +0 -0
- package/dist/ingest/simcity/icons/office.png +0 -0
- package/dist/ingest/simcity/icons/pause-color.png +0 -0
- package/dist/ingest/simcity/icons/pause.png +0 -0
- package/dist/ingest/simcity/icons/person.png +0 -0
- package/dist/ingest/simcity/icons/play-color.png +0 -0
- package/dist/ingest/simcity/icons/play.png +0 -0
- package/dist/ingest/simcity/icons/power-color.png +0 -0
- package/dist/ingest/simcity/icons/power-line-color.png +0 -0
- package/dist/ingest/simcity/icons/power.png +0 -0
- package/dist/ingest/simcity/icons/road-color.png +0 -0
- package/dist/ingest/simcity/icons/road.png +0 -0
- package/dist/ingest/simcity/icons/select-color.png +0 -0
- package/dist/ingest/simcity/icons/select.png +0 -0
- package/dist/ingest/simcity/icons/store-color.png +0 -0
- package/dist/ingest/simcity/index.html +94 -0
- package/dist/ingest/simcity/index.js +58 -0
- package/dist/ingest/simcity/main.css +233 -0
- package/dist/ingest/simcity/models/armored-truck.glb +0 -0
- package/dist/ingest/simcity/models/building-block-4floor-corner.glb +0 -0
- package/dist/ingest/simcity/models/building-block-4floor-short.glb +0 -0
- package/dist/ingest/simcity/models/building-block-5floor.glb +0 -0
- package/dist/ingest/simcity/models/building-burger-joint.glb +0 -0
- package/dist/ingest/simcity/models/building-cafe.glb +0 -0
- package/dist/ingest/simcity/models/building-casino.glb +0 -0
- package/dist/ingest/simcity/models/building-cinema.glb +0 -0
- package/dist/ingest/simcity/models/building-house-block-big.glb +0 -0
- package/dist/ingest/simcity/models/building-house-family-large.glb +0 -0
- package/dist/ingest/simcity/models/building-house-family-small.glb +0 -0
- package/dist/ingest/simcity/models/building-office-balcony.glb +0 -0
- package/dist/ingest/simcity/models/building-office-big.glb +0 -0
- package/dist/ingest/simcity/models/building-office-pyramid.glb +0 -0
- package/dist/ingest/simcity/models/building-office-tall.glb +0 -0
- package/dist/ingest/simcity/models/building-office.glb +0 -0
- package/dist/ingest/simcity/models/building-restaurant.glb +0 -0
- package/dist/ingest/simcity/models/building-skyscraper.glb +0 -0
- package/dist/ingest/simcity/models/car-ambulance-pickup.glb +0 -0
- package/dist/ingest/simcity/models/car-baywatch.glb +0 -0
- package/dist/ingest/simcity/models/car-hippie-van.glb +0 -0
- package/dist/ingest/simcity/models/car-passenger-race.glb +0 -0
- package/dist/ingest/simcity/models/car-passenger.glb +0 -0
- package/dist/ingest/simcity/models/car-police.glb +0 -0
- package/dist/ingest/simcity/models/car-taxi.glb +0 -0
- package/dist/ingest/simcity/models/car-tow-truck.glb +0 -0
- package/dist/ingest/simcity/models/car-truck-dump.glb +0 -0
- package/dist/ingest/simcity/models/car-veteran.glb +0 -0
- package/dist/ingest/simcity/models/construction-small.glb +0 -0
- package/dist/ingest/simcity/models/data-center.glb +0 -0
- package/dist/ingest/simcity/models/industry-factory-old.glb +0 -0
- package/dist/ingest/simcity/models/industry-factory.glb +0 -0
- package/dist/ingest/simcity/models/industry-refinery.glb +0 -0
- package/dist/ingest/simcity/models/industry-warehouse.glb +0 -0
- package/dist/ingest/simcity/models/power_line_pole_modified.glb +0 -0
- package/dist/ingest/simcity/models/tile-plain_grass.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-curve.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-end.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-intersection-t.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-intersection.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-straight.glb +0 -0
- package/dist/ingest/simcity/models/truck.glb +0 -0
- package/dist/ingest/simcity/statusIcons/no-power.png +0 -0
- package/dist/ingest/simcity/statusIcons/no-road-access.png +0 -0
- package/dist/ingest/simcity/textures/base.png +0 -0
- package/dist/ingest/simcity/textures/grid.png +0 -0
- package/dist/ingest/simcity/textures/specular.png +0 -0
- package/dist/ingest/simcity/three-r155.module.js +51466 -0
- package/dist/ingest/simcity/vgai.game.json +20 -0
- package/dist/ingest/tanks/UPSTREAM.md +69 -0
- package/dist/ingest/tanks/index.html +214 -0
- package/dist/ingest/tanks/models/pixelTank.fbx +0 -0
- package/dist/ingest/tanks/tanks-entry.js +92 -0
- package/dist/ingest/tanks/three-r170.module.js +54571 -0
- package/dist/ingest/tanks/vgai.game.json +20 -0
- package/dist/inputmaps/default.inputmap.json +41 -0
- package/dist/jsm/libs/draco/gltf/draco_decoder.js +33 -0
- package/dist/jsm/libs/draco/gltf/draco_decoder.wasm +0 -0
- package/dist/jsm/libs/draco/gltf/draco_wasm_wrapper.js +116 -0
- package/dist/scenes/untitled.vscn.json +29 -0
- package/dist/textures/waternormals.jpg +0 -0
- package/dist-server/packaged.mjs +18155 -0
- package/package.json +36 -0
- package/server/asset-library-routes.ts +833 -0
- package/server/canonical-path.ts +50 -0
- package/server/cloud-asset-catalog.ts +101 -0
- package/server/dev.ts +350 -0
- package/server/editor-server.ts +1537 -0
- package/server/editor-sse.ts +73 -0
- package/server/game-globals-shadow.ts +58 -0
- package/server/local-asset-catalog.ts +163 -0
- package/server/packaged.ts +571 -0
- package/server/prod.ts +75 -0
- package/server/project-optimize-deps-entries.ts +149 -0
- package/server/project-view.ts +65 -0
- package/server/server-utils.ts +430 -0
- package/server/session-registry.ts +93 -0
- package/src/EditorContext.tsx +87 -0
- package/src/achieved-tier.ts +126 -0
- package/src/action-registry.ts +268 -0
- package/src/adapter-resolver.ts +1858 -0
- package/src/asset-events.ts +100 -0
- package/src/authoring/active-adapter.ts +98 -0
- package/src/authoring/active-systems.ts +50 -0
- package/src/authoring/boundary-authoring-adapter.ts +178 -0
- package/src/authoring/composite-authoring-adapter.ts +891 -0
- package/src/authoring/design-time-layers.ts +776 -0
- package/src/authoring/edit-mode-authoring.ts +643 -0
- package/src/authoring/embed-only-adapter.ts +57 -0
- package/src/authoring/ephemeral-persistence.ts +26 -0
- package/src/authoring/eyedropper-session.ts +69 -0
- package/src/authoring/first-party-hierarchy-menu.ts +74 -0
- package/src/authoring/first-party-inspector/AddSectionMenu.tsx +118 -0
- package/src/authoring/first-party-inspector/EnvironmentSection.tsx +550 -0
- package/src/authoring/first-party-inspector/HeaderSection.tsx +329 -0
- package/src/authoring/first-party-inspector/is-first-party-selection.ts +44 -0
- package/src/authoring/first-party-inspector/register.tsx +108 -0
- package/src/authoring/ingest-authoring-adapter.ts +475 -0
- package/src/authoring/ingest-capture.ts +94 -0
- package/src/authoring/ingest-game-adapter.ts +291 -0
- package/src/authoring/ingest-iframe-adapter.ts +38 -0
- package/src/authoring/ingest-iframe-reachable-adapter.ts +401 -0
- package/src/authoring/ingest-overlay.ts +193 -0
- package/src/authoring/layered-pick.ts +87 -0
- package/src/authoring/mount-failure-report.ts +112 -0
- package/src/authoring/overlay-report.ts +52 -0
- package/src/authoring/project-scenes.ts +111 -0
- package/src/authoring/react-dom-authoring-adapter.ts +795 -0
- package/src/authoring/react-inspector-section.tsx +1041 -0
- package/src/authoring/react-world-authoring-adapter.ts +2400 -0
- package/src/authoring/root-activation.ts +61 -0
- package/src/authoring/scene-ui-layer.ts +485 -0
- package/src/authoring/story-overlay.ts +48 -0
- package/src/authoring/ui-authoring-adapter.ts +325 -0
- package/src/authoring/ui-hierarchy-menu.ts +40 -0
- package/src/authoring/ui-inspector-sections.tsx +156 -0
- package/src/authoring/undo-timeline.ts +145 -0
- package/src/authoring/vgai-scene-authoring-adapter.ts +746 -0
- package/src/authoring/viewport-pick-context.ts +31 -0
- package/src/authoring/viewport-raycast.ts +80 -0
- package/src/authoring/world-overlay.ts +76 -0
- package/src/authoring/world-pan-state.ts +115 -0
- package/src/authoring/world-session-state.ts +102 -0
- package/src/authoring/world2d-authoring-adapter.ts +313 -0
- package/src/browser-external-watch.ts +98 -0
- package/src/browser-play-entry.ts +112 -0
- package/src/browser-project-scripts.ts +112 -0
- package/src/browser-transpile.ts +246 -0
- package/src/browser-watch-reload.ts +100 -0
- package/src/command-listener.ts +326 -0
- package/src/components/AlignToolbar.tsx +189 -0
- package/src/components/AppRoot.tsx +73 -0
- package/src/components/AssetBrowser.tsx +502 -0
- package/src/components/BottomPanel.tsx +326 -0
- package/src/components/BuildPanel.tsx +455 -0
- package/src/components/CameraInfo.tsx +56 -0
- package/src/components/CommandPalette.tsx +265 -0
- package/src/components/ConsolePanel.tsx +272 -0
- package/src/components/DataPanel.tsx +1268 -0
- package/src/components/DefaultEditorLayout.tsx +509 -0
- package/src/components/FolderTree.tsx +179 -0
- package/src/components/GameHierarchy.tsx +1349 -0
- package/src/components/Inspector.tsx +671 -0
- package/src/components/InspectorToolSection.tsx +74 -0
- package/src/components/MountFailureBanner.tsx +110 -0
- package/src/components/NetworkSection.tsx +109 -0
- package/src/components/OnlineAssetBrowser.tsx +448 -0
- package/src/components/OverlayReportBanner.tsx +122 -0
- package/src/components/PlayBar.tsx +356 -0
- package/src/components/PrefabSaveDialog.tsx +201 -0
- package/src/components/ProjectHeader.tsx +174 -0
- package/src/components/ProjectScreen.tsx +1004 -0
- package/src/components/RightPanel.tsx +166 -0
- package/src/components/RootActivateDialog.tsx +105 -0
- package/src/components/SaveDialog.tsx +197 -0
- package/src/components/SaveStatus.tsx +69 -0
- package/src/components/StatsOverlay.tsx +35 -0
- package/src/components/ToolHost.tsx +80 -0
- package/src/components/Toolbar.tsx +307 -0
- package/src/components/ViewportOverlay.tsx +234 -0
- package/src/components/ViewportPanel.tsx +369 -0
- package/src/components/WorldContextMenu.tsx +257 -0
- package/src/components/WorldSelectionOverlay.tsx +2003 -0
- package/src/components/WorldTextEditor.tsx +86 -0
- package/src/components/asset-viewers/AssetViewer.tsx +38 -0
- package/src/components/asset-viewers/AudioViewer.tsx +149 -0
- package/src/components/asset-viewers/GenericJsonViewer.tsx +77 -0
- package/src/components/asset-viewers/ImageViewer.tsx +65 -0
- package/src/components/asset-viewers/InputMapViewer.tsx +206 -0
- package/src/components/asset-viewers/ModelViewer.tsx +180 -0
- package/src/components/asset-viewers/OnlineAssetDetail.tsx +236 -0
- package/src/components/data-csv.ts +301 -0
- package/src/components/entity-name-commit.ts +18 -0
- package/src/components/inspector-property-grouping.ts +65 -0
- package/src/components/inspector-widgets/AlignmentGrid.tsx +188 -0
- package/src/components/inspector-widgets/BorderEditor.tsx +305 -0
- package/src/components/inspector-widgets/ColorPicker.tsx +548 -0
- package/src/components/inspector-widgets/FilterEditor.tsx +108 -0
- package/src/components/inspector-widgets/FontPicker.tsx +196 -0
- package/src/components/inspector-widgets/GradientEditor.tsx +277 -0
- package/src/components/inspector-widgets/ScrubbableInput.tsx +152 -0
- package/src/components/inspector-widgets/ShadowEditor.tsx +332 -0
- package/src/components/inspector-widgets/color-utils.ts +202 -0
- package/src/components/inspector-widgets/gallery-main.tsx +167 -0
- package/src/components/inspector-widgets/index.ts +22 -0
- package/src/components/inspector-widgets/shared.tsx +135 -0
- package/src/components/inspectors/AnimationSection.tsx +143 -0
- package/src/components/inspectors/AudioSection.tsx +215 -0
- package/src/components/inspectors/BoneAttachmentSection.tsx +82 -0
- package/src/components/inspectors/CameraSection.tsx +105 -0
- package/src/components/inspectors/ComponentsSection.tsx +358 -0
- package/src/components/inspectors/JointsSection.tsx +255 -0
- package/src/components/inspectors/LightSection.tsx +116 -0
- package/src/components/inspectors/MaterialSection.tsx +1189 -0
- package/src/components/inspectors/MeshSection.tsx +99 -0
- package/src/components/inspectors/NavigationSection.tsx +58 -0
- package/src/components/inspectors/ParticleSection.tsx +681 -0
- package/src/components/inspectors/PhysicsSection.tsx +326 -0
- package/src/components/inspectors/PostProcessingSection.tsx +1290 -0
- package/src/components/inspectors/RenderingSection.tsx +109 -0
- package/src/components/inspectors/ShadowSection.tsx +84 -0
- package/src/components/inspectors/SplineSection.tsx +191 -0
- package/src/components/inspectors/TransformSection.tsx +191 -0
- package/src/components/inspectors/json-schema-fields.ts +281 -0
- package/src/components/inspectors/multi-edit-utils.ts +49 -0
- package/src/components/inspectors/zod-fields.ts +184 -0
- package/src/components/primitives/ColorInput.tsx +109 -0
- package/src/components/primitives/JsonInput.tsx +98 -0
- package/src/components/primitives/NumberInput.tsx +164 -0
- package/src/components/primitives/Panel.tsx +68 -0
- package/src/components/primitives/SectionHeader.tsx +45 -0
- package/src/components/primitives/Tooltip.tsx +82 -0
- package/src/components/primitives/Vec3Input.tsx +62 -0
- package/src/components/world-overlay-gestures.ts +1438 -0
- package/src/doctor/data-checks.ts +285 -0
- package/src/doctor/report.ts +386 -0
- package/src/editor-api.ts +700 -0
- package/src/editor-console.ts +194 -0
- package/src/editor-hotkeys.ts +441 -0
- package/src/editor-layers.ts +2 -0
- package/src/editor-mode.ts +28 -0
- package/src/editor-store.ts +2225 -0
- package/src/editor-viewport.ts +1924 -0
- package/src/entity-factory.ts +221 -0
- package/src/game-globals-prelude.ts +27 -0
- package/src/gated-globals.ts +131 -0
- package/src/generated/setup-by-example.generated.ts +53 -0
- package/src/gizmo-registry.ts +608 -0
- package/src/handle-system.ts +221 -0
- package/src/hierarchy-menu-registry.ts +70 -0
- package/src/hierarchy-rows.ts +73 -0
- package/src/hosted-example.ts +160 -0
- package/src/hotkeys.ts +142 -0
- package/src/ingest/discovery-react.ts +132 -0
- package/src/ingest/discovery.ts +205 -0
- package/src/ingest/discovery2d.ts +204 -0
- package/src/ingest/esm-entry-resolution.ts +102 -0
- package/src/ingest/games/bloom-composer/game.js +99 -0
- package/src/ingest/games/bloom-composer/vgai.game.json +21 -0
- package/src/ingest/games/embed-bundle/embed.html +10 -0
- package/src/ingest/games/embed-bundle/vgai.game.json +21 -0
- package/src/ingest/games/games-fps/assets/collision-world.glb +0 -0
- package/src/ingest/games/games-fps/game.js +479 -0
- package/src/ingest/games/games-fps/vgai.game.json +24 -0
- package/src/ingest/games/iframe-reachable/game-source.js +58 -0
- package/src/ingest/games/iframe-reachable/vgai.game.json +21 -0
- package/src/ingest/games/interactive-cubes/game.js +139 -0
- package/src/ingest/games/interactive-cubes/vgai.game.json +21 -0
- package/src/ingest/games/mismatched-three/fake-three.js +38 -0
- package/src/ingest/games/mismatched-three/game.js +10 -0
- package/src/ingest/games/mismatched-three/vgai.game.json +22 -0
- package/src/ingest/games/own-three-bundled/game.js +43 -0
- package/src/ingest/games/own-three-bundled/vgai.game.json +21 -0
- package/src/ingest/games/pointerlock-maze/game.js +288 -0
- package/src/ingest/games/pointerlock-maze/vgai.game.json +22 -0
- package/src/ingest/games-2d/_fixtures/first-party-scene.scn2d.json +124 -0
- package/src/ingest/games-2d/bubbo-bubbo/vgai.game.json +25 -0
- package/src/ingest/games-2d/bunnymark/game.ts +88 -0
- package/src/ingest/games-2d/bunnymark/vgai.game.json +21 -0
- package/src/ingest/games-2d/flappy/game.ts +148 -0
- package/src/ingest/games-2d/flappy/vgai.game.json +21 -0
- package/src/ingest/games-2d/flappy-pixi/vgai.game.json +25 -0
- package/src/ingest/games-2d/puzzling-potions/vgai.game.json +25 -0
- package/src/ingest/games-2d/tilemap-camera/game.ts +115 -0
- package/src/ingest/games-2d/tilemap-camera/vgai.game.json +21 -0
- package/src/ingest/games-react/react-rpg/entry.tsx +185 -0
- package/src/ingest/games-react/react-rpg/shims.d.ts +26 -0
- package/src/ingest/games-react/react-rpg/src/ui/game-state.tsx +129 -0
- package/src/ingest/games-react/react-rpg/vgai.game.json +21 -0
- package/src/ingest/host-namespace-registry.ts +196 -0
- package/src/ingest/types.ts +127 -0
- package/src/ingest-hierarchy-rows.ts +207 -0
- package/src/ingest-mode.ts +2092 -0
- package/src/ingest-siblings.ts +466 -0
- package/src/initial-scene.ts +194 -0
- package/src/inspector-section-registry.ts +117 -0
- package/src/interactive-edit-scope.ts +34 -0
- package/src/main.tsx +23 -0
- package/src/manifest-project.ts +43 -0
- package/src/model-thumbnail.ts +156 -0
- package/src/module-mode.ts +513 -0
- package/src/navmesh-handler.ts +94 -0
- package/src/packaged-runtime.ts +49 -0
- package/src/play-mode.ts +1148 -0
- package/src/playground/main.ts +206 -0
- package/src/project-manager.ts +173 -0
- package/src/scene-diff.ts +112 -0
- package/src/scene-serializer.ts +144 -0
- package/src/scene-sync.ts +587 -0
- package/src/scene-ui-mode/active-scene-ui.ts +58 -0
- package/src/scene-ui-mode/scene-ui-palette-panel.tsx +152 -0
- package/src/scene-ui-mode/scene-ui-timeline-tab.tsx +29 -0
- package/src/scene-ui-mode/ui-prefab-dialog-host.tsx +74 -0
- package/src/scene-ui-mode/ui-prefab-hierarchy-menu.ts +49 -0
- package/src/scene-ui-mode/ui-prefab-io.ts +76 -0
- package/src/script-loader.ts +143 -0
- package/src/storage/fsa-storage.ts +55 -0
- package/src/storage/handle-storage.ts +150 -0
- package/src/storage/handle-store.ts +116 -0
- package/src/storage/http-storage.ts +137 -0
- package/src/storage/index.ts +147 -0
- package/src/storage/mem-storage.ts +135 -0
- package/src/storage/opfs-storage.ts +25 -0
- package/src/storage/paths.ts +26 -0
- package/src/storage/seed.ts +106 -0
- package/src/storage/types.ts +110 -0
- package/src/tool-loader.ts +291 -0
- package/src/ui-editor/inspector.tsx +563 -0
- package/src/ui-editor/node-ops.ts +266 -0
- package/src/ui-editor/overlay.tsx +380 -0
- package/src/ui-editor/palette.tsx +114 -0
- package/src/ui-editor/react-store.ts +78 -0
- package/src/ui-editor/timeline-panel.tsx +443 -0
- package/src/ui-editor/ui-edit-store.ts +552 -0
- package/src/ui-editor/ui-prefab.ts +95 -0
- package/src/ui-source/inspect.ts +544 -0
- package/src/ui-source/oid-transform.ts +133 -0
- package/src/ui-source/source-write-backend.ts +203 -0
- package/src/ui-source/tailwind-map.ts +174 -0
- package/src/ui-source/writer.ts +1175 -0
- package/src/widgets/index.ts +73 -0
- package/template/.claude/skills/editor/SKILL.md +252 -0
- package/template/CLAUDE.md +512 -0
- package/template/LICENSE +30 -0
- package/template/emit-schemas.ts +42 -0
- package/template/index.html +17 -0
- package/template/package.json +53 -0
- package/template/project.json +9 -0
- package/template/public/audio/ambient-arena.wav +0 -0
- package/template/public/audio/ambient-music.wav +0 -0
- package/template/public/audio/ambient-nature.wav +0 -0
- package/template/public/audio/ambient-wind.wav +0 -0
- package/template/public/inputmaps/default.inputmap.json +41 -0
- package/template/public/jsm/libs/draco/gltf/draco_decoder.js +33 -0
- package/template/public/jsm/libs/draco/gltf/draco_decoder.wasm +0 -0
- package/template/public/jsm/libs/draco/gltf/draco_encoder.js +33 -0
- package/template/public/jsm/libs/draco/gltf/draco_wasm_wrapper.js +116 -0
- package/template/public/scenes/default.vscn.json +43 -0
- package/template/public/scenes/untitled.vscn.json +29 -0
- package/template/public/textures/waternormals.jpg +0 -0
- package/template/server/colyseus-setup.ts +85 -0
- package/template/server/main.ts +34 -0
- package/template/server/rooms/arena-room.ts +420 -0
- package/template/server/rooms/game-room.ts +162 -0
- package/template/server/rooms.ts +15 -0
- package/template/server/tsconfig.json +19 -0
- package/template/src/data/README.md +40 -0
- package/template/src/data/assets.ts +31 -0
- package/template/src/data/tuning.data.json +5 -0
- package/template/src/data/tuning.schema.json +21 -0
- package/template/src/data/tuning.schema.ts +34 -0
- package/template/src/data/tuning.ts +24 -0
- package/template/src/main.ts +157 -0
- package/template/src/scripts/components/data-spinner.ts +45 -0
- package/template/src/scripts/components/scene-camera.ts +40 -0
- package/template/src/scripts/main.ts +94 -0
- package/template/src/scripts/registry.ts +46 -0
- package/template/src/tools/README.md +14 -0
- package/template/src/tools/example.tool.tsx +91 -0
- package/template/src/tools/spin.tool.tsx +101 -0
- package/template/src/ui/Button.tsx +27 -0
- package/template/src/ui/DefaultHud.tsx +40 -0
- package/template/src/ui/KeyHint.tsx +25 -0
- package/template/src/ui/Panel.tsx +52 -0
- package/template/src/ui/RebindPanel.tsx +129 -0
- package/template/src/ui/Slider.tsx +28 -0
- package/template/src/ui/StatusDot.tsx +22 -0
- package/template/src/ui/Toggle.tsx +19 -0
- package/template/src/ui/game-state.tsx +37 -0
- package/template/src/ui/index.ts +7 -0
- package/template/src/ui/mount-default-hud.tsx +19 -0
- package/template/src/ui/mount-ui.ts +15 -0
- package/template/tsconfig.json +36 -0
- package/template/validate-asset-content.ts +78 -0
- package/template/validate-manifest.ts +55 -0
- package/template/validate-scenes.ts +211 -0
- package/template/vgai.game.json +21 -0
- package/template/vite.config.ts +128 -0
- package/vite-plugin-example-assets.ts +60 -0
- package/vite-plugin-game-globals.ts +46 -0
- package/vite-plugin-game-static.ts +71 -0
- package/vite-plugin-project-game-static.ts +84 -0
- package/vite-plugin-react-game-static.ts +98 -0
- package/vite-plugin-react-world-provider.ts +81 -0
- package/vite-plugin-react-world-runtime.ts +76 -0
- 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 }];
|