summer-engine 3.0.0 → 3.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +27 -5
- package/.codex-plugin/plugin.json +28 -6
- package/.cursor-plugin/plugin.json +27 -5
- package/.factory-plugin/plugin.json +27 -5
- package/.opencode/plugins/summer.js +1 -1
- package/CHANGELOG.md +120 -0
- package/README.md +22 -7
- package/assets/audit/scene_audit.gd +4136 -0
- package/assets/seeing/seeing_probe.gd +1873 -0
- package/dist/cli/commands/setup.js +6 -19
- package/dist/cli/commands/skills.js +57 -32
- package/dist/core/api-client.d.ts +9 -1
- package/dist/core/api-client.js +21 -4
- package/dist/core/capabilities/agent-playbook.js +46 -0
- package/dist/core/capabilities/audit/accept.d.ts +55 -0
- package/dist/core/capabilities/audit/accept.js +147 -0
- package/dist/core/capabilities/audit/args.d.ts +95 -0
- package/dist/core/capabilities/audit/args.js +89 -0
- package/dist/core/capabilities/audit/audit.d.ts +56 -0
- package/dist/core/capabilities/audit/audit.js +287 -0
- package/dist/core/capabilities/audit/frame.d.ts +14 -0
- package/dist/core/capabilities/audit/frame.js +35 -0
- package/dist/core/capabilities/audit/gaps.d.ts +96 -0
- package/dist/core/capabilities/audit/gaps.js +257 -0
- package/dist/core/capabilities/audit/judge.d.ts +162 -0
- package/dist/core/capabilities/audit/judge.js +1134 -0
- package/dist/core/capabilities/audit/math.d.ts +110 -0
- package/dist/core/capabilities/audit/math.js +263 -0
- package/dist/core/capabilities/audit/summary.d.ts +74 -0
- package/dist/core/capabilities/audit/summary.js +212 -0
- package/dist/core/capabilities/board.d.ts +31 -0
- package/dist/core/capabilities/board.js +43 -0
- package/dist/core/capabilities/camera-view.d.ts +4 -2
- package/dist/core/capabilities/camera-view.js +6 -3
- package/dist/core/capabilities/connect-signal.d.ts +85 -0
- package/dist/core/capabilities/connect-signal.js +285 -0
- package/dist/core/capabilities/engine-fallbacks.d.ts +5 -0
- package/dist/core/capabilities/engine-fallbacks.js +5 -0
- package/dist/core/capabilities/engine-ops.d.ts +5 -1
- package/dist/core/capabilities/engine-ops.js +0 -3
- package/dist/core/capabilities/engine-receipt.d.ts +12 -0
- package/dist/core/capabilities/engine-receipt.js +26 -0
- package/dist/core/capabilities/file-read.d.ts +64 -0
- package/dist/core/capabilities/file-read.js +325 -0
- package/dist/core/capabilities/game-task-plan.js +4 -2
- package/dist/core/capabilities/grep.d.ts +60 -0
- package/dist/core/capabilities/grep.js +190 -0
- package/dist/core/capabilities/inspect-node.d.ts +56 -0
- package/dist/core/capabilities/inspect-node.js +225 -0
- package/dist/core/capabilities/inspect-resource.d.ts +61 -0
- package/dist/core/capabilities/inspect-resource.js +376 -0
- package/dist/core/capabilities/math3d.d.ts +28 -0
- package/dist/core/capabilities/math3d.js +88 -0
- package/dist/core/capabilities/motion-generation.d.ts +62 -0
- package/dist/core/capabilities/motion-generation.js +138 -0
- package/dist/core/capabilities/placement-batch.d.ts +102 -0
- package/dist/core/capabilities/placement-batch.js +483 -0
- package/dist/core/capabilities/placement-math.d.ts +70 -0
- package/dist/core/capabilities/placement-math.js +209 -0
- package/dist/core/capabilities/placement-script.d.ts +42 -0
- package/dist/core/capabilities/placement-script.js +1096 -0
- package/dist/core/capabilities/placement.d.ts +485 -0
- package/dist/core/capabilities/placement.js +1731 -0
- package/dist/core/capabilities/project-context.d.ts +33 -2
- package/dist/core/capabilities/project-context.js +87 -4
- package/dist/core/capabilities/replace-node.d.ts +91 -0
- package/dist/core/capabilities/replace-node.js +423 -0
- package/dist/core/capabilities/runtime-control.d.ts +20 -4
- package/dist/core/capabilities/runtime-control.js +67 -2
- package/dist/core/capabilities/scene-batch.d.ts +63 -0
- package/dist/core/capabilities/scene-batch.js +394 -0
- package/dist/core/capabilities/scene-readback.d.ts +36 -0
- package/dist/core/capabilities/scene-readback.js +54 -0
- package/dist/core/capabilities/seeing/args.d.ts +348 -0
- package/dist/core/capabilities/seeing/args.js +178 -0
- package/dist/core/capabilities/seeing/candidates.d.ts +127 -0
- package/dist/core/capabilities/seeing/candidates.js +282 -0
- package/dist/core/capabilities/seeing/math.d.ts +134 -0
- package/dist/core/capabilities/seeing/math.js +269 -0
- package/dist/core/capabilities/seeing/probe.d.ts +62 -0
- package/dist/core/capabilities/seeing/probe.js +188 -0
- package/dist/core/capabilities/seeing/scoring.d.ts +235 -0
- package/dist/core/capabilities/seeing/scoring.js +639 -0
- package/dist/core/capabilities/seeing/seeing.d.ts +233 -0
- package/dist/core/capabilities/seeing/seeing.js +1341 -0
- package/dist/core/capabilities/seeing/shot-store.d.ts +52 -0
- package/dist/core/capabilities/seeing/shot-store.js +204 -0
- package/dist/core/capabilities/surface-snap-mesh.d.ts +88 -0
- package/dist/core/capabilities/surface-snap-mesh.js +425 -0
- package/dist/core/capabilities/surface-snap.d.ts +77 -0
- package/dist/core/capabilities/surface-snap.js +461 -0
- package/dist/core/capabilities/tool-dispatch.js +231 -72
- package/dist/core/capabilities/tscn.d.ts +85 -0
- package/dist/core/capabilities/tscn.js +391 -0
- package/dist/core/capabilities/world-snapshot.d.ts +58 -0
- package/dist/core/capabilities/world-snapshot.js +150 -0
- package/dist/core/capability-skew.d.ts +5 -5
- package/dist/core/capability-skew.js +14 -6
- package/dist/core/headless/worker-engine-client.d.ts +1 -1
- package/dist/core/headless/worker-engine-client.js +2 -2
- package/dist/core/library-read.d.ts +37 -5
- package/dist/core/library-read.js +264 -33
- package/dist/core/library-search.d.ts +2 -2
- package/dist/core/skills-registry.d.ts +9 -1
- package/dist/core/skills-registry.js +25 -2
- package/dist/installer/agent-config.d.ts +9 -4
- package/dist/installer/agent-config.js +194 -392
- package/dist/installer/agent-table.d.ts +119 -0
- package/dist/installer/agent-table.js +619 -0
- package/dist/installer/setup.js +4 -2
- package/dist/installer/skill-locations.d.ts +8 -17
- package/dist/installer/skill-locations.js +22 -101
- package/dist/installer/version-check.d.ts +5 -1
- package/dist/installer/version-check.js +38 -28
- package/dist/mcp/server.js +9 -3
- package/dist/mcp/tools/asset-tools.js +21 -3
- package/dist/mcp/tools/audit-tools.d.ts +23 -0
- package/dist/mcp/tools/audit-tools.js +60 -0
- package/dist/mcp/tools/debug-tools.js +3 -1
- package/dist/mcp/tools/file-tools.js +17 -4
- package/dist/mcp/tools/generate-tools.js +74 -66
- package/dist/mcp/tools/library-tools.js +2 -0
- package/dist/mcp/tools/perception-tools.js +6 -19
- package/dist/mcp/tools/placement-tools.d.ts +24 -0
- package/dist/mcp/tools/placement-tools.js +91 -0
- package/dist/mcp/tools/project-tools.js +18 -15
- package/dist/mcp/tools/scene-tools.js +71 -83
- package/dist/mcp/tools/seeing-tools.d.ts +24 -0
- package/dist/mcp/tools/seeing-tools.js +64 -0
- package/dist/mcp/tools/spatial-tools.js +17 -9
- package/dist/mcp/tools/visual-tools.js +59 -7
- package/gemini-extension.json +26 -4
- package/integrations/README.md +48 -19
- package/integrations/amp/README.md +12 -0
- package/integrations/amp/manifest-target.json +3 -0
- package/integrations/antigravity/README.md +12 -0
- package/integrations/antigravity/manifest-target.json +3 -0
- package/integrations/claude-desktop/README.md +12 -0
- package/integrations/claude-desktop/manifest-target.json +3 -0
- package/integrations/cline/README.md +8 -9
- package/integrations/cline-cli/README.md +12 -0
- package/integrations/cline-cli/manifest-target.json +3 -0
- package/integrations/copilot-jetbrains/README.md +12 -0
- package/integrations/copilot-jetbrains/manifest-target.json +3 -0
- package/integrations/crush/README.md +12 -0
- package/integrations/crush/manifest-target.json +3 -0
- package/integrations/github-copilot/README.md +8 -8
- package/integrations/goose/README.md +12 -0
- package/integrations/goose/manifest-target.json +3 -0
- package/integrations/grok-build/README.md +12 -0
- package/integrations/grok-build/manifest-target.json +3 -0
- package/integrations/hermes/README.md +12 -0
- package/integrations/hermes/manifest-target.json +3 -0
- package/integrations/junie/README.md +12 -0
- package/integrations/junie/manifest-target.json +3 -0
- package/integrations/kilo-code/README.md +6 -10
- package/integrations/kimi-code/README.md +12 -0
- package/integrations/kimi-code/manifest-target.json +3 -0
- package/integrations/kiro/README.md +12 -0
- package/integrations/kiro/manifest-target.json +3 -0
- package/integrations/lm-studio/README.md +8 -8
- package/integrations/mistral-vibe/README.md +12 -0
- package/integrations/mistral-vibe/manifest-target.json +3 -0
- package/integrations/opencode/README.md +9 -12
- package/integrations/qoder/README.md +12 -0
- package/integrations/qoder/manifest-target.json +3 -0
- package/integrations/qwen-code/README.md +12 -0
- package/integrations/qwen-code/manifest-target.json +3 -0
- package/integrations/roo-code/README.md +8 -7
- package/integrations/rovo-dev/README.md +12 -0
- package/integrations/rovo-dev/manifest-target.json +3 -0
- package/integrations/trae/README.md +12 -0
- package/integrations/trae/manifest-target.json +3 -0
- package/integrations/visual-studio/README.md +12 -0
- package/integrations/visual-studio/manifest-target.json +3 -0
- package/integrations/vscode-copilot/README.md +9 -9
- package/integrations/warp/README.md +12 -0
- package/integrations/warp/manifest-target.json +3 -0
- package/integrations/windsurf/README.md +8 -9
- package/integrations/zed/README.md +12 -0
- package/integrations/zed/manifest-target.json +3 -0
- package/library/references/mcp-tools-reference/mcp-tools-reference.md +59 -15
- package/library/references/mcp-tools-reference/resource.yaml +1 -1
- package/library/references/summer-folder/resource.yaml +1 -1
- package/library/references/summer-folder/summer-folder.md +1 -1
- package/library/skills/3d-lighting/SKILL.md +1 -1
- package/library/skills/README.md +264 -0
- package/library/skills/adaptive-music/SKILL.md +1 -1
- package/library/skills/agent-playtesting/SKILL.md +1 -1
- package/library/skills/ai-characters-in-games/SKILL.md +220 -0
- package/library/skills/ai-characters-in-games/resource.yaml +30 -0
- package/library/skills/ambient-bed/SKILL.md +1 -1
- package/library/skills/animated-loop/SKILL.md +1 -1
- package/library/skills/animation-tree/SKILL.md +1 -1
- package/library/skills/art-direction/SKILL.md +1 -1
- package/library/skills/asset-strategy/SKILL.md +2 -1
- package/library/skills/asset-strategy/resource.yaml +2 -1
- package/library/skills/audio-direction/SKILL.md +1 -1
- package/library/skills/auto-fire-targeting/SKILL.md +1 -1
- package/library/skills/brainstorm-game/SKILL.md +1 -1
- package/library/skills/brainstorming/SKILL.md +1 -1
- package/library/skills/browse-templates/SKILL.md +3 -3
- package/library/skills/browse-templates/resource.yaml +1 -1
- package/library/skills/camera-rigs/SKILL.md +128 -0
- package/library/skills/camera-rigs/resource.yaml +29 -0
- package/library/skills/celeste-momentum-platforming/SKILL.md +1 -1
- package/library/skills/character-animation-wiring/SKILL.md +1 -1
- package/library/skills/character-model/SKILL.md +1 -1
- package/library/skills/character-movement/SKILL.md +326 -0
- package/library/skills/character-movement/references/default-third-person-controller.gd +201 -0
- package/library/skills/character-movement/references/default-third-person-verification-probe.gd +154 -0
- package/library/skills/character-movement/references/movement-3d.md +136 -0
- package/library/skills/character-movement/references/platformer-feel.md +83 -0
- package/library/skills/character-movement/resource.yaml +40 -0
- package/library/skills/character-portrait/SKILL.md +1 -1
- package/library/skills/cinematic-cutscene/SKILL.md +1 -1
- package/library/skills/combat-basics/SKILL.md +159 -0
- package/library/skills/combat-basics/references/projectiles-and-aoe.md +100 -0
- package/library/skills/combat-basics/resource.yaml +34 -0
- package/library/skills/concept-art/SKILL.md +1 -1
- package/library/skills/create-asset-sheet/SKILL.md +1 -1
- package/library/skills/debug/SKILL.md +1 -1
- package/library/skills/debugging-game-feel/SKILL.md +1 -1
- package/library/skills/design-level/SKILL.md +1 -1
- package/library/skills/design-mechanic/SKILL.md +1 -1
- package/library/skills/design-npc/SKILL.md +1 -1
- package/library/skills/diagnosing-perf-regressions/SKILL.md +1 -1
- package/library/skills/dispatching-parallel-agents/SKILL.md +1 -1
- package/library/skills/driving-the-editor-ui/SKILL.md +1 -1
- package/library/skills/environment-kit/SKILL.md +1 -1
- package/library/skills/export-and-ship/SKILL.md +1 -1
- package/library/skills/fabricating-assets/SKILL.md +1 -1
- package/library/skills/facial-and-lipsync/SKILL.md +1 -1
- package/library/skills/fps-controller/SKILL.md +9 -7
- package/library/skills/fps-controller/resource.yaml +4 -1
- package/library/skills/game-feel/SKILL.md +1 -1
- package/library/skills/gameskill/SKILL.md +1 -1
- package/library/skills/gdscript-patterns/SKILL.md +1 -1
- package/library/skills/generate-motion/SKILL.md +9 -9
- package/library/skills/generate-motion/resource.yaml +2 -1
- package/library/skills/godogen-visual-proof-game-generation/SKILL.md +1 -1
- package/library/skills/headless-scripting/SKILL.md +1 -1
- package/library/skills/in-game-purchases/SKILL.md +254 -0
- package/library/skills/in-game-purchases/resource.yaml +29 -0
- package/library/skills/instantiate-asset-pack/SKILL.md +1 -1
- package/library/skills/investigating-bugs/SKILL.md +1 -1
- package/library/skills/lumera-single-image-scene-reconstruction/SKILL.md +1 -1
- package/library/skills/make-game/SKILL.md +25 -3
- package/library/skills/make-game/references/2d-platformer-polish.md +93 -0
- package/library/skills/make-game/references/2d-platformer.md +117 -0
- package/library/skills/make-game/references/2d-rpg-content.md +234 -0
- package/library/skills/make-game/references/2d-rpg.md +241 -0
- package/library/skills/make-game/references/2d-survivors-progression.md +201 -0
- package/library/skills/make-game/references/2d-survivors.md +237 -0
- package/library/skills/make-game/references/3d-fps-systems.md +216 -0
- package/library/skills/make-game/references/3d-fps.md +177 -0
- package/library/skills/make-game/references/3d-horror-systems.md +186 -0
- package/library/skills/make-game/references/3d-horror.md +201 -0
- package/library/skills/make-game/references/3d-third-person-systems.md +220 -0
- package/library/skills/make-game/references/3d-third-person.md +215 -0
- package/library/skills/make-game/resource.yaml +6 -1
- package/library/skills/multiplayer/SKILL.md +150 -0
- package/library/skills/multiplayer/resource.yaml +33 -0
- package/library/skills/multiplayer-movement/SKILL.md +510 -0
- package/library/skills/multiplayer-movement/files.md +501 -0
- package/library/skills/multiplayer-movement/resource.yaml +30 -0
- package/library/skills/multiplayer-project/SKILL.md +521 -0
- package/library/skills/multiplayer-project/files.md +448 -0
- package/library/skills/multiplayer-project/resource.yaml +36 -0
- package/library/skills/multiplayer-publish/SKILL.md +229 -0
- package/library/skills/multiplayer-publish/export-errors.md +89 -0
- package/library/skills/multiplayer-publish/resource.yaml +28 -0
- package/library/skills/multiplayer-state/SKILL.md +438 -0
- package/library/skills/multiplayer-state/files.md +592 -0
- package/library/skills/multiplayer-state/resource.yaml +35 -0
- package/library/skills/multiplayer-testing/SKILL.md +236 -0
- package/library/skills/multiplayer-testing/resource.yaml +39 -0
- package/library/skills/music-track/SKILL.md +1 -1
- package/library/skills/navigate-summer/SKILL.md +1 -1
- package/library/skills/new-project/SKILL.md +1 -1
- package/library/skills/organic-model/SKILL.md +1 -1
- package/library/skills/pixel-art/SKILL.md +1 -1
- package/library/skills/play/SKILL.md +1 -1
- package/library/skills/playtesting-a-feature/SKILL.md +1 -1
- package/library/skills/procedural-animation/SKILL.md +2 -2
- package/library/skills/procedural-animation/resource.yaml +1 -1
- package/library/skills/prop-model/SKILL.md +1 -1
- package/library/skills/psx-retro-rendering/SKILL.md +1 -1
- package/library/skills/realtime-wet-surfaces/SKILL.md +2 -2
- package/library/skills/remote-deploy/SKILL.md +1 -1
- package/library/skills/retarget/SKILL.md +1 -1
- package/library/skills/running-in-the-cloud/SKILL.md +1 -1
- package/library/skills/save-load/SKILL.md +156 -0
- package/library/skills/save-load/resource.yaml +27 -0
- package/library/skills/scene-composition/SKILL.md +1 -1
- package/library/skills/scene-hierarchy-design/SKILL.md +1 -1
- package/library/skills/scene-scripting/SKILL.md +1 -1
- package/library/skills/scene-to-level/SKILL.md +1 -1
- package/library/skills/skill-create/SKILL.md +1 -1
- package/library/skills/skill-improve/SKILL.md +1 -1
- package/library/skills/skill-test/SKILL.md +1 -1
- package/library/skills/skintokens-auto-rigging/SKILL.md +1 -1
- package/library/skills/skybox-panorama/SKILL.md +1 -1
- package/library/skills/sound-effect/SKILL.md +1 -1
- package/library/skills/spatial-placement/SKILL.md +134 -3
- package/library/skills/spatial-placement/references/kit-placement-tools.md +364 -0
- package/library/skills/spatial-placement/resource.yaml +16 -2
- package/library/skills/sprite-sheet/SKILL.md +1 -1
- package/library/skills/summer-analytics/SKILL.md +147 -0
- package/library/skills/summer-analytics/resource.yaml +28 -0
- package/library/skills/summer-friends/SKILL.md +226 -0
- package/library/skills/summer-friends/resource.yaml +28 -0
- package/library/skills/summer-leaderboards/SKILL.md +231 -0
- package/library/skills/summer-leaderboards/resource.yaml +30 -0
- package/library/skills/summer-match-results/SKILL.md +286 -0
- package/library/skills/summer-match-results/resource.yaml +32 -0
- package/library/skills/summer-matchmaking/SKILL.md +301 -0
- package/library/skills/summer-matchmaking/resource.yaml +32 -0
- package/library/skills/summer-parties/SKILL.md +253 -0
- package/library/skills/summer-parties/resource.yaml +30 -0
- package/library/skills/summer-player-data/SKILL.md +322 -0
- package/library/skills/summer-player-data/resource.yaml +30 -0
- package/library/skills/summer-store/SKILL.md +271 -0
- package/library/skills/summer-store/resource.yaml +29 -0
- package/library/skills/summer-world-chat/SKILL.md +195 -0
- package/library/skills/summer-world-chat/resource.yaml +30 -0
- package/library/skills/summer-world-saves/SKILL.md +284 -0
- package/library/skills/summer-world-saves/resource.yaml +29 -0
- package/library/skills/text-to-motion/SKILL.md +99 -0
- package/library/skills/text-to-motion/prompts-and-controls.md +34 -0
- package/library/skills/text-to-motion/resource.yaml +41 -0
- package/library/skills/text-to-motion/rigging-for-text-motion.md +54 -0
- package/library/skills/tileable-texture/SKILL.md +1 -1
- package/library/skills/trailer-shot/SKILL.md +1 -1
- package/library/skills/tune-performance/SKILL.md +1 -1
- package/library/skills/ui-basics/SKILL.md +10 -1
- package/library/skills/ui-graphics/SKILL.md +1 -1
- package/library/skills/use-widget-asset/SKILL.md +1 -1
- package/library/skills/using-summer/SKILL.md +2 -2
- package/library/skills/using-summer/resource.yaml +2 -2
- package/library/skills/vehicle-model/SKILL.md +1 -1
- package/library/skills/verification-before-completion/SKILL.md +1 -1
- package/library/skills/verifying-scenes/SKILL.md +76 -2
- package/library/skills/verifying-scenes/resource.yaml +13 -2
- package/library/skills/vfx-dissolve/SKILL.md +1 -1
- package/library/skills/vfx-fire/SKILL.md +1 -1
- package/library/skills/vfx-hit-spark/SKILL.md +1 -1
- package/library/skills/vfx-lightning/SKILL.md +1 -1
- package/library/skills/vfx-magic-glow/SKILL.md +1 -1
- package/library/skills/vfx-muzzle-flash/SKILL.md +1 -1
- package/library/skills/vfx-smoke/SKILL.md +1 -1
- package/library/skills/vfx-water-ripple/SKILL.md +1 -1
- package/library/skills/voice-line/SKILL.md +1 -1
- package/library/skills/web-and-touch-input/SKILL.md +38 -0
- package/library/skills/web-and-touch-input/resource.yaml +28 -0
- package/library/skills/web-compatibility-look/SKILL.md +62 -0
- package/library/skills/web-compatibility-look/resource.yaml +30 -0
- package/library/skills/world-building-3d/SKILL.md +8 -1
- package/library/skills/world-building-3d/references/spatial-tools.md +1 -1
- package/library/skills/world-building-3d/resource.yaml +2 -1
- package/library/skills/writing-plans/SKILL.md +1 -1
- package/library/skills/writing-skills/SKILL.md +1 -1
- package/library/templates/3d-lan-multiplayer-starter/resource.yaml +2 -2
- package/library/tools/attach-to-surface/resource.yaml +142 -0
- package/library/tools/batch/resource.yaml +9 -3
- package/library/tools/connect-ports/resource.yaml +120 -0
- package/library/tools/connect-signal/resource.yaml +3 -2
- package/library/tools/debug-views/resource.yaml +94 -0
- package/library/tools/frame-nodes/resource.yaml +104 -0
- package/library/tools/frame-shot/resource.yaml +134 -0
- package/library/tools/generate-motion/resource.yaml +44 -9
- package/library/tools/get-board/resource.yaml +50 -0
- package/library/tools/get-project-context/resource.yaml +13 -2
- package/library/tools/grep/resource.yaml +77 -0
- package/library/tools/inspect-asset/resource.yaml +71 -0
- package/library/tools/inspect-node/resource.yaml +9 -2
- package/library/tools/inspect-resource/resource.yaml +16 -5
- package/library/tools/instantiate-scene/resource.yaml +27 -2
- package/library/tools/measure/resource.yaml +96 -0
- package/library/tools/place-adjacent/resource.yaml +113 -0
- package/library/tools/play/resource.yaml +15 -2
- package/library/tools/raycast/resource.yaml +101 -0
- package/library/tools/read-file/resource.yaml +36 -2
- package/library/tools/read-library/resource.yaml +2 -2
- package/library/tools/repeat-along/resource.yaml +129 -0
- package/library/tools/replace-node/resource.yaml +11 -5
- package/library/tools/scene-audit/resource.yaml +144 -0
- package/library/tools/screenshot/resource.yaml +13 -2
- package/library/tools/search-assets/resource.yaml +18 -0
- package/library/tools/shot-sheet/resource.yaml +110 -0
- package/library/tools/snap-to-surface/resource.yaml +4 -1
- package/library/tools/world-snapshot/resource.yaml +24 -3
- package/library/tools/zoom/resource.yaml +105 -0
- package/package.json +3 -3
- package/registry/generated/aliases.json +17 -3
- package/registry/generated/counts.json +3 -3
- package/registry/generated/gemini-extension.json +26 -4
- package/registry/generated/index.json +2154 -469
- package/registry/generated/marketplace.claude.json +2 -2
- package/registry/generated/plugin.claude.json +27 -5
- package/registry/generated/plugin.codex.json +28 -6
- package/registry/generated/plugin.cursor.json +27 -5
- package/registry/generated/plugin.factory.json +27 -5
- package/registry/generated/skills-index.md +264 -0
- package/registry/generated/skills-registry.json +901 -206
- package/registry/generated/templates-registry.json +2 -2
- package/library/skills/host-authoritative-state/SKILL.md +0 -332
- package/library/skills/host-authoritative-state/resource.yaml +0 -28
- package/library/skills/peer-to-peer-multiplayer/SKILL.md +0 -373
- package/library/skills/peer-to-peer-multiplayer/resource.yaml +0 -28
- package/library/skills/setup-multiplayer/SKILL.md +0 -336
- package/library/skills/setup-multiplayer/resource.yaml +0 -28
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# 2D Survivors - Progression
|
|
2
|
+
|
|
3
|
+
The systems layer on top of `2d-survivors.md`: enemy roster, wave-driven
|
|
4
|
+
spawning, elites, level-up upgrades, the between-wave shop, weapon evolution,
|
|
5
|
+
and characters. Build these only after the core loop (move + auto-fire + chase)
|
|
6
|
+
runs.
|
|
7
|
+
|
|
8
|
+
## Enemy roster (one script, many behaviors)
|
|
9
|
+
|
|
10
|
+
All types share `enemy_base.gd` (Area2D, "enemies" group), configured through
|
|
11
|
+
`setup(type, wave)` - no per-type scenes. HP/speed scale with wave number:
|
|
12
|
+
|
|
13
|
+
| Type | Behavior | Health | Speed | Special |
|
|
14
|
+
|---|---|---|---|---|
|
|
15
|
+
| Walker | chase + wobble | 15 + wave*8 | 55 + wave*2 | standard horde unit |
|
|
16
|
+
| Runner | fast chase | 8 + wave*4 | 110 + wave*3 | 0.7x scale |
|
|
17
|
+
| Tank | slow, big | 50 + wave*15 | 35 + wave*1 | 1.5x scale, 3 XP |
|
|
18
|
+
| Boss | massive | 500 + wave*80 | 45 | 2.8x scale, 25 XP |
|
|
19
|
+
| Ranged | keeps distance | 12 + wave*5 | 50 + wave*1.5 | fires projectiles |
|
|
20
|
+
| Charger | telegraph + dash | 20 + wave*7 | 50 + wave*1.5 | 550px/s burst |
|
|
21
|
+
| Spawner | stays back | 80 + wave*20 | 30 | spawns 2-3 minions |
|
|
22
|
+
|
|
23
|
+
Behavior dispatch is a `match behavior:` in `_physics_process`. Give every
|
|
24
|
+
enemy a randomized wobble (`_wobble_phase = randf() * TAU`, per-enemy speed and
|
|
25
|
+
amplitude) so hordes look organic instead of converging in a straight line.
|
|
26
|
+
|
|
27
|
+
Charger is a 4-state machine: WALK -> TELEGRAPH (0.45s, within 320px trigger
|
|
28
|
+
range) -> CHARGE (0.4s at 550px/s in the locked direction) -> RECOVER (0.3s),
|
|
29
|
+
then 3.5s cooldown. The telegraph is what makes it fair - flash/scale the sprite
|
|
30
|
+
during it.
|
|
31
|
+
|
|
32
|
+
Knockback: on hit, set `_knockback_vel = (pos - source).normalized() * 180`,
|
|
33
|
+
decay with `move_toward(Vector2.ZERO, 900 * delta)` and add to movement.
|
|
34
|
+
|
|
35
|
+
### Hit feedback and death (non-negotiable)
|
|
36
|
+
|
|
37
|
+
`take_damage(amount, source_pos, is_crit)` must: flash the sprite, spawn a
|
|
38
|
+
floating damage number (bigger + colored on crit), spawn directional particles,
|
|
39
|
+
apply knockback, show the health bar. Death: `GameManager.add_kill()`,
|
|
40
|
+
`add_xp(xp_value)`, drop gold (rare health orb), particle burst, then disable
|
|
41
|
+
before animating out:
|
|
42
|
+
|
|
43
|
+
```gdscript
|
|
44
|
+
set_physics_process(false)
|
|
45
|
+
set_deferred("monitoring", false)
|
|
46
|
+
set_deferred("monitorable", false)
|
|
47
|
+
var tween := create_tween().set_parallel(true)
|
|
48
|
+
tween.tween_property(self, "scale", scale * 1.5, 0.1)
|
|
49
|
+
tween.tween_property(self, "modulate:a", 0.0, 0.1)
|
|
50
|
+
tween.chain().tween_callback(queue_free)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The `set_deferred` pair matters: freeing an Area2D mid-physics-callback without
|
|
54
|
+
disabling monitoring first throws errors.
|
|
55
|
+
|
|
56
|
+
## Spawner and wave data
|
|
57
|
+
|
|
58
|
+
The spawner reads a per-wave config and spawns groups on an interval, always
|
|
59
|
+
off-screen (650-900px from the player, clamped to arena bounds):
|
|
60
|
+
|
|
61
|
+
```gdscript
|
|
62
|
+
func _random_spawn_pos() -> Vector2:
|
|
63
|
+
var angle := randf() * TAU
|
|
64
|
+
var dist := randf_range(650.0, 900.0)
|
|
65
|
+
var pos := GameManager.player_node.global_position + Vector2.from_angle(angle) * dist
|
|
66
|
+
pos.x = clampf(pos.x, -ARENA_HALF_SIZE, ARENA_HALF_SIZE)
|
|
67
|
+
pos.y = clampf(pos.y, -ARENA_HALF_SIZE, ARENA_HALF_SIZE)
|
|
68
|
+
return pos
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Each WaveConfig: `enemy_weights: Dictionary`, `spawn_interval`, `group_size`,
|
|
72
|
+
`max_alive`, `elite_chance`, `boss: bool`, `hazards: int`, `duration`. Type
|
|
73
|
+
selection is weighted random over the dictionary. Hand-tune 20 waves; the ramp
|
|
74
|
+
that matters:
|
|
75
|
+
|
|
76
|
+
| Wave | Types | Interval | Group | Max alive | Elite % | Boss | Duration |
|
|
77
|
+
|---|---|---|---|---|---|---|---|
|
|
78
|
+
| 1 | walker | 2.5s | 2 | 25 | 0% | no | 20s |
|
|
79
|
+
| 3 | +runner | 2.0s | 4 | 45 | 0% | no | 20s |
|
|
80
|
+
| 5 | +charger | 1.7s | 5 | 65 | 3% | no | 22s |
|
|
81
|
+
| 8 | +tank +ranged | 1.3s | 8 | 100 | 6% | no | 22s |
|
|
82
|
+
| 10 | all 6 | 1.1s | 10 | 120 | 8% | YES | 30s |
|
|
83
|
+
| 15 | tank heavy | 0.75s | 15 | 190 | 13% | no | 28s |
|
|
84
|
+
| 20 | max density | 0.45s | 22 | 250 | 20% | YES | 30s |
|
|
85
|
+
|
|
86
|
+
Do NOT scale difficulty by HP alone. The ramp is spawn rate + group size +
|
|
87
|
+
max alive + type diversity + elite chance + new mechanics at fixed waves.
|
|
88
|
+
Bosses spawn 3s into their combat phase via a scene-tree timer.
|
|
89
|
+
|
|
90
|
+
Arena hazards from wave 3+: spike traps (static) and fire pools (drift at
|
|
91
|
+
15-35px/s). Tick damage every 0.6s (6 + wave*1.5) to enemies in radius, half
|
|
92
|
+
damage to the player - positioning pressure for both sides.
|
|
93
|
+
|
|
94
|
+
## Elites
|
|
95
|
+
|
|
96
|
+
`elite_system.gd` is a static RefCounted helper applied at spawn time with
|
|
97
|
+
`elite_chance` probability. One of four modifiers, 3x XP:
|
|
98
|
+
|
|
99
|
+
- FAST: speed x1.8, HP x1.3, lightened color
|
|
100
|
+
- TANKY: HP x3, scale x1.3, contact damage x1.5, darkened
|
|
101
|
+
- SPLITTING: HP x1.5, green; on death spawns 2 walker copies at 0.6x scale,
|
|
102
|
+
0.3x HP (use `call_deferred("add_child", copy)` - you are inside a physics
|
|
103
|
+
callback)
|
|
104
|
+
- VAMPIRIC: HP x2; heals 8% max HP every 3s
|
|
105
|
+
|
|
106
|
+
Give elites a visible pulse tween so players can prioritize them.
|
|
107
|
+
|
|
108
|
+
## Two-track progression
|
|
109
|
+
|
|
110
|
+
Track 1 - mid-wave level-ups: on level-up the tree pauses, 3 random stat
|
|
111
|
+
upgrades from the pool are offered, each stackable to level 3. Also vacuum all
|
|
112
|
+
pickups (`attract()` on the "pickups" group) as a reward beat.
|
|
113
|
+
|
|
114
|
+
| Upgrade | Per level | Stat touched |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| Might | +15% damage | player_damage_mult |
|
|
117
|
+
| Haste | +12% attack speed | player_attack_speed_mult |
|
|
118
|
+
| Swiftness | +20 speed | player_speed |
|
|
119
|
+
| Vitality | +15 max HP (and heal 15) | player_max_health |
|
|
120
|
+
| Magnetism | +20 pickup range | player_pickup_range |
|
|
121
|
+
| Armor | +1 flat reduction | player_armor |
|
|
122
|
+
| Regeneration | +0.5 HP/s | player_regen |
|
|
123
|
+
| Critical Eye | +5% crit | player_crit_chance |
|
|
124
|
+
|
|
125
|
+
`get_random_choices(3)` filters out maxed upgrades, shuffles, takes 3.
|
|
126
|
+
`apply_upgrade` bumps level and writes the stat straight onto GameManager.
|
|
127
|
+
Armor applies as `maxi(1, amount - player_armor)` - always at least 1 damage.
|
|
128
|
+
|
|
129
|
+
Track 2 - between-wave shop: 4 random items, gold-priced, 4 tiers (Common /
|
|
130
|
+
Uncommon / Rare / Legendary). Tier odds shift with wave - by wave 16+:
|
|
131
|
+
15% Legendary / 30% Rare / 35% Uncommon / 20% Common; waves 1-5 have no
|
|
132
|
+
Legendary at all. Prices scale `cost_base * (1.0 + wave * 0.12)`; reroll costs
|
|
133
|
+
`2 + reroll_count + wave / 5` (escalates within a single shop visit).
|
|
134
|
+
|
|
135
|
+
Items are either stat dictionaries applied to GameManager
|
|
136
|
+
(`{"player_armor": 2}`) or weapon grants that call
|
|
137
|
+
`player.add_or_upgrade_weapon(id)`. The interesting ones trade off:
|
|
138
|
+
|
|
139
|
+
```gdscript
|
|
140
|
+
_add("glass_cannon", "Glass Cannon", 2, 40,
|
|
141
|
+
{"player_damage_mult": 0.35, "player_max_health": -25}, ["offense"])
|
|
142
|
+
_add("berserker_mask", "Berserker Mask", 2, 45,
|
|
143
|
+
{"player_damage_mult": 0.25, "player_attack_speed_mult": 0.20,
|
|
144
|
+
"player_armor": -3}, ["offense"])
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Do not skip the shop. Without a between-wave decision point the game is just
|
|
148
|
+
"survive longer"; trade-off items are what create builds.
|
|
149
|
+
|
|
150
|
+
## Weapon evolution
|
|
151
|
+
|
|
152
|
+
A base weapon evolves when the player owns it AND a specific catalyst upgrade
|
|
153
|
+
is maxed (level 3). Check after every upgrade pick and shop purchase; emit
|
|
154
|
+
`evolution_available` once per recipe:
|
|
155
|
+
|
|
156
|
+
| Evolved | Base weapon | Catalyst | Effect |
|
|
157
|
+
|---|---|---|---|
|
|
158
|
+
| Piercing Barrage | Projectile | Multi Shot | pierces 4+, faster bullets |
|
|
159
|
+
| Radiant Barrier | Orbital | Armor | 4 large orbitals + damage pulses |
|
|
160
|
+
| Supernova | Holy Nova | Might | bigger AoE + burning ground DOT |
|
|
161
|
+
|
|
162
|
+
Evolving replaces the node: `queue_free()` the old weapon, erase from the
|
|
163
|
+
player's `_weapons` dict, instantiate the evolved script, screen flash + zoom
|
|
164
|
+
pulse. DOT zones (Supernova) are a Polygon2D + tween loop: N tick callbacks at
|
|
165
|
+
0.5s intervals doing a group distance scan, then fade and free. Guard every
|
|
166
|
+
tick with `is_instance_valid` - the zone can outlive enemies and vice versa.
|
|
167
|
+
|
|
168
|
+
## Characters
|
|
169
|
+
|
|
170
|
+
Four picks before the run, each a starting weapon + stat override dictionary:
|
|
171
|
+
|
|
172
|
+
- Sentinel: projectile, no overrides (baseline)
|
|
173
|
+
- Berserker: projectile, damage_mult 1.4, speed 240, max HP 80
|
|
174
|
+
- Warden: orbital, max HP 150, armor 3, attack_speed 0.85
|
|
175
|
+
- Arcanist: nova, attack_speed 1.25, damage_mult 1.15, max HP 70
|
|
176
|
+
|
|
177
|
+
`GameManager.reset_run()` resets all stats to base, then applies
|
|
178
|
+
`selected_character.stat_overrides` via `set(key, value)` and fills health to
|
|
179
|
+
max. Character select is just 4 cards that set `selected_character` and load
|
|
180
|
+
the arena.
|
|
181
|
+
|
|
182
|
+
## UI stack (all procedural, all CanvasLayer, all process_mode ALWAYS)
|
|
183
|
+
|
|
184
|
+
- HUD: health + dash bars top-left, wave counter + timer top-center,
|
|
185
|
+
gold/kills/level top-right, full-width 6px XP bar at the bottom.
|
|
186
|
+
- LevelUpUI (layer 20): dark overlay, 3 cards, gamepad focus.
|
|
187
|
+
- ShopUI (layer 25): stats panel left, 4 tier-colored item cards, reroll +
|
|
188
|
+
next-wave buttons, "SOLD" placeholders, live stat refresh after purchase.
|
|
189
|
+
- PauseMenu / GameOverUI (layer 40): game over shows victory or death plus
|
|
190
|
+
time / kills / level / wave, retry + menu.
|
|
191
|
+
|
|
192
|
+
Retry = `get_tree().paused = false` THEN `reload_current_scene()`. Forgetting
|
|
193
|
+
the unpause is the classic frozen-retry bug.
|
|
194
|
+
|
|
195
|
+
## Audio integration points
|
|
196
|
+
|
|
197
|
+
Pooled AudioManager (16 global + 16 positional players, round-robin; all play
|
|
198
|
+
methods null-safe so missing streams never crash). Pitch variance 5% global /
|
|
199
|
+
10% positional prevents machine-gun repetition. Hook: enemy hit + death
|
|
200
|
+
(positional), pickup collect, level-up, shop purchase, weapon fire (-6dB, it
|
|
201
|
+
fires constantly), music on combat start with fade.
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# 2D Survivors - Core
|
|
2
|
+
|
|
3
|
+
Vampire Survivors-style auto-battler blueprint: arena, wave state machine, player,
|
|
4
|
+
and auto-firing weapons. The player only moves and dashes; weapons aim and fire
|
|
5
|
+
themselves. Waves, enemies, upgrades, shop, and evolution live in
|
|
6
|
+
`2d-survivors-progression.md`.
|
|
7
|
+
|
|
8
|
+
## Minimum playable base (build this first)
|
|
9
|
+
|
|
10
|
+
One arena scene: player CharacterBody2D with WASD movement, one auto-firing
|
|
11
|
+
projectile weapon, a handful of chasing enemies, and visible feedback on every
|
|
12
|
+
hit (flash + damage number). Prove "move, enemies chase, weapon kills them"
|
|
13
|
+
before waves, shop, or characters exist.
|
|
14
|
+
|
|
15
|
+
Two rules for every scene:
|
|
16
|
+
|
|
17
|
+
- Dynamic viewport: `display/window/stretch/mode = "canvas_items"`,
|
|
18
|
+
`stretch/aspect = "expand"`. All UI uses Control anchors, never hardcoded
|
|
19
|
+
pixel positions. Spawn enemies relative to the player (650-900px out), never
|
|
20
|
+
from screen coordinates.
|
|
21
|
+
- Playable slice, not tech demo: every hit needs shake/flash/particles/damage
|
|
22
|
+
number. A silent wave where numbers move but nothing feels alive fails.
|
|
23
|
+
|
|
24
|
+
## Architecture
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
res://
|
|
28
|
+
scenes/
|
|
29
|
+
main.tscn # main menu (run entry point)
|
|
30
|
+
levels/arena.tscn # THE gameplay scene (owns everything)
|
|
31
|
+
player/player.tscn
|
|
32
|
+
enemies/enemy.tscn # one scene reused for ALL enemy types
|
|
33
|
+
enemies/enemy_projectile.tscn
|
|
34
|
+
pickups/pickup.tscn # gold coin / health orb
|
|
35
|
+
weapons/projectile.tscn
|
|
36
|
+
ui/character_select.tscn
|
|
37
|
+
scripts/
|
|
38
|
+
autoloads/{game_manager,audio_manager}.gd
|
|
39
|
+
player/player.gd
|
|
40
|
+
enemies/{enemy_base,enemy_spawner,enemy_projectile,elite_system}.gd
|
|
41
|
+
weapons/{projectile_weapon,projectile,orbital_weapon,area_weapon}.gd
|
|
42
|
+
weapons/evolved/{piercing_projectile_weapon,radiant_orbital_weapon,supernova_weapon}.gd
|
|
43
|
+
systems/{wave_data,upgrade_data,upgrade_manager,weapon_evolution,
|
|
44
|
+
item_data,item_manager,item_registry,character_data,
|
|
45
|
+
character_registry,pickup,camera_effects,arena_hazard}.gd
|
|
46
|
+
ui/{main_menu,character_select_ui,hud,level_up_ui,shop_ui,
|
|
47
|
+
pause_menu,game_over_ui}.gd
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Two autoloads only: `GameManager` (single source of truth for ALL run state -
|
|
51
|
+
stats, gold, XP, health, wave, signals) and `AudioManager` (pooled SFX + music).
|
|
52
|
+
Every system reads and writes GameManager; UI listens to its signals.
|
|
53
|
+
|
|
54
|
+
## Physics layers
|
|
55
|
+
|
|
56
|
+
| Layer | Bit | Name |
|
|
57
|
+
|-------|-----|------|
|
|
58
|
+
| 1 | 1 | Player |
|
|
59
|
+
| 2 | 2 | Enemies |
|
|
60
|
+
| 3 | 4 | Pickups |
|
|
61
|
+
| 4 | 8 | PlayerProjectiles |
|
|
62
|
+
| 5 | 16 | Walls |
|
|
63
|
+
| 6 | 32 | EnemyProjectiles |
|
|
64
|
+
|
|
65
|
+
Wiring: Player body layer 1 mask 16 (walls only). Player Hurtbox layer 0 mask 34
|
|
66
|
+
(Enemies + EnemyProjectiles). PickupRange layer 0 mask 4. Enemies layer 2 mask 0.
|
|
67
|
+
Player projectiles layer 8 mask 2. Enemy projectiles layer 32 mask 1.
|
|
68
|
+
|
|
69
|
+
Input map: move_up/down/left/right (WASD + left stick), dash (Space + R2),
|
|
70
|
+
pause (Escape + Start).
|
|
71
|
+
|
|
72
|
+
## Wave state machine (arena.gd)
|
|
73
|
+
|
|
74
|
+
The arena scene owns the player, spawner, all managers, and all UI CanvasLayers.
|
|
75
|
+
Everything wires together via signals in `_ready()`. Per wave, 4 phases:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
PRE_WAVE (1.5s announce) -> COMBAT (timed, 20-30s) ->
|
|
79
|
+
WAVE_CLEAR (2.5s: kill all, vacuum gold, slow-mo) ->
|
|
80
|
+
SHOPPING (buy/reroll/next) -> next PRE_WAVE
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
20 waves total, bosses at 10 and 20, victory after 20, death = game over.
|
|
84
|
+
|
|
85
|
+
```gdscript
|
|
86
|
+
# arena.gd core
|
|
87
|
+
enum WaveState { PRE_WAVE, COMBAT, WAVE_CLEAR, SHOPPING }
|
|
88
|
+
|
|
89
|
+
func _physics_process(delta: float) -> void:
|
|
90
|
+
if GameManager.current_state != GameManager.State.PLAYING:
|
|
91
|
+
return
|
|
92
|
+
match _wave_state:
|
|
93
|
+
WaveState.PRE_WAVE:
|
|
94
|
+
_state_timer -= delta
|
|
95
|
+
if _state_timer <= 0.0: _start_combat()
|
|
96
|
+
WaveState.COMBAT:
|
|
97
|
+
_combat_time_remaining -= delta
|
|
98
|
+
hud.update_wave_timer(_combat_time_remaining)
|
|
99
|
+
if _combat_time_remaining <= 0.0: _start_wave_clear()
|
|
100
|
+
WaveState.WAVE_CLEAR:
|
|
101
|
+
_state_timer -= delta
|
|
102
|
+
if _state_timer <= 0.0: _start_shopping()
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Wave clear = spawner off, `kill_all_enemies()`, gold bonus (5 + wave*2),
|
|
106
|
+
flash + slow_motion(0.4, 0.8) + zoom pulse. GameManager pauses the tree for
|
|
107
|
+
LEVEL_UP / SHOPPING / PAUSED / GAME_OVER states and unpauses on PLAYING:
|
|
108
|
+
|
|
109
|
+
```gdscript
|
|
110
|
+
func change_state(new_state: State) -> void:
|
|
111
|
+
current_state = new_state
|
|
112
|
+
state_changed.emit(new_state)
|
|
113
|
+
match new_state:
|
|
114
|
+
State.PLAYING: get_tree().paused = false
|
|
115
|
+
State.PAUSED, State.LEVEL_UP, State.SHOPPING, State.GAME_OVER:
|
|
116
|
+
get_tree().paused = true
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Arena is a 6000x6000 field: background ColorRect, StaticBody2D walls on layer 5
|
|
120
|
+
at +-3020, Line2D border for visibility. Player collides with walls; enemies
|
|
121
|
+
do not (spawn logic clamps them).
|
|
122
|
+
|
|
123
|
+
## Player
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
Player (CharacterBody2D, layer 1, mask 16/Walls)
|
|
127
|
+
CollisionShape2D (CircleShape2D radius 14)
|
|
128
|
+
Body (Polygon2D, arrow shape, character color)
|
|
129
|
+
Hurtbox (Area2D, layer 0, mask 34)
|
|
130
|
+
CollisionShape2D (CircleShape2D radius 12)
|
|
131
|
+
PickupRange (Area2D, layer 0, mask 4)
|
|
132
|
+
CollisionShape2D (CircleShape2D radius 140, driven by GameManager stat)
|
|
133
|
+
WeaponMount (Node2D) # weapons attach here
|
|
134
|
+
Camera2D (smoothing on, speed 4.0)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Movement is trivial by design - all depth lives in the build:
|
|
138
|
+
|
|
139
|
+
```gdscript
|
|
140
|
+
func _move() -> void:
|
|
141
|
+
var input := Input.get_vector("move_left", "move_right", "move_up", "move_down")
|
|
142
|
+
velocity = input * GameManager.player_speed
|
|
143
|
+
move_and_slide()
|
|
144
|
+
if input.length_squared() > 0.01:
|
|
145
|
+
body.rotation = input.angle()
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Dash: 0.14s invincible burst at 650px/s, 1.2s cooldown, afterimage ghost
|
|
149
|
+
(Polygon2D copy, tween alpha to 0 over 0.2s) every 0.025s. Invincibility timer =
|
|
150
|
+
dash duration + 0.08. HUD shows the cooldown bar.
|
|
151
|
+
|
|
152
|
+
Contact damage: do NOT use `area_entered` signals. Check
|
|
153
|
+
`hurtbox.get_overlapping_areas()` manually each `_physics_process` and apply
|
|
154
|
+
damage with a 0.6s invincibility window. Signals miss continuous contact;
|
|
155
|
+
polling + i-frames is reliable.
|
|
156
|
+
|
|
157
|
+
Player also handles: regen tick (1s interval, `GameManager.player_regen` HP),
|
|
158
|
+
pickup magnet (attract pickups inside PickupRange), and a `_weapons` Dictionary
|
|
159
|
+
keyed by weapon id - `add_or_upgrade_weapon(id)` upgrades if owned, else
|
|
160
|
+
instantiates the weapon script onto WeaponMount.
|
|
161
|
+
|
|
162
|
+
## Weapons (auto-fire, no aiming)
|
|
163
|
+
|
|
164
|
+
All weapons are Node2D scripts on WeaponMount, firing from `_physics_process`.
|
|
165
|
+
Damage scales with `GameManager.player_damage_mult`, cooldowns divide by
|
|
166
|
+
`player_attack_speed_mult`, crits roll `player_crit_chance` for 2x. Three bases:
|
|
167
|
+
|
|
168
|
+
- Projectile: fires at nearest enemy within 800px (compare
|
|
169
|
+
`distance_squared_to` against 640000, never sqrt). Extra projectiles fan out
|
|
170
|
+
at 0.15 rad spread. If no target, retry in 0.1s instead of a full cooldown.
|
|
171
|
+
- Orbital: N shields orbit at radius 80, speed 3.5 rad/s. Per-enemy hit
|
|
172
|
+
cooldown Dictionary (0.4s keyed by instance id) so one orbit does not
|
|
173
|
+
multi-hit the same enemy every frame.
|
|
174
|
+
- Area (nova): every 3.0s damage all enemies within radius 120 (group scan +
|
|
175
|
+
distance_squared), show an expanding ring tween.
|
|
176
|
+
|
|
177
|
+
```gdscript
|
|
178
|
+
func _find_nearest_enemy() -> Node2D:
|
|
179
|
+
var nearest: Node2D = null
|
|
180
|
+
var best := MAX_TARGET_RANGE_SQ
|
|
181
|
+
for enemy in get_tree().get_nodes_in_group("enemies"):
|
|
182
|
+
if not is_instance_valid(enemy): continue
|
|
183
|
+
var d := global_position.distance_squared_to(enemy.global_position)
|
|
184
|
+
if d < best:
|
|
185
|
+
best = d
|
|
186
|
+
nearest = enemy
|
|
187
|
+
return nearest
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Group scan is fine to ~250 enemies (the max_alive cap); past that use spatial
|
|
191
|
+
hashing.
|
|
192
|
+
|
|
193
|
+
## Pickups and XP
|
|
194
|
+
|
|
195
|
+
XP is NOT a physical pickup - it auto-earns on kill (`GameManager.add_xp`,
|
|
196
|
+
amount = enemy xp_value). Gold and health orbs ARE physical Area2D pickups in
|
|
197
|
+
the "pickups" group with magnet behavior: once `attract()` is called (player
|
|
198
|
+
range overlap, or level-up vacuums all), accelerate toward the player at up to
|
|
199
|
+
650px/s and collect within 12px. Level-up curve:
|
|
200
|
+
`xp_needed = 5 + level*level + level*3`.
|
|
201
|
+
|
|
202
|
+
## Camera effects (the juice layer)
|
|
203
|
+
|
|
204
|
+
`camera_effects.gd` attaches as a child of the player's Camera2D. Provides
|
|
205
|
+
`screen_shake(intensity, duration)` (tween_method randomized offset decaying to
|
|
206
|
+
zero), `screen_flash(color, duration)`, `slow_motion(scale, duration)` (set
|
|
207
|
+
`Engine.time_scale`, restore via tween with `set_ignore_time_scale(true)`),
|
|
208
|
+
`zoom_pulse`, and a low-HP vignette shader that fades in below 35% health.
|
|
209
|
+
Register it on GameManager (`GameManager.camera_fx`) so every system can call it.
|
|
210
|
+
|
|
211
|
+
## Build order
|
|
212
|
+
|
|
213
|
+
1. project.godot: autoloads, input map, layer names, main scene, 1920x1080
|
|
214
|
+
canvas_items/expand stretch. Audio buses: Master, Music, SFX.
|
|
215
|
+
2. Scenes smallest-first: pickup, projectile, enemy_projectile, enemy, player,
|
|
216
|
+
character_select, main, arena.
|
|
217
|
+
3. Base loop: player move + dash, projectile weapon, walker enemies chasing.
|
|
218
|
+
4. GameManager stats + HUD (health, gold, XP bar, wave timer, kills, dash bar).
|
|
219
|
+
5. Wave state machine + spawner + wave data (progression file).
|
|
220
|
+
6. Level-up upgrades, shop, elites, evolution, characters (progression file).
|
|
221
|
+
|
|
222
|
+
## Traps
|
|
223
|
+
|
|
224
|
+
- Physics-based enemies. Never RigidBody2D or move_and_slide for enemies -
|
|
225
|
+
direct `position +=` on Area2D. Hundreds of colliding bodies kill perf;
|
|
226
|
+
enemies only need to chase, not collide with each other.
|
|
227
|
+
- Heavy enemy scenes. One Area2D + CollisionShape2D + Polygon2D + ProgressBar.
|
|
228
|
+
No AnimationPlayer, no particles, no per-enemy shader materials - you will
|
|
229
|
+
have 200+ alive.
|
|
230
|
+
- UI without `process_mode = ALWAYS`. The tree pauses during level-up, shop,
|
|
231
|
+
and game over; without ALWAYS, no button responds and the game soft-locks.
|
|
232
|
+
- Manual aim. The entire input surface is move + dash. Weapons auto-target;
|
|
233
|
+
adding aim breaks the genre.
|
|
234
|
+
- Editor-connected signals on runtime-instantiated scenes. Connect in `_ready()`
|
|
235
|
+
via code for enemies/projectiles/pickups - editor connections on templates
|
|
236
|
+
are fragile and undebuggable.
|
|
237
|
+
- On-screen spawns. Always 650-900px from the player, clamped to arena bounds.
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# 3D FPS - Systems
|
|
2
|
+
|
|
3
|
+
The combat and interaction layer on top of `3d-fps.md`: hitscan and
|
|
4
|
+
projectile shooting, the data-driven weapon system, raycast interaction, and
|
|
5
|
+
the HUD.
|
|
6
|
+
|
|
7
|
+
## Shooting: hitscan vs projectile
|
|
8
|
+
|
|
9
|
+
Hitscan (raycast) for rifles/pistols, projectiles for rockets/grenades.
|
|
10
|
+
Most weapons should be hitscan - instant, cheap, reliable.
|
|
11
|
+
|
|
12
|
+
### Hitscan
|
|
13
|
+
|
|
14
|
+
Uses the player's ShootRay (RayCast3D under Camera3D, target (0,0,-100),
|
|
15
|
+
mask 6 = Environment + Enemy):
|
|
16
|
+
|
|
17
|
+
```gdscript
|
|
18
|
+
func _shoot() -> void:
|
|
19
|
+
if not can_shoot:
|
|
20
|
+
return
|
|
21
|
+
can_shoot = false
|
|
22
|
+
shoot_cooldown = 0.1 # fire rate
|
|
23
|
+
shoot_ray.force_raycast_update()
|
|
24
|
+
if shoot_ray.is_colliding():
|
|
25
|
+
var collider := shoot_ray.get_collider()
|
|
26
|
+
_spawn_hit_effect(shoot_ray.get_collision_point(), shoot_ray.get_collision_normal())
|
|
27
|
+
if collider.has_method("take_damage"):
|
|
28
|
+
collider.take_damage(25.0)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`force_raycast_update()` matters: the ray otherwise reports last frame's
|
|
32
|
+
state and fast turns produce misses. Cooldown ticks down in `_process` and
|
|
33
|
+
re-arms `can_shoot`.
|
|
34
|
+
|
|
35
|
+
### Projectile
|
|
36
|
+
|
|
37
|
+
CharacterBody3D moved with `move_and_collide` (not RigidBody - you want
|
|
38
|
+
deterministic flight):
|
|
39
|
+
|
|
40
|
+
```gdscript
|
|
41
|
+
func _physics_process(delta: float) -> void:
|
|
42
|
+
time_alive += delta
|
|
43
|
+
if time_alive >= lifetime: # 5s despawn, always
|
|
44
|
+
queue_free()
|
|
45
|
+
return
|
|
46
|
+
velocity = direction * speed
|
|
47
|
+
if gravity_affected:
|
|
48
|
+
velocity.y -= 9.8 * delta
|
|
49
|
+
var collision := move_and_collide(velocity * delta)
|
|
50
|
+
if collision:
|
|
51
|
+
var collider := collision.get_collider()
|
|
52
|
+
if collider and collider.has_method("take_damage"):
|
|
53
|
+
collider.take_damage(damage)
|
|
54
|
+
_explode()
|
|
55
|
+
queue_free()
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Spawn from the camera, not the weapon model, so shots go where the
|
|
59
|
+
crosshair points:
|
|
60
|
+
|
|
61
|
+
```gdscript
|
|
62
|
+
proj.global_position = camera.global_position
|
|
63
|
+
proj.direction = -camera.global_transform.basis.z
|
|
64
|
+
get_tree().current_scene.add_child(proj)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Weapon system (data-driven)
|
|
68
|
+
|
|
69
|
+
WeaponData is a Resource; one .tres (or in-code instance) per weapon:
|
|
70
|
+
|
|
71
|
+
```gdscript
|
|
72
|
+
class_name WeaponData
|
|
73
|
+
extends Resource
|
|
74
|
+
|
|
75
|
+
@export var weapon_name: String = "Pistol"
|
|
76
|
+
@export var damage: float = 25.0
|
|
77
|
+
@export var fire_rate: float = 0.15 # seconds between shots
|
|
78
|
+
@export var reload_time: float = 1.5
|
|
79
|
+
@export var magazine_size: int = 12
|
|
80
|
+
@export var max_ammo: int = 120
|
|
81
|
+
@export var is_automatic: bool = false
|
|
82
|
+
@export var is_hitscan: bool = true
|
|
83
|
+
@export var spread: float = 0.0 # radians of random spread
|
|
84
|
+
@export var projectile_scene: PackedScene = null
|
|
85
|
+
@export var weapon_model_scene: PackedScene = null
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
WeaponManager (Node3D child of Player) owns `weapons: Array[WeaponData]`,
|
|
89
|
+
current index, magazine/reserve ammo, and these signals for the HUD:
|
|
90
|
+
`weapon_changed`, `ammo_changed(current, reserve)`,
|
|
91
|
+
`reload_started(time)`, `reload_finished`.
|
|
92
|
+
|
|
93
|
+
Core rules learned the hard way:
|
|
94
|
+
|
|
95
|
+
- Automatic vs semi-auto is one branch: automatic polls
|
|
96
|
+
`Input.is_action_pressed("shoot")`, semi-auto uses
|
|
97
|
+
`is_action_just_pressed`.
|
|
98
|
+
- Fire path: refuse if `not can_shoot or is_reloading`; if magazine empty,
|
|
99
|
+
auto-start reload instead of firing; decrement ammo and emit
|
|
100
|
+
`ammo_changed` BEFORE resolving the hit.
|
|
101
|
+
- Spread: randomize `shoot_ray.target_position` x/y within +-spread before
|
|
102
|
+
the raycast, then RESET it to (0,0,-100) after - a forgotten reset makes
|
|
103
|
+
every later weapon inherit the spread.
|
|
104
|
+
- Switching: clear WeaponHolder children (`queue_free`), instantiate the
|
|
105
|
+
new `weapon_model_scene`, reset `is_reloading`/`can_shoot`/`shoot_timer`.
|
|
106
|
+
Guard against re-equipping the same index.
|
|
107
|
+
- Reload:
|
|
108
|
+
|
|
109
|
+
```gdscript
|
|
110
|
+
func _start_reload() -> void:
|
|
111
|
+
if is_reloading or magazine_ammo == current_weapon.magazine_size:
|
|
112
|
+
return
|
|
113
|
+
if reserve_ammo <= 0:
|
|
114
|
+
return
|
|
115
|
+
is_reloading = true
|
|
116
|
+
can_shoot = false
|
|
117
|
+
reload_started.emit(current_weapon.reload_time)
|
|
118
|
+
var tween := create_tween()
|
|
119
|
+
tween.tween_interval(current_weapon.reload_time)
|
|
120
|
+
tween.tween_callback(_finish_reload)
|
|
121
|
+
|
|
122
|
+
func _finish_reload() -> void:
|
|
123
|
+
var available := mini(current_weapon.magazine_size - magazine_ammo, reserve_ammo)
|
|
124
|
+
magazine_ammo += available
|
|
125
|
+
reserve_ammo -= available
|
|
126
|
+
is_reloading = false
|
|
127
|
+
can_shoot = true
|
|
128
|
+
ammo_changed.emit(magazine_ammo, reserve_ammo)
|
|
129
|
+
reload_finished.emit()
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The `is_reloading` guard is not optional - without it players stack reload
|
|
133
|
+
timers. Partial reloads take `mini(needed, reserve)`, never a full magazine.
|
|
134
|
+
|
|
135
|
+
Weapon switching by number keys: loop `weapons.size()` checking
|
|
136
|
+
`Input.is_action_just_pressed("weapon_%d" % (i + 1))`.
|
|
137
|
+
|
|
138
|
+
## Interaction system
|
|
139
|
+
|
|
140
|
+
Duck-typed interface - any node is interactable if it implements:
|
|
141
|
+
|
|
142
|
+
```gdscript
|
|
143
|
+
func interact(player: Node3D) -> void
|
|
144
|
+
func get_interaction_prompt() -> String
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The player polls its InteractionRay every physics frame and caches
|
|
148
|
+
`look_target`. Check the collider AND its parent for the methods (the
|
|
149
|
+
collider is often a StaticBody3D child of the actual object):
|
|
150
|
+
|
|
151
|
+
```gdscript
|
|
152
|
+
func _update_interaction_target() -> void:
|
|
153
|
+
if interaction_ray.is_colliding():
|
|
154
|
+
var target: Node = interaction_ray.get_collider()
|
|
155
|
+
if target and not target.has_method("get_interaction_prompt"):
|
|
156
|
+
target = target.get_parent()
|
|
157
|
+
if target and target.has_method("get_interaction_prompt"):
|
|
158
|
+
look_target = target
|
|
159
|
+
return
|
|
160
|
+
look_target = null
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Door pattern
|
|
164
|
+
|
|
165
|
+
Node3D root that rotates; mesh + StaticBody3D child positioned at
|
|
166
|
+
(0.5, 1.4, 0) so the hinge is the parent origin, not the door center.
|
|
167
|
+
`_process` lerps `rotation.y` toward `target_rotation`. Open away from the
|
|
168
|
+
player by checking which side they are on:
|
|
169
|
+
|
|
170
|
+
```gdscript
|
|
171
|
+
var to_door := global_position - player.global_position
|
|
172
|
+
var local_dir := global_transform.basis.inverse() * to_door
|
|
173
|
+
target_rotation = deg_to_rad(open_angle if local_dir.z > 0 else -open_angle)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Prompt reflects state: "Locked" / "Open" / "Close". The door body's
|
|
177
|
+
collision_layer is 6 (Environment + Interactable) so it both blocks
|
|
178
|
+
movement and answers the interaction ray.
|
|
179
|
+
|
|
180
|
+
### Pickup pattern
|
|
181
|
+
|
|
182
|
+
RigidBody3D with `freeze = true` (physics off, still hit by rays), idle bob
|
|
183
|
+
(`base_y + sin(bob_time) * 0.05`) and slow rotate, randomized starting
|
|
184
|
+
phase (`bob_time = randf() * TAU`) so a row of pickups does not bob in
|
|
185
|
+
lockstep. `interact()` applies the effect and `queue_free()`s.
|
|
186
|
+
|
|
187
|
+
## HUD
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
HUD (CanvasLayer)
|
|
191
|
+
Crosshair (TextureRect, centered 16x16)
|
|
192
|
+
InteractionPrompt (Label, bottom center, "[E] Open")
|
|
193
|
+
AmmoLabel (Label, bottom right, "12 / 120")
|
|
194
|
+
HealthBar (ProgressBar, bottom left)
|
|
195
|
+
DamageOverlay (ColorRect, full rect, red, alpha 0)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Wiring pattern: the HUD finds the player via the "player" group in
|
|
199
|
+
`_process` (retry until found - spawn order is not guaranteed), then
|
|
200
|
+
connects `weapon_manager.ammo_changed`. Interaction prompt shows
|
|
201
|
+
`"[E] " + player.look_target.get_interaction_prompt()` while `look_target`
|
|
202
|
+
is set, hides otherwise.
|
|
203
|
+
|
|
204
|
+
Damage feedback: on hit, set the overlay to `Color(1, 0, 0, 0.3)` and fade
|
|
205
|
+
alpha down at 2.0/s in `_process`. Cheap and effective.
|
|
206
|
+
|
|
207
|
+
## Order of work
|
|
208
|
+
|
|
209
|
+
1. Hitscan fire on the base controller (fixed 25 damage, no weapon data).
|
|
210
|
+
2. Hit effects + damage overlay - shooting must feel real before it is
|
|
211
|
+
configurable.
|
|
212
|
+
3. WeaponData + WeaponManager, migrate the hardcoded fire into it, add
|
|
213
|
+
ammo/reload + HUD ammo label.
|
|
214
|
+
4. Second and third weapons (one automatic with spread, one projectile) to
|
|
215
|
+
prove the data path.
|
|
216
|
+
5. Interaction ray + door + pickup + prompt label.
|