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,510 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: multiplayer-movement
|
|
3
|
+
description: "Client-side movement in Summer multiplayer: owners move instantly, others glide, the authority checks each pose."
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: [Cursor, Claude Code, Windsurf, Codex]
|
|
6
|
+
category: multiplayer-and-networking
|
|
7
|
+
user-invocable: true
|
|
8
|
+
allowed-tools: Read Grep Glob Edit Write summer_read_file summer_write_file summer_get_scene_tree summer_inspect_node summer_get_script_errors summer_project_setting summer_play summer_stop summer_get_diagnostics
|
|
9
|
+
paths: ["network/**", "client/**", "authority/**"]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# /multiplayer-movement — each player moves their own character
|
|
13
|
+
|
|
14
|
+
Step 2 of the multiplayer sequence. It continues the Courtyard game from
|
|
15
|
+
`multiplayer-project`; read that first if `network/game.gd` does not exist.
|
|
16
|
+
[files.md](files.md) has every file this step changes, complete, as it ran
|
|
17
|
+
under Local Play.
|
|
18
|
+
|
|
19
|
+
## How it works
|
|
20
|
+
|
|
21
|
+
Summer games use **client-side movement**:
|
|
22
|
+
|
|
23
|
+
- **The owner** runs the whole character controller locally: input, walking,
|
|
24
|
+
gravity, jumping, collision. Its camera follows instantly, at any latency.
|
|
25
|
+
About 20 times a second it **publishes its pose** (position, facing) to an
|
|
26
|
+
owner-written State group.
|
|
27
|
+
- **The authority** runs no movement. It **checks every pose** with an
|
|
28
|
+
acceptance policy: inside the level, no faster than the game allows, still
|
|
29
|
+
the same player. A refused pose snaps the owner back to the last accepted
|
|
30
|
+
one. The authority can also move a player on purpose (respawn, portal).
|
|
31
|
+
- **Everyone else** receives each accepted pose and draws it 100 ms in the
|
|
32
|
+
past, gliding between poses, so remote players move smoothly through
|
|
33
|
+
jitter and lost packets.
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
owner client authority other clients
|
|
37
|
+
input → controller ──pose──► acceptance policy ──pose──► glide between poses
|
|
38
|
+
camera follows instantly refuses: owner snaps back (100 ms behind)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Game rules on the authority (pickups, melee, triggers) read the latest
|
|
42
|
+
accepted pose (`multiplayer-state`).
|
|
43
|
+
|
|
44
|
+
The pose is bound to the player's **entity**, which the Spawner creates for
|
|
45
|
+
every joined player. The authority creates the pose group when that entity
|
|
46
|
+
spawns, which is after the player's network is ready and again after a
|
|
47
|
+
reconnect. The group ends with the entity.
|
|
48
|
+
|
|
49
|
+
## 1. Declare the pose stream
|
|
50
|
+
|
|
51
|
+
Add a `Motion` State stream to `network/composition.tres`, before the
|
|
52
|
+
Command stream's sub-resources:
|
|
53
|
+
|
|
54
|
+
```ini
|
|
55
|
+
[sub_resource type="SummerNetworkField" id="MotionPosition"]
|
|
56
|
+
field_name = &"position"
|
|
57
|
+
type = 9
|
|
58
|
+
|
|
59
|
+
[sub_resource type="SummerNetworkField" id="MotionYaw"]
|
|
60
|
+
field_name = &"yaw"
|
|
61
|
+
type = 2
|
|
62
|
+
|
|
63
|
+
[sub_resource type="SummerNetworkField" id="MotionEntity"]
|
|
64
|
+
field_name = &"entity"
|
|
65
|
+
type = 1
|
|
66
|
+
|
|
67
|
+
[sub_resource type="SummerNetworkSchema" id="MotionSchema"]
|
|
68
|
+
schema_id = &"courtyard.motion.v1"
|
|
69
|
+
state_fields = Array[SummerNetworkField]([SubResource("MotionEntity"), SubResource("MotionPosition"), SubResource("MotionYaw")])
|
|
70
|
+
|
|
71
|
+
[sub_resource type="SummerWorldAudience" id="Everyone"]
|
|
72
|
+
max_expansion = 8
|
|
73
|
+
|
|
74
|
+
[sub_resource type="SummerNetworkStateStream" id="Motion"]
|
|
75
|
+
stream_id = &"courtyard.motion"
|
|
76
|
+
writer_policy = 1
|
|
77
|
+
skip_writer_echo = true
|
|
78
|
+
state_schema = SubResource("MotionSchema")
|
|
79
|
+
audience_policy = SubResource("Everyone")
|
|
80
|
+
max_audience_count = 8
|
|
81
|
+
max_keys = 8
|
|
82
|
+
max_state_bytes = 1024
|
|
83
|
+
max_total_bytes = 8192
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
and list it in the `[resource]`:
|
|
87
|
+
|
|
88
|
+
```ini
|
|
89
|
+
[resource]
|
|
90
|
+
network_version = "1.0.0"
|
|
91
|
+
state_streams = Array[SummerNetworkStateStream]([SubResource("Motion")])
|
|
92
|
+
occurrence_streams = Array[SummerNetworkOccurrenceStream]([SubResource("Commands")])
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Update `load_steps` in the first line to the number of sub-resources plus
|
|
96
|
+
one. [files.md](files.md) has the complete file.
|
|
97
|
+
|
|
98
|
+
| Setting | Why |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `writer_policy = 1` | `WRITER_OWNER`: a Session writes it, not the authority |
|
|
101
|
+
| `skip_writer_echo = true` | The owner is not sent its own accepted poses, only refusals and the authority's moves |
|
|
102
|
+
| `audience_policy` World, `max_expansion = 8`, `max_audience_count = 8` | Everyone sees every pose. Set both to at least the player count: 0 reaches nobody |
|
|
103
|
+
| `max_keys = 8` | One key per seat |
|
|
104
|
+
| `max_state_bytes = 1024` | Room for the encoded pose. Too tight and every submission fails as `invalid_state` |
|
|
105
|
+
| fields `entity` (`1`, INT), `position` (`9`, VECTOR3), `yaw` (`2`, FLOAT) | Exactly what others need to draw the player |
|
|
106
|
+
|
|
107
|
+
Field `type` values are `SummerNetworkField.TYPE_*`: BOOL 0, INT 1, FLOAT 2,
|
|
108
|
+
STRING 3, VECTOR2 5, VECTOR3 9, QUATERNION 15, COLOR 19, PACKED_BYTE_ARRAY
|
|
109
|
+
20. A pose Dictionary must have **exactly** the declared fields with exactly
|
|
110
|
+
those types: no extras, no missing keys, no ints for floats. To send more
|
|
111
|
+
(an animation state, a carried item), add a field to the schema and to every
|
|
112
|
+
pose.
|
|
113
|
+
|
|
114
|
+
## 2. Shared rules
|
|
115
|
+
|
|
116
|
+
`network/game.gd` gains the stream id, the speed limit and the level
|
|
117
|
+
bounds, after `const QUEUE`:
|
|
118
|
+
|
|
119
|
+
```gdscript
|
|
120
|
+
const MOTION := &"courtyard.motion"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```gdscript
|
|
124
|
+
## The authority's sanity bound on movement, in metres per second. Keep it
|
|
125
|
+
## above the fastest legitimate speed (walking, jumping, falling).
|
|
126
|
+
const MAX_SPEED := 15.0
|
|
127
|
+
const ARENA_RADIUS := 40.0
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
and the seat key, the spawn pose and the bounds check, after
|
|
131
|
+
`build_network`:
|
|
132
|
+
|
|
133
|
+
```gdscript
|
|
134
|
+
## Pose groups are keyed by seat, so a stream needs at most MAX_PLAYERS keys.
|
|
135
|
+
static func motion_key(seat: int) -> StringName:
|
|
136
|
+
return StringName("p%d" % seat)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
## Seats spawn on a circle. `entity` ties the pose to the player's entity.
|
|
140
|
+
static func spawn_pose(seat: int, entity_id: int) -> Dictionary:
|
|
141
|
+
var angle := TAU * float(seat) / MAX_PLAYERS
|
|
142
|
+
return {"entity": entity_id, "position": Vector3(cos(angle) * 4.0, 1.0, sin(angle) * 4.0), "yaw": 0.0}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
static func in_bounds(position: Vector3) -> bool:
|
|
146
|
+
return position.is_finite() and Vector2(position.x, position.z).length() <= ARENA_RADIUS \
|
|
147
|
+
and position.y > -10.0 and position.y < 30.0
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`MAX_SPEED` must stay above the fastest legitimate speed, falling included.
|
|
151
|
+
The authority enforces it; the client never reads it.
|
|
152
|
+
|
|
153
|
+
## 3. The authority checks every pose
|
|
154
|
+
|
|
155
|
+
`authority/main.gd`:
|
|
156
|
+
|
|
157
|
+
```gdscript
|
|
158
|
+
extends Node
|
|
159
|
+
## The match server. It runs headless and owns every rule.
|
|
160
|
+
|
|
161
|
+
const Game = preload("res://network/game.gd")
|
|
162
|
+
const PlayerArchetype = preload("res://authority/player_archetype.tres")
|
|
163
|
+
## How far a player may move in one burst after a network stall, in metres.
|
|
164
|
+
const BURST := Game.MAX_SPEED * 0.5
|
|
165
|
+
|
|
166
|
+
var spawner: SummerNetworkSpawner
|
|
167
|
+
var world: SummerNetworkWorld
|
|
168
|
+
var seat_of := {} # session_id -> seat, held until the Session leaves
|
|
169
|
+
var allowance := {} # seat -> metres the player may still move
|
|
170
|
+
var refilled_at := {} # seat -> msec of the last allowance refill
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
func _ready() -> void:
|
|
174
|
+
spawner = Game.build_network(self, PlayerArchetype)
|
|
175
|
+
world = $World
|
|
176
|
+
spawner.entity_spawned.connect(_on_player_spawned)
|
|
177
|
+
spawner.command_received.connect(_on_command)
|
|
178
|
+
world.session_left.connect(_on_session_left)
|
|
179
|
+
var initialized: SummerResult = await Summer.initialize(Game.GAME_ID).get_result_or_completed_signal()
|
|
180
|
+
if not initialized.ok:
|
|
181
|
+
push_error(initialized.message)
|
|
182
|
+
return
|
|
183
|
+
if not world.is_ready():
|
|
184
|
+
await world.binding_ready
|
|
185
|
+
if not spawner.is_network_ready():
|
|
186
|
+
await spawner.network_ready
|
|
187
|
+
print("COURTYARD_AUTHORITY_READY")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
## A seat is a player's place in this World for their whole stay, across
|
|
191
|
+
## reconnects. Returns -1 when the World is full.
|
|
192
|
+
func _seat(session: SummerSession) -> int:
|
|
193
|
+
if seat_of.has(session.session_id):
|
|
194
|
+
return seat_of[session.session_id]
|
|
195
|
+
var taken := seat_of.values()
|
|
196
|
+
for seat in Game.MAX_PLAYERS:
|
|
197
|
+
if seat not in taken:
|
|
198
|
+
seat_of[session.session_id] = seat
|
|
199
|
+
return seat
|
|
200
|
+
return -1
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
## The Spawner spawns one player entity per joined player, once that player's
|
|
204
|
+
## network is ready (and again after a reconnect). Its owner becomes the only
|
|
205
|
+
## writer of the pose; the group ends with the entity.
|
|
206
|
+
func _on_player_spawned(entity: SummerNetworkEntity) -> void:
|
|
207
|
+
var seat := _seat(entity.session)
|
|
208
|
+
if seat < 0:
|
|
209
|
+
push_warning("no free seat")
|
|
210
|
+
return
|
|
211
|
+
allowance[seat] = BURST
|
|
212
|
+
refilled_at[seat] = Time.get_ticks_msec()
|
|
213
|
+
var group := spawner.create_state_group(Game.MOTION, Game.motion_key(seat), Game.spawn_pose(seat, entity.entity_id), entity.session, entity)
|
|
214
|
+
group.set_acceptance_policy(_accept_motion.bind(seat))
|
|
215
|
+
print("COURTYARD_PLAYER_SPAWNED seat=%d entity=%d" % [seat, entity.entity_id])
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
## Runs on every pose an owner submits. True accepts it unchanged; false
|
|
219
|
+
## refuses it, and the owner snaps back to the last accepted pose.
|
|
220
|
+
func _accept_motion(candidate: Dictionary, current: Dictionary, seat: int) -> bool:
|
|
221
|
+
if candidate.entity != current.entity:
|
|
222
|
+
return false # a player can move, never become someone else
|
|
223
|
+
var position: Vector3 = candidate.position
|
|
224
|
+
if not Game.in_bounds(position) or not is_finite(candidate.yaw):
|
|
225
|
+
return false
|
|
226
|
+
# A distance allowance refilled at MAX_SPEED: bursts after a stall pass,
|
|
227
|
+
# speed hacks and teleports do not.
|
|
228
|
+
var now := Time.get_ticks_msec()
|
|
229
|
+
var budget := minf(float(allowance[seat]) + Game.MAX_SPEED * (now - int(refilled_at[seat])) / 1000.0, BURST)
|
|
230
|
+
refilled_at[seat] = now
|
|
231
|
+
var distance := position.distance_to(current.position)
|
|
232
|
+
if distance > budget:
|
|
233
|
+
allowance[seat] = budget
|
|
234
|
+
return false
|
|
235
|
+
allowance[seat] = budget - distance
|
|
236
|
+
return true
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
## Moves a player on purpose (respawn, portal). The owner snaps to it.
|
|
240
|
+
func teleport(seat: int, position: Vector3) -> void:
|
|
241
|
+
var group := spawner.get_state_group(Game.MOTION, Game.motion_key(seat))
|
|
242
|
+
if group != null and group.active:
|
|
243
|
+
var pose := group.get_state()
|
|
244
|
+
pose.position = position
|
|
245
|
+
allowance[seat] = BURST
|
|
246
|
+
group.reset(pose)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
func _on_command(request: SummerNetworkCommandRequest) -> void:
|
|
250
|
+
var command := Game.unpack(request.payload)
|
|
251
|
+
match str(command.get("c", "")):
|
|
252
|
+
"join":
|
|
253
|
+
_join(request)
|
|
254
|
+
_:
|
|
255
|
+
request.refuse(&"unknown_command")
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
## A client sends "join" once its network is ready.
|
|
259
|
+
func _join(request: SummerNetworkCommandRequest) -> void:
|
|
260
|
+
var seat := _seat(request.get_session())
|
|
261
|
+
if seat < 0:
|
|
262
|
+
request.refuse(&"match_full")
|
|
263
|
+
return
|
|
264
|
+
request.accept({}, Game.pack({"seat": seat}))
|
|
265
|
+
print("COURTYARD_PLAYER_JOINED seat=%d" % seat)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
## The Session ended for good: free the seat. (Its pose group already ended
|
|
269
|
+
## with the player's entity.)
|
|
270
|
+
func _on_session_left(session: SummerSession, reason: String) -> void:
|
|
271
|
+
var seat: int = seat_of.get(session.session_id, -1)
|
|
272
|
+
seat_of.erase(session.session_id)
|
|
273
|
+
print("COURTYARD_PLAYER_LEFT seat=%d reason=%s" % [seat, reason])
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
- `create_state_group(stream, key, initial, writer_session, entity)` makes
|
|
277
|
+
that Session the only writer, and ties the group's life to the entity.
|
|
278
|
+
- `set_acceptance_policy` runs synchronously on each pose. Return `true` to
|
|
279
|
+
accept it unchanged and `false` to refuse it. It cannot edit a pose; to
|
|
280
|
+
move a player, call `reset()` as `teleport()` does.
|
|
281
|
+
- The distance allowance refills at `MAX_SPEED` and holds at most half a
|
|
282
|
+
second of movement. Poses bunched together after a lag spike pass;
|
|
283
|
+
teleports and speed hacks don't. A flat per-pose distance check would
|
|
284
|
+
refuse honest players after every hiccup and still let a cheat move fast in
|
|
285
|
+
small steps.
|
|
286
|
+
- A **seat** is the player's place in the World until `session_left`. Pose
|
|
287
|
+
groups are keyed by seat, so a stream needs at most `MAX_PLAYERS` keys.
|
|
288
|
+
|
|
289
|
+
## 4. The client: own player and everyone else
|
|
290
|
+
|
|
291
|
+
`client/main.gd` pairs each pose group with its entity. Either can arrive
|
|
292
|
+
first. Add a `waiting` dictionary:
|
|
293
|
+
|
|
294
|
+
```gdscript
|
|
295
|
+
var waiting := {} # entity id -> pose group that arrived before its entity
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
connect both signals in `_ready()` before joining:
|
|
299
|
+
|
|
300
|
+
```gdscript
|
|
301
|
+
# Connect before joining, or the first values arrive unseen.
|
|
302
|
+
spawner.state_group_created.connect(_on_group_created)
|
|
303
|
+
spawner.entity_spawned.connect(_on_entity_spawned)
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
and add the handlers at the end:
|
|
307
|
+
|
|
308
|
+
```gdscript
|
|
309
|
+
func _on_group_created(group: SummerNetworkStateGroup) -> void:
|
|
310
|
+
if group.get_stream_id() == Game.MOTION:
|
|
311
|
+
_attach_pose(group, int(group.get_state().entity))
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
## The pose group and the player's entity can arrive in either order.
|
|
315
|
+
func _attach_pose(group: SummerNetworkStateGroup, entity_id: int) -> void:
|
|
316
|
+
var entity := spawner.get_entity(entity_id)
|
|
317
|
+
if entity == null or entity.root == null:
|
|
318
|
+
waiting[entity_id] = group
|
|
319
|
+
return
|
|
320
|
+
entity.root.bind(group)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
func _on_entity_spawned(entity: SummerNetworkEntity) -> void:
|
|
324
|
+
if waiting.has(entity.entity_id):
|
|
325
|
+
_attach_pose(waiting[entity.entity_id], entity.entity_id)
|
|
326
|
+
waiting.erase(entity.entity_id)
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
The client archetype from `multiplayer-project` already gives each player an
|
|
330
|
+
owner scene and an observer scene. Give them scripts.
|
|
331
|
+
|
|
332
|
+
`client/player_owner.gd`, your own player:
|
|
333
|
+
|
|
334
|
+
```gdscript
|
|
335
|
+
extends CharacterBody3D
|
|
336
|
+
## Your own player. You move it locally and publish where it is; the
|
|
337
|
+
## authority only snaps you somewhere on spawn, teleport or a refused pose.
|
|
338
|
+
|
|
339
|
+
const WALK_SPEED := 6.0
|
|
340
|
+
const JUMP_VELOCITY := 5.0
|
|
341
|
+
const SEND_INTERVAL := 1.0 / 20.0
|
|
342
|
+
|
|
343
|
+
var group: SummerNetworkStateGroup
|
|
344
|
+
var _pose := {} # the full pose; only position and yaw change
|
|
345
|
+
var _sent := {}
|
|
346
|
+
var _send_elapsed := 0.0
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
func bind(motion: SummerNetworkStateGroup) -> void:
|
|
350
|
+
group = motion
|
|
351
|
+
group.state_installed.connect(_on_installed)
|
|
352
|
+
_on_installed(group.get_state(), group.get_revision(), true)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
func _on_installed(state: Dictionary, _revision: int, reset: bool) -> void:
|
|
356
|
+
# Accepted poses are not echoed back to you (skip_writer_echo). A reset is
|
|
357
|
+
# the authority moving you: spawn, teleport, or a pose it refused.
|
|
358
|
+
if not reset or state.is_empty():
|
|
359
|
+
return
|
|
360
|
+
_pose = state.duplicate()
|
|
361
|
+
_sent = state.duplicate()
|
|
362
|
+
global_position = state.position
|
|
363
|
+
rotation.y = state.yaw
|
|
364
|
+
velocity = Vector3.ZERO
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
func _physics_process(delta: float) -> void:
|
|
368
|
+
if group == null or not group.active:
|
|
369
|
+
return
|
|
370
|
+
if not is_on_floor():
|
|
371
|
+
velocity += get_gravity() * delta
|
|
372
|
+
if Input.is_action_just_pressed(&"ui_accept") and is_on_floor():
|
|
373
|
+
velocity.y = JUMP_VELOCITY
|
|
374
|
+
var input := Input.get_vector(&"ui_left", &"ui_right", &"ui_up", &"ui_down")
|
|
375
|
+
velocity.x = input.x * WALK_SPEED
|
|
376
|
+
velocity.z = input.y * WALK_SPEED
|
|
377
|
+
if input != Vector2.ZERO:
|
|
378
|
+
rotation.y = atan2(-input.x, -input.y)
|
|
379
|
+
move_and_slide()
|
|
380
|
+
_publish(delta)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
## Publishes the pose 20 times a second, and only when it changed.
|
|
384
|
+
func _publish(delta: float) -> void:
|
|
385
|
+
_send_elapsed += delta
|
|
386
|
+
if _send_elapsed < SEND_INTERVAL:
|
|
387
|
+
return
|
|
388
|
+
_send_elapsed = 0.0
|
|
389
|
+
_pose.position = global_position
|
|
390
|
+
_pose.yaw = rotation.y
|
|
391
|
+
if _pose == _sent:
|
|
392
|
+
return
|
|
393
|
+
group.submit(_pose)
|
|
394
|
+
_sent = _pose.duplicate()
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Attach it to `client/player_owner.tscn` (`script = ExtResource(...)` on the
|
|
398
|
+
root, as in [files.md](files.md)).
|
|
399
|
+
|
|
400
|
+
`client/player_observer.gd`, everyone else:
|
|
401
|
+
|
|
402
|
+
```gdscript
|
|
403
|
+
extends Node3D
|
|
404
|
+
## Someone else's player. It glides between the poses its owner published,
|
|
405
|
+
## drawn VIEW_DELAY in the past so there is always a newer pose to glide to.
|
|
406
|
+
|
|
407
|
+
const VIEW_DELAY := 0.1
|
|
408
|
+
|
|
409
|
+
var group: SummerNetworkStateGroup
|
|
410
|
+
var _samples: Array = [] # [time, position, yaw] per received pose
|
|
411
|
+
var _offsets: Array = [] # recent arrival minus authority acceptance, seconds
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
func bind(motion: SummerNetworkStateGroup) -> void:
|
|
415
|
+
group = motion
|
|
416
|
+
group.state_installed.connect(_on_installed)
|
|
417
|
+
_on_installed(group.get_state(), group.get_revision(), true)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
func _on_installed(state: Dictionary, _revision: int, reset: bool) -> void:
|
|
421
|
+
if state.is_empty():
|
|
422
|
+
return
|
|
423
|
+
var now := Time.get_ticks_usec() / 1000000.0
|
|
424
|
+
if reset:
|
|
425
|
+
_samples.clear() # a spawn or teleport is a cut, not a slide
|
|
426
|
+
global_position = state.position
|
|
427
|
+
rotation.y = state.yaw
|
|
428
|
+
_samples.append([_pose_time(now), state.position, state.yaw])
|
|
429
|
+
if _samples.size() > 20:
|
|
430
|
+
_samples.pop_front()
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
func _process(_delta: float) -> void:
|
|
434
|
+
if _samples.is_empty():
|
|
435
|
+
return
|
|
436
|
+
var at := Time.get_ticks_usec() / 1000000.0 - VIEW_DELAY
|
|
437
|
+
var newest: Array = _samples[-1]
|
|
438
|
+
if at >= newest[0] or _samples.size() == 1:
|
|
439
|
+
global_position = newest[1]
|
|
440
|
+
rotation.y = newest[2]
|
|
441
|
+
return
|
|
442
|
+
for i in range(_samples.size() - 1, 0, -1):
|
|
443
|
+
var a: Array = _samples[i - 1]
|
|
444
|
+
var b: Array = _samples[i]
|
|
445
|
+
if at >= a[0]:
|
|
446
|
+
var t := clampf((at - a[0]) / maxf(b[0] - a[0], 0.0001), 0.0, 1.0)
|
|
447
|
+
global_position = (a[1] as Vector3).lerp(b[1], t)
|
|
448
|
+
rotation.y = lerp_angle(a[2], b[2], t)
|
|
449
|
+
return
|
|
450
|
+
global_position = _samples[0][1]
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
## Places a pose on the local timeline by when the authority accepted it, so
|
|
454
|
+
## poses that arrive bunched together still play back evenly.
|
|
455
|
+
func _pose_time(now: float) -> float:
|
|
456
|
+
var timing := group.get_timing()
|
|
457
|
+
if timing == null or timing.state_authority_usec < 0:
|
|
458
|
+
return now
|
|
459
|
+
var accepted := timing.state_authority_usec / 1000000.0
|
|
460
|
+
_offsets.append(now - accepted)
|
|
461
|
+
if _offsets.size() > 40:
|
|
462
|
+
_offsets.pop_front()
|
|
463
|
+
return minf(accepted + float(_offsets.min()), now)
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Attach it to `client/player_observer.tscn` the same way.
|
|
467
|
+
|
|
468
|
+
- The owner ignores ordinary installs: with `skip_writer_echo` it is not sent
|
|
469
|
+
its own accepted poses. A `reset` install is the authority moving it:
|
|
470
|
+
spawn, `teleport()`, or a refused pose.
|
|
471
|
+
- The owner publishes only changed poses, 20 times a second. The engine
|
|
472
|
+
keeps only the newest pending pose per player, so a faster rate adds
|
|
473
|
+
traffic, not smoothness.
|
|
474
|
+
- The observer orders poses by when the authority accepted them
|
|
475
|
+
(`get_timing().state_authority_usec`), so a burst of late packets still
|
|
476
|
+
plays back evenly. A `reset` is a cut, not a slide.
|
|
477
|
+
- Observer scenes have no collision. Other players are drawings on your
|
|
478
|
+
screen; their owners decide where they are.
|
|
479
|
+
|
|
480
|
+
## 5. Test it under latency
|
|
481
|
+
|
|
482
|
+
Run Local Play with 2 players at 100 ms round trip, 10 ms jitter and 1 %
|
|
483
|
+
loss (`multiplayer-testing` shows how):
|
|
484
|
+
|
|
485
|
+
- [ ] Your own player responds instantly and never snaps back while moving normally.
|
|
486
|
+
- [ ] The other window's copy of your player moves smoothly and stops exactly where you stopped.
|
|
487
|
+
- [ ] Jumping and falling look the same on both screens.
|
|
488
|
+
- [ ] No `SCRIPT ERROR`, no `cannot attach`, and every process has `"script_errors":0`.
|
|
489
|
+
|
|
490
|
+
To check the anti-cheat, move the owner by 30 m in one frame from a test
|
|
491
|
+
script. The authority refuses the pose, the owner snaps back, and the other
|
|
492
|
+
client never sees the jump.
|
|
493
|
+
|
|
494
|
+
## Common mistakes
|
|
495
|
+
|
|
496
|
+
| Don't | Do | Why |
|
|
497
|
+
|---|---|---|
|
|
498
|
+
| Move other players' characters locally | Draw others from their published poses | Each player's client is the only source of their position |
|
|
499
|
+
| Create the pose group on `session_joined` | Create it in `entity_spawned` | At `session_joined` the player's network is not ready yet, and creation fails |
|
|
500
|
+
| Trust poses unchecked | An acceptance policy with a refilling distance allowance | Otherwise any client can teleport |
|
|
501
|
+
| A flat "max distance per pose" check | An allowance refilled at `MAX_SPEED` | Bunched poses after lag get refused, and cheats still move fast |
|
|
502
|
+
| Snap observers to each pose as it arrives | Glide 100 ms behind, ordered by acceptance time | Poses arrive unevenly; snapping looks like stutter |
|
|
503
|
+
| Add a key to the pose Dictionary without the schema | Add the field to the schema too | Undeclared or missing fields fail the submission |
|
|
504
|
+
| `max_state_bytes = 256` | 1024 or more | The bound covers the encoded pose; too tight fails as `invalid_state` |
|
|
505
|
+
| Collide with observer bodies | Leave observer scenes without collision | Another player's body is only a drawing on your screen |
|
|
506
|
+
|
|
507
|
+
## Next
|
|
508
|
+
|
|
509
|
+
`multiplayer-state`: scores, doors, private inventories, and actions the
|
|
510
|
+
authority checks against these poses.
|