summer-engine 3.1.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 +102 -0
- package/README.md +18 -3
- package/assets/audit/scene_audit.gd +4136 -0
- package/assets/seeing/seeing_probe.gd +1873 -0
- package/dist/cli/commands/skills.js +20 -2
- 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 +18 -2
- 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/installer/agent-config.js +5 -3
- package/dist/installer/agent-table.d.ts +16 -0
- package/dist/installer/agent-table.js +52 -17
- 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/amp/README.md +1 -1
- package/integrations/crush/README.md +1 -1
- package/integrations/grok-build/README.md +1 -1
- package/integrations/hermes/README.md +1 -1
- package/integrations/kimi-code/README.md +1 -1
- package/integrations/mistral-vibe/README.md +1 -1
- package/integrations/opencode/README.md +1 -1
- package/integrations/rovo-dev/README.md +1 -1
- package/integrations/vscode-copilot/README.md +1 -1
- package/integrations/warp/README.md +1 -1
- package/integrations/windsurf/README.md +1 -1
- 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 +36 -14
- 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 +1 -1
- 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 +36 -14
- package/registry/generated/skills-registry.json +421 -124
- 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,30 @@
|
|
|
1
|
+
id: skill/summer-leaderboards
|
|
2
|
+
kind: skill
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
summary: "Leaderboards in a Summer game: queue ratings from matches, and Game rankings your authority scores."
|
|
5
|
+
use_when:
|
|
6
|
+
- "the user wants a leaderboard, high scores, ranks, MMR, Elo or a 'top 50' screen in a Summer game"
|
|
7
|
+
- "a rated queue should show each player's rating, standing or percentile"
|
|
8
|
+
- "the authority should write a score, such as total coins or best time, to a Game-wide ranking"
|
|
9
|
+
- "a co-op mode should rate players against the content with authority_v1 rating deltas"
|
|
10
|
+
facets:
|
|
11
|
+
lifecycle:
|
|
12
|
+
- build
|
|
13
|
+
domains:
|
|
14
|
+
- multiplayer
|
|
15
|
+
- ui
|
|
16
|
+
modalities:
|
|
17
|
+
- scripts
|
|
18
|
+
- project
|
|
19
|
+
compatibility:
|
|
20
|
+
engine: ">=4.6"
|
|
21
|
+
related:
|
|
22
|
+
skills:
|
|
23
|
+
- skill/multiplayer
|
|
24
|
+
- skill/summer-matchmaking
|
|
25
|
+
- skill/summer-match-results
|
|
26
|
+
- skill/summer-player-data
|
|
27
|
+
source: official
|
|
28
|
+
license: MIT
|
|
29
|
+
status: preview
|
|
30
|
+
recommended: false
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: summer-match-results
|
|
3
|
+
description: "End a Summer match: report each player's win, loss or draw, show results on clients, then close the World."
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: [Cursor, Claude Code, Windsurf, Codex]
|
|
6
|
+
category: multiplayer-and-networking
|
|
7
|
+
user-invocable: true
|
|
8
|
+
allowed-tools: Read Grep Edit Write summer_read_file summer_replace_text summer_write_file summer_get_script_errors summer_project_setting summer_play summer_stop summer_get_diagnostics
|
|
9
|
+
paths: ["authority/**/*.gd", "client/**/*.gd", "world.json"]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# /summer-match-results — who won, and closing the match
|
|
13
|
+
|
|
14
|
+
## Overview
|
|
15
|
+
|
|
16
|
+
A match ends when **the authority** says so. It decides the outcome for every
|
|
17
|
+
player, then:
|
|
18
|
+
|
|
19
|
+
1. **Concludes** the match: `Summer.authority.match.conclude(outcomes, reason)`.
|
|
20
|
+
Summer settles it once and returns a settlement, including each player's
|
|
21
|
+
rating change on a rated queue.
|
|
22
|
+
2. Summer delivers each player **their own** result on the client
|
|
23
|
+
(`Summer.client.ratings.match_result`), and later their committed rating
|
|
24
|
+
(`rating_committed`). The authority never relays results or ratings.
|
|
25
|
+
3. **Completes** the World: `Summer.authority.world.complete()` for a
|
|
26
|
+
match-scoped World. Clients are disconnected and go back to your menu.
|
|
27
|
+
|
|
28
|
+
Where it works: all of this runs under Local Play with a deterministic
|
|
29
|
+
settlement, so the whole flow is testable locally. Rating values are real only
|
|
30
|
+
when hosted.
|
|
31
|
+
|
|
32
|
+
This skill extends the Courtyard game (`skill/multiplayer-project` and
|
|
33
|
+
`skill/multiplayer-state`): the first player to collect `WIN_COINS` coins wins.
|
|
34
|
+
With a team queue (`skill/summer-matchmaking`), their whole team wins.
|
|
35
|
+
|
|
36
|
+
## Steps
|
|
37
|
+
|
|
38
|
+
### 1. Track who is in the match
|
|
39
|
+
|
|
40
|
+
In `authority/main.gd`, keep every Session the engine admitted, and the
|
|
41
|
+
players who left. Add these variables:
|
|
42
|
+
|
|
43
|
+
```gdscript
|
|
44
|
+
const WIN_COINS := 1 # coins that win the match
|
|
45
|
+
var ended := false
|
|
46
|
+
var departed := {} # session_id of players who left before the end
|
|
47
|
+
var sessions := {} # session_id -> SummerSession
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
In `_ready()`, next to the other World signals (skip this line if
|
|
51
|
+
`skill/summer-player-data` already connected it):
|
|
52
|
+
|
|
53
|
+
```gdscript
|
|
54
|
+
world.session_joined.connect(_on_session_joined)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```gdscript
|
|
58
|
+
## Every player admitted into this match, kept as the exact Session object
|
|
59
|
+
## the engine handed over: conclude accepts only these.
|
|
60
|
+
func _on_session_joined(session: SummerSession) -> void:
|
|
61
|
+
sessions[session.session_id] = session
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
If the game already has `_on_session_joined` (`skill/summer-player-data`
|
|
65
|
+
adds one), put the line at its start instead of defining it twice.
|
|
66
|
+
|
|
67
|
+
At the top of `_on_session_left`:
|
|
68
|
+
|
|
69
|
+
```gdscript
|
|
70
|
+
departed[session.session_id] = true
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Keep a Session in `sessions` after it leaves: it stays on the match roster
|
|
74
|
+
until the match is delivered, and it still needs an outcome.
|
|
75
|
+
|
|
76
|
+
Presence signals on the authority's `SummerNetworkWorld`:
|
|
77
|
+
|
|
78
|
+
| Signal | Meaning | Typical response |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `session_joined(session)` | First admission into this World | Nothing extra: clients send `join` when their network is ready |
|
|
81
|
+
| `session_disconnected(session)` | Transport lost; the seat is kept | Pause or freeze that player; don't end the match yet |
|
|
82
|
+
| `session_reconnected(session)` | Same Session is back | Resume them |
|
|
83
|
+
| `session_left(session, reason)` | The Session ended, exactly once | Count them as departed; retire their groups |
|
|
84
|
+
|
|
85
|
+
Hosted, a seat waits `lifecycle.reconnect_seconds` (`world.json`) before
|
|
86
|
+
Summer ends the Session. Under Local Play a quitting player only
|
|
87
|
+
disconnects; `session_left` arrives when the World ends.
|
|
88
|
+
|
|
89
|
+
### 2. Decide the outcome and conclude
|
|
90
|
+
|
|
91
|
+
At the end of `_collect`, after `request.accept(...)`:
|
|
92
|
+
|
|
93
|
+
```gdscript
|
|
94
|
+
if wallets[session.session_id] >= WIN_COINS:
|
|
95
|
+
_end_match(session)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Then add:
|
|
99
|
+
|
|
100
|
+
```gdscript
|
|
101
|
+
## Ends the match once: every player gets an outcome, Summer settles it, and
|
|
102
|
+
## the World closes after a short results screen.
|
|
103
|
+
func _end_match(winner: SummerSession) -> void:
|
|
104
|
+
if ended:
|
|
105
|
+
return
|
|
106
|
+
ended = true
|
|
107
|
+
var winner_team := Summer.authority.match.get_participant(winner).team
|
|
108
|
+
var outcomes: Dictionary[SummerSession, SummerAuthorityMatch.MatchOutcome] = {}
|
|
109
|
+
for id: String in sessions:
|
|
110
|
+
var session: SummerSession = sessions[id]
|
|
111
|
+
var participant := Summer.authority.match.get_participant(session)
|
|
112
|
+
if participant == null or not participant.outcome_eligible:
|
|
113
|
+
continue # spectators get no outcome
|
|
114
|
+
if departed.has(id):
|
|
115
|
+
outcomes[session] = SummerAuthorityMatch.MATCH_OUTCOME_ABANDON
|
|
116
|
+
elif (participant.team == winner_team) if winner_team >= 0 else (id == winner.session_id):
|
|
117
|
+
outcomes[session] = SummerAuthorityMatch.MATCH_OUTCOME_WIN
|
|
118
|
+
else:
|
|
119
|
+
outcomes[session] = SummerAuthorityMatch.MATCH_OUTCOME_LOSS
|
|
120
|
+
match_doc.winner = winner.player.display_name
|
|
121
|
+
match_group.reset(Game.pack(match_doc))
|
|
122
|
+
var settled := await _conclude(outcomes)
|
|
123
|
+
# Let players read the results screen, then end the World.
|
|
124
|
+
await get_tree().create_timer(3.0).timeout
|
|
125
|
+
await Summer.authority.world.complete("match_complete").get_result_or_completed_signal()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
## Commits the outcomes. A retryable failure is retried with the same
|
|
129
|
+
## arguments: Summer settles a match at most once.
|
|
130
|
+
func _conclude(outcomes: Dictionary[SummerSession, SummerAuthorityMatch.MatchOutcome]) -> SummerMatchSettlement:
|
|
131
|
+
for attempt in 3:
|
|
132
|
+
var concluding := Summer.authority.match.conclude(outcomes, "coins_collected")
|
|
133
|
+
var concluded: SummerResult = await concluding.get_result_or_completed_signal()
|
|
134
|
+
if concluded.ok:
|
|
135
|
+
return concluding.settlement
|
|
136
|
+
if not concluded.retryable:
|
|
137
|
+
push_error("conclude: %s %s" % [concluded.code, concluded.message])
|
|
138
|
+
return null
|
|
139
|
+
await get_tree().create_timer(1.0 + attempt).timeout
|
|
140
|
+
return null
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Give `match_doc` a `"winner": ""` entry so clients can show the winner's name.
|
|
144
|
+
|
|
145
|
+
The rules `conclude` enforces:
|
|
146
|
+
|
|
147
|
+
- **One outcome for every outcome-eligible player admitted into the match**,
|
|
148
|
+
including players who already left (`MATCH_OUTCOME_ABANDON`). A missing or
|
|
149
|
+
extra player fails with `invalid_roster`; spectators are skipped.
|
|
150
|
+
- **Keys must be the exact `SummerSession` objects the engine delivered**
|
|
151
|
+
(`session_joined`, or `request.get_session()` in a Command). Copies, such as
|
|
152
|
+
the Sessions in `Summer.authority.context.participants`, fail with
|
|
153
|
+
`invalid_roster`.
|
|
154
|
+
- Outcomes are `MATCH_OUTCOME_WIN`, `MATCH_OUTCOME_LOSS`, `MATCH_OUTCOME_DRAW`
|
|
155
|
+
and `MATCH_OUTCOME_ABANDON`. On a team queue give every member of a team the
|
|
156
|
+
same outcome; Summer scores the sides it formed.
|
|
157
|
+
- **It settles once.** Retrying with identical arguments replays the same
|
|
158
|
+
settlement (`concluding.replayed` is true); different arguments are
|
|
159
|
+
refused. A rejected conclusion (`invalid_request`) commits nothing, so you
|
|
160
|
+
may conclude again with corrected arguments.
|
|
161
|
+
- `rating_deltas`, the third argument, is only for `authority_v1` queues
|
|
162
|
+
(`skill/summer-leaderboards`).
|
|
163
|
+
|
|
164
|
+
The settlement lists each player's committed rating changes:
|
|
165
|
+
|
|
166
|
+
```gdscript
|
|
167
|
+
if settled != null:
|
|
168
|
+
for player: SummerPlayerSettlement in settled.players:
|
|
169
|
+
for change: SummerRatingChange in player.ratings:
|
|
170
|
+
print("%s %s %d -> %d" % [player.session.player.display_name, change.system, change.before.value, change.after.value])
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Use it for authority logic (logs, rewards). Players get their own numbers
|
|
174
|
+
directly from Summer, step 3.
|
|
175
|
+
|
|
176
|
+
### 3. Show the result on each client
|
|
177
|
+
|
|
178
|
+
In `client/main.gd`'s `_ready()`, connect the result signals right after
|
|
179
|
+
`Summer.initialize` succeeds and before joining (`Summer.client` is null
|
|
180
|
+
until then), and handle the end of the World:
|
|
181
|
+
|
|
182
|
+
```gdscript
|
|
183
|
+
Summer.client.ratings.match_result.connect(_on_match_result)
|
|
184
|
+
Summer.client.ratings.rating_committed.connect(_on_rating_committed)
|
|
185
|
+
$World.server_disconnected.connect(_on_server_disconnected)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
```gdscript
|
|
189
|
+
func _on_match_result(result: SummerMatchResult) -> void:
|
|
190
|
+
var words := {
|
|
191
|
+
SummerMatchResult.OUTCOME_WIN: "You win!",
|
|
192
|
+
SummerMatchResult.OUTCOME_LOSS: "You lose.",
|
|
193
|
+
SummerMatchResult.OUTCOME_DRAW: "Draw.",
|
|
194
|
+
}
|
|
195
|
+
show_status(words.get(result.outcome, "Match over."))
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
## Arrives later, privately, once Summer committed this player's rating.
|
|
199
|
+
func _on_rating_committed(result: SummerMatchResult) -> void:
|
|
200
|
+
var change := result.rating
|
|
201
|
+
show_status("Rating %d (%+d)" % [change.after.value, change.delta])
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
func _on_server_disconnected(code: String, _detail: String) -> void:
|
|
205
|
+
show_status("The match has ended.")
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`show_status` and the `Hud/Status` label come from `skill/summer-matchmaking`.
|
|
209
|
+
|
|
210
|
+
- `match_result` arrives when the authority concludes. Its `rating_state` is
|
|
211
|
+
`RATING_STATE_PENDING` on a rated queue, or `RATING_STATE_NONE`.
|
|
212
|
+
- `rating_committed` follows when Summer committed the rating, with
|
|
213
|
+
`result.rating.before`, `after` and `delta`. Hosted only.
|
|
214
|
+
- `Summer.client.ratings.last_result` and `results` keep recent results, so a
|
|
215
|
+
results screen opened later can read them.
|
|
216
|
+
- Drive the results screen from `match_result`, not from a disconnect. Give
|
|
217
|
+
it a "Back to menu" button that awaits `Summer.client.leave()`, then offers
|
|
218
|
+
"Play again" (`find_match` again).
|
|
219
|
+
- Hosted, completing the World ends every Session and Summer stops the server,
|
|
220
|
+
so clients get `server_disconnected`. Under Local Play the World completes
|
|
221
|
+
but clients stay connected until they leave or Local Play stops.
|
|
222
|
+
|
|
223
|
+
### 4. Close the World
|
|
224
|
+
|
|
225
|
+
- **Match-scoped World** (`"lifetime": "match_scoped"`, the project skill's
|
|
226
|
+
default): conclude, then `Summer.authority.world.complete(reason)`. This
|
|
227
|
+
permanently ends the World; Summer stops the server. Completing before a
|
|
228
|
+
successful conclusion fails with `settlement_required`.
|
|
229
|
+
- **Persistent or multi-match World**: conclude, then
|
|
230
|
+
`Summer.authority.match.complete_delivery()`. Match-bound Sessions end,
|
|
231
|
+
World-bound Sessions stay, and the World keeps serving. Never call
|
|
232
|
+
`world.complete()` on a World that should keep running.
|
|
233
|
+
- `draining(deadline_unix)` on the World means Summer is shutting the server
|
|
234
|
+
down (maintenance). Conclude or save before the deadline
|
|
235
|
+
(`skill/summer-world-saves`).
|
|
236
|
+
|
|
237
|
+
A match that never concludes holds its players until the World is stopped;
|
|
238
|
+
every match needs an ending, including "everyone else left".
|
|
239
|
+
|
|
240
|
+
### 5. Test it with Local Play
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
summer_project_setting name="summer/local_play/players" value=2
|
|
244
|
+
summer_play
|
|
245
|
+
summer_get_diagnostics
|
|
246
|
+
summer_stop
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Make one player win. Expect, in order: the authority's conclusion succeeds,
|
|
250
|
+
with a settlement of deterministic test ratings (system `deterministic-v1`);
|
|
251
|
+
every client receives `match_result` with its own outcome and `rating_state`
|
|
252
|
+
`RATING_STATE_PENDING` on a rated queue; about 3 seconds later the authority
|
|
253
|
+
logs `session_left` with reason `match_complete` for each player and
|
|
254
|
+
`world.complete()` succeeds. `rating_committed` does not fire locally, and
|
|
255
|
+
clients stay connected until they leave.
|
|
256
|
+
|
|
257
|
+
## Checklist
|
|
258
|
+
|
|
259
|
+
- [ ] There is exactly one place that ends the match, guarded so it runs once.
|
|
260
|
+
- [ ] Outcomes cover every outcome-eligible participant, including those who left (`MATCH_OUTCOME_ABANDON`).
|
|
261
|
+
- [ ] Team queues give a whole team the same outcome.
|
|
262
|
+
- [ ] A retryable conclusion failure is retried with identical arguments.
|
|
263
|
+
- [ ] Clients show `match_result`, and `rating_committed` when it arrives.
|
|
264
|
+
- [ ] Match-scoped Worlds call `world.complete()` after concluding; persistent Worlds call `match.complete_delivery()`.
|
|
265
|
+
- [ ] Local Play shows the result on every client, and "Back to menu" leaves cleanly.
|
|
266
|
+
|
|
267
|
+
## Common mistakes
|
|
268
|
+
|
|
269
|
+
| Don't | Do | Why |
|
|
270
|
+
|---|---|---|
|
|
271
|
+
| Decide the winner on a client | Decide on the authority and conclude there | Only the authority's outcome is settled |
|
|
272
|
+
| Build outcomes from players still connected | Every Session from `session_joined`, leavers as `MATCH_OUTCOME_ABANDON` | Leavers stay on the roster; a missing one fails `invalid_roster` |
|
|
273
|
+
| Key outcomes by `Summer.authority.context.participants` sessions or copied ids | The exact Session objects from `session_joined` | Copies are rejected with `invalid_roster` |
|
|
274
|
+
| Wait for `server_disconnected` to show results | Show results on `match_result` | Local Play keeps clients connected after completion |
|
|
275
|
+
| Send results or ratings to clients yourself | Let `Summer.client.ratings` deliver them | Each player gets their own result privately; the authority never relays account data |
|
|
276
|
+
| Retry a conclusion with changed outcomes | Retry with identical arguments | A match settles once; divergent retries are refused |
|
|
277
|
+
| `world.complete()` in a persistent World | `match.complete_delivery()` | `complete()` ends the World for good |
|
|
278
|
+
| Call `world.complete()` before concluding | Conclude, show results, then complete | Completion ends every Session |
|
|
279
|
+
| End the match when one player disconnects | Wait for `session_left` | A disconnect keeps the seat for the reconnect window |
|
|
280
|
+
|
|
281
|
+
## See also
|
|
282
|
+
|
|
283
|
+
- `skill/summer-matchmaking` — queues and teams that the outcome refers to
|
|
284
|
+
- `skill/summer-leaderboards` — ratings and `authority_v1` rating moves
|
|
285
|
+
- `skill/summer-world-saves` — persistent Worlds that outlive a match
|
|
286
|
+
- `skill/multiplayer-testing` — running bots through a whole match
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
id: skill/summer-match-results
|
|
2
|
+
kind: skill
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
summary: "End a Summer match: report each player's win, loss or draw, show results on clients, then close the World."
|
|
5
|
+
use_when:
|
|
6
|
+
- "a multiplayer Summer game needs a winner, a game-over screen or a match that ends"
|
|
7
|
+
- "reporting match outcomes, abandons or rating changes from the authority"
|
|
8
|
+
- "players disconnect, reconnect or leave mid-match and the authority must handle it"
|
|
9
|
+
- "the World should shut down after a match, or a persistent World runs several matches"
|
|
10
|
+
facets:
|
|
11
|
+
lifecycle:
|
|
12
|
+
- build
|
|
13
|
+
domains:
|
|
14
|
+
- multiplayer
|
|
15
|
+
- gameplay
|
|
16
|
+
modalities:
|
|
17
|
+
- scripts
|
|
18
|
+
compatibility:
|
|
19
|
+
engine: ">=4.6"
|
|
20
|
+
related:
|
|
21
|
+
skills:
|
|
22
|
+
- skill/multiplayer
|
|
23
|
+
- skill/multiplayer-state
|
|
24
|
+
- skill/summer-matchmaking
|
|
25
|
+
- skill/summer-leaderboards
|
|
26
|
+
- skill/summer-world-saves
|
|
27
|
+
- skill/multiplayer-testing
|
|
28
|
+
- skill/summer-analytics
|
|
29
|
+
source: official
|
|
30
|
+
license: MIT
|
|
31
|
+
status: stable
|
|
32
|
+
recommended: false
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: summer-matchmaking
|
|
3
|
+
description: "Get players into matches with Summer matchmaking: queues, teams, accept prompts, join progress and failures."
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: [Cursor, Claude Code, Windsurf, Codex]
|
|
6
|
+
category: multiplayer-and-networking
|
|
7
|
+
user-invocable: true
|
|
8
|
+
allowed-tools: Read Grep Edit Write summer_read_file summer_replace_text summer_write_file summer_get_script_errors summer_project_setting summer_play summer_stop summer_get_diagnostics
|
|
9
|
+
paths: ["summer.build.json", "client/**/*.gd", "authority/**/*.gd", "client/**/*.tscn"]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# /summer-matchmaking — queues, teams and the way into a match
|
|
13
|
+
|
|
14
|
+
## Overview
|
|
15
|
+
|
|
16
|
+
A **queue** is a game mode players search in: "casual 2–8", "duel 1v1",
|
|
17
|
+
"ranked 2v2". You declare queues in `summer.build.json`; Summer matchmaking
|
|
18
|
+
groups searching players, reserves a server for them, and every player's
|
|
19
|
+
`Summer.client.join(SummerJoinTarget.queue(...))` completes in the same World.
|
|
20
|
+
The authority then reads each player's team from the verified Session.
|
|
21
|
+
|
|
22
|
+
There is no lobby object, invite code or server browser. Friends who want to
|
|
23
|
+
play together use a party (`skill/summer-parties`), which enters a queue as one group.
|
|
24
|
+
|
|
25
|
+
Where it works:
|
|
26
|
+
|
|
27
|
+
| Piece | Local Play | Hosted |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| Joining a queue, progress, failures | Yes: players are placed at once | Yes |
|
|
30
|
+
| Teams (`get_participant(session).team`) | Yes: round-robin in roster order | Yes: balanced by rating, parties kept together |
|
|
31
|
+
| Accept/Decline prompt | No: no proposal happens | Yes, when the queue declares `acceptance` |
|
|
32
|
+
| Rating-based grouping | No | Yes |
|
|
33
|
+
|
|
34
|
+
This skill extends the Courtyard game from `skill/multiplayer-project` and
|
|
35
|
+
`skill/multiplayer-state`. Its client already joins `Game.QUEUE` and sends a
|
|
36
|
+
`join` Command; its authority publishes `match_doc`.
|
|
37
|
+
|
|
38
|
+
## Steps
|
|
39
|
+
|
|
40
|
+
### 1. Declare the queues
|
|
41
|
+
|
|
42
|
+
Each queue in `runtime.queues` of `summer.build.json` names the
|
|
43
|
+
WorldDefinition its matches run. A casual mode and a 1v1 rated duel:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
"queues": [
|
|
47
|
+
{ "name": "courtyard", "minPlayers": 2, "maxPlayers": 8, "worldDefinition": "courtyard", "policy": "fifo", "transport": "enet" },
|
|
48
|
+
{
|
|
49
|
+
"name": "duel", "minPlayers": 2, "maxPlayers": 2, "worldDefinition": "courtyard",
|
|
50
|
+
"policy": "rating", "ratingSystemRef": "elo_v1",
|
|
51
|
+
"teams": { "count": 2, "size": 1 },
|
|
52
|
+
"acceptance": { "mode": "all", "ttl": "20s" },
|
|
53
|
+
"searchTtl": { "min": "30s", "default": "2m", "max": "5m" },
|
|
54
|
+
"transport": "enet"
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The fields Summer accepts, and their rules:
|
|
60
|
+
|
|
61
|
+
| Field | Meaning | Rules |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `name` | The id clients join | Unique; ASCII letters, digits, `_ . : -` |
|
|
64
|
+
| `minPlayers` / `maxPlayers` | Players per match | `minPlayers` ≥ 1, `maxPlayers` ≥ `minPlayers`. Measured support is up to 16 per match |
|
|
65
|
+
| `worldDefinition` | The WorldDefinition's `definition_id` | Required; every WorldDefinition must be used by a queue |
|
|
66
|
+
| `policy` | `fifo` (first come) or `rating` (group by skill) | `rating` needs `ratingSystemRef`; `fifo` must not declare one |
|
|
67
|
+
| `ratingSystemRef` | `elo_v1` (Summer rates two sides) or `authority_v1` (your authority reports rating moves) | See `skill/summer-leaderboards` |
|
|
68
|
+
| `teams` | `{count, size}`: fixed sides, such as 2v2 = `{2, 2}` | `count` 2..64, `size` 1..16, `minPlayers` = `maxPlayers` = count × size; `elo_v1` needs exactly 2 teams |
|
|
69
|
+
| `acceptance` | `{"mode": "all", "ttl": "20s"}` asks every player to accept; `{"mode": "none"}` skips it | `ttl` required with `all`, forbidden with `none` |
|
|
70
|
+
| `searchTtl` | `{min, default, max}` search lifetime | Go durations (`30s`, `2m`); min ≤ default ≤ max |
|
|
71
|
+
| `regions` | Regions this queue may use | Omit to allow all |
|
|
72
|
+
| `transport` | `enet` (UDP, default) or `ws` (WebSocket) | |
|
|
73
|
+
|
|
74
|
+
Don't add `lobby` fields or `backfill`: Summer rejects both when you publish.
|
|
75
|
+
Teams and acceptance need `runtime.interfaces.multiplayer` 5 or higher; the
|
|
76
|
+
project skill's `summer.build.json` declares 6.
|
|
77
|
+
|
|
78
|
+
### 2. Search, with progress the player can see
|
|
79
|
+
|
|
80
|
+
Add a status label and two hidden buttons to the client scene,
|
|
81
|
+
`client/main.tscn` (position them in the editor as you like):
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
[node name="Hud" type="CanvasLayer" parent="."]
|
|
85
|
+
|
|
86
|
+
[node name="Status" type="Label" parent="Hud"]
|
|
87
|
+
|
|
88
|
+
[node name="Accept" type="Button" parent="Hud"]
|
|
89
|
+
visible = false
|
|
90
|
+
text = "Accept"
|
|
91
|
+
|
|
92
|
+
[node name="Decline" type="Button" parent="Hud"]
|
|
93
|
+
visible = false
|
|
94
|
+
text = "Decline"
|
|
95
|
+
|
|
96
|
+
[connection signal="pressed" from="Hud/Accept" to="." method="_on_accept_pressed"]
|
|
97
|
+
[connection signal="pressed" from="Hud/Decline" to="." method="_on_decline_pressed"]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
In `client/main.gd`, replace the join in `_ready()`, from `var join :=`
|
|
101
|
+
through the `if not joined.ok:` block, with a call to `find_match`:
|
|
102
|
+
|
|
103
|
+
```gdscript
|
|
104
|
+
var joined := await find_match(Game.QUEUE)
|
|
105
|
+
if not joined.ok:
|
|
106
|
+
return
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
and add the matchmaking functions:
|
|
110
|
+
|
|
111
|
+
```gdscript
|
|
112
|
+
var proposal: SummerMatchmakingProposal # a found match waiting for Accept/Decline
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
## Searches `queue` until the player is in a match, showing progress.
|
|
116
|
+
func find_match(queue: StringName) -> SummerResult:
|
|
117
|
+
var join := Summer.client.join(SummerJoinTarget.queue(queue))
|
|
118
|
+
join.progress_changed.connect(_on_progress)
|
|
119
|
+
join.acceptance_required.connect(_on_match_found)
|
|
120
|
+
var joined: SummerResult = await join.get_result_or_completed_signal()
|
|
121
|
+
proposal = null
|
|
122
|
+
$Hud/Accept.hide()
|
|
123
|
+
$Hud/Decline.hide()
|
|
124
|
+
show_status("" if joined.ok else join_failure_text(joined))
|
|
125
|
+
return joined
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
## Stages carry no tickets or addresses, so they are safe to show.
|
|
129
|
+
func _on_progress(progress: SummerJoinProgress) -> void:
|
|
130
|
+
match progress.stage:
|
|
131
|
+
SummerJoinProgress.STAGE_RESOLVING:
|
|
132
|
+
show_status("Searching for players… %ds" % (progress.elapsed_msec / 1000))
|
|
133
|
+
SummerJoinProgress.STAGE_RESERVED, SummerJoinProgress.STAGE_PROPOSED:
|
|
134
|
+
show_status("Match found")
|
|
135
|
+
SummerJoinProgress.STAGE_LAUNCHING, SummerJoinProgress.STAGE_CONNECTING, SummerJoinProgress.STAGE_ADMITTING:
|
|
136
|
+
show_status("Joining the match…")
|
|
137
|
+
SummerJoinProgress.STAGE_RECONNECTING:
|
|
138
|
+
show_status("Reconnecting…")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
func show_status(text: String) -> void:
|
|
142
|
+
$Hud/Status.text = text
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 3. Accept or decline a found match
|
|
146
|
+
|
|
147
|
+
With `"acceptance": {"mode": "all"}`, Summer reserves a server first, then asks
|
|
148
|
+
every player. The match starts only when all accept before the deadline. The
|
|
149
|
+
engine never decides for the player: connect `acceptance_required`, or the
|
|
150
|
+
join waits until the proposal expires.
|
|
151
|
+
|
|
152
|
+
```gdscript
|
|
153
|
+
## Summer reserved a server for these players. The match starts only when
|
|
154
|
+
## everyone accepts before the deadline.
|
|
155
|
+
func _on_match_found(found: SummerMatchmakingProposal) -> void:
|
|
156
|
+
proposal = found
|
|
157
|
+
var seconds_left := maxi(0, (found.accept_by_unix_msec - int(Time.get_unix_time_from_system() * 1000.0)) / 1000)
|
|
158
|
+
show_status("Match found! Accept within %ds" % seconds_left)
|
|
159
|
+
$Hud/Accept.show()
|
|
160
|
+
$Hud/Decline.show()
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
func _on_accept_pressed() -> void: _decide(true)
|
|
164
|
+
func _on_decline_pressed() -> void: _decide(false)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
func _decide(accept: bool) -> void:
|
|
168
|
+
if proposal == null or proposal.is_decided():
|
|
169
|
+
return
|
|
170
|
+
var decided: SummerResult = proposal.accept() if accept else proposal.decline()
|
|
171
|
+
if not decided.ok:
|
|
172
|
+
show_status(join_failure_text(decided))
|
|
173
|
+
$Hud/Accept.hide()
|
|
174
|
+
$Hud/Decline.hide()
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
After an accept, `find_match` keeps waiting until everyone accepted. A
|
|
178
|
+
decline ends it with `proposal_declined`. A game that always accepts (one
|
|
179
|
+
"Play" button) connects `acceptance_required` straight to `proposal.accept()`.
|
|
180
|
+
|
|
181
|
+
### 4. Tell players what went wrong
|
|
182
|
+
|
|
183
|
+
Every failure is a `SummerResult` with a stable `code` and `retryable`:
|
|
184
|
+
|
|
185
|
+
```gdscript
|
|
186
|
+
## Turns a typed failure into words for the player. `retryable` says whether
|
|
187
|
+
## offering "Try again" makes sense.
|
|
188
|
+
func join_failure_text(result: SummerResult) -> String:
|
|
189
|
+
match String(result.code):
|
|
190
|
+
"proposal_declined":
|
|
191
|
+
return "You declined the match."
|
|
192
|
+
"proposal_expired":
|
|
193
|
+
return "The match wasn't accepted in time."
|
|
194
|
+
"queue_not_found", "queue_closed":
|
|
195
|
+
return "This mode isn't available right now."
|
|
196
|
+
"capacity_exhausted", "world_full", "world_start_timeout", "region_unavailable":
|
|
197
|
+
return "Servers are busy. Try again in a moment."
|
|
198
|
+
"rate_limited":
|
|
199
|
+
return "Too many tries. Wait a moment."
|
|
200
|
+
"cancelled":
|
|
201
|
+
return ""
|
|
202
|
+
return "Couldn't join. Try again." if result.retryable else "Couldn't join: %s" % result.message
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Other codes: `proposal_stale` and `proposal_already_decided` (a second
|
|
206
|
+
decision), `world_waking` (a persistent World is restoring), `qos_stale`,
|
|
207
|
+
`platform_error`, and `unavailable` (Local Play hosts a different queue).
|
|
208
|
+
|
|
209
|
+
### 5. Cancel the search, or leave
|
|
210
|
+
|
|
211
|
+
`Summer.client.leave()` cancels a search in flight, or leaves the current
|
|
212
|
+
match. Await it before quitting the game, so the platform knows the player is
|
|
213
|
+
gone and doesn't hold a seat for them:
|
|
214
|
+
|
|
215
|
+
```gdscript
|
|
216
|
+
## Stops searching, or leaves the current match. Await it before quitting.
|
|
217
|
+
func leave_match() -> void:
|
|
218
|
+
var left: SummerResult = await Summer.client.leave().get_result_or_completed_signal()
|
|
219
|
+
if not left.ok:
|
|
220
|
+
push_warning("leave: %s" % left.code)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
To offer "Play online" only when it can work, read
|
|
224
|
+
`Summer.client.matchmaking.availability.hosted_sessions_available` (and
|
|
225
|
+
`hosted_unavailable_reason`) before showing the button.
|
|
226
|
+
|
|
227
|
+
### 6. Read teams and the queue on the authority
|
|
228
|
+
|
|
229
|
+
The authority learns each player's team from the verified Session. In
|
|
230
|
+
`authority/main.gd`, give `match_doc` a `"teams": {}` entry and add this to
|
|
231
|
+
`_join` just before `request.accept(...)`:
|
|
232
|
+
|
|
233
|
+
```gdscript
|
|
234
|
+
# The team matchmaking seated this player on: 0..count-1, or -1 without teams.
|
|
235
|
+
var participant := Summer.authority.match.get_participant(session)
|
|
236
|
+
var team := participant.team if participant != null else -1
|
|
237
|
+
match_doc.teams[session.player.display_name] = team
|
|
238
|
+
match_group.reset(Game.pack(match_doc))
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Players learn teams only from what the authority publishes, here the shared
|
|
242
|
+
match document. `Summer.get_world().queue` on the authority names the queue
|
|
243
|
+
this match came from, so one authority scene can serve several modes.
|
|
244
|
+
|
|
245
|
+
### 7. Test it with Local Play
|
|
246
|
+
|
|
247
|
+
Local Play stands in for one queue at a time: the first one declared, or the
|
|
248
|
+
one you name. In the editor and with `summer_play`, name it with the
|
|
249
|
+
`summer/local_play/queue` project setting:
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
summer_project_setting name="summer/local_play/queue" value="duel"
|
|
253
|
+
summer_project_setting name="summer/local_play/players" value=2
|
|
254
|
+
summer_play
|
|
255
|
+
summer_get_diagnostics
|
|
256
|
+
summer_stop
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
A terminal run ignores that setting; pass the queue instead:
|
|
260
|
+
`<summer> --path . --summer-local-play 2 --summer-local-play-queue duel --summer-local-play-smoke`.
|
|
261
|
+
|
|
262
|
+
- The client must join the queue Local Play stands in for. Another queue
|
|
263
|
+
fails with `unavailable`: "Local Play is hosting queue 'courtyard'. Start
|
|
264
|
+
Local Play with queue 'duel' to test that queue."
|
|
265
|
+
- Players are placed at once: progress goes straight to `STAGE_CONNECTED`,
|
|
266
|
+
with no search and no Accept/Decline.
|
|
267
|
+
- A team queue seats players round-robin in roster order (player-1 on team 0,
|
|
268
|
+
player-2 on team 1, and so on), whatever order they connect in.
|
|
269
|
+
- `Summer.client.matchmaking.availability` reports hosted sessions as
|
|
270
|
+
available.
|
|
271
|
+
|
|
272
|
+
Test the accept prompt and rating-based grouping on a hosted staging build.
|
|
273
|
+
|
|
274
|
+
## Checklist
|
|
275
|
+
|
|
276
|
+
- [ ] Every queue names a WorldDefinition; teams queues have `minPlayers` = `maxPlayers` = count × size.
|
|
277
|
+
- [ ] The client shows search progress and a readable message for each failure code.
|
|
278
|
+
- [ ] Queues with `acceptance` show Accept/Decline with the deadline; nothing auto-accepts unless the game means to.
|
|
279
|
+
- [ ] Cancel and quit both await `Summer.client.leave()`.
|
|
280
|
+
- [ ] The authority reads `get_participant(session).team` and publishes teams to players.
|
|
281
|
+
- [ ] Local Play with `summer/local_play/queue` set runs each queue you declared.
|
|
282
|
+
|
|
283
|
+
## Common mistakes
|
|
284
|
+
|
|
285
|
+
| Don't | Do | Why |
|
|
286
|
+
|---|---|---|
|
|
287
|
+
| Build a lobby, invite codes or join-by-IP | Queues, plus parties for friends | Summer has no lobby object; invite, follow, portal and resume joins return `unsupported_placement` |
|
|
288
|
+
| Ignore `acceptance_required` on a queue with `acceptance` | Show Accept/Decline, or connect it to `proposal.accept()` | The engine never accepts for the player; the join waits until `proposal_expired` |
|
|
289
|
+
| `teams` with `minPlayers` ≠ count × size | Equal sizes: 2v2 is 4 and 4 | Summer rejects the queue when you publish |
|
|
290
|
+
| Read the team on the client from matchmaking | Publish it from the authority | Clients only learn teams through the game's own state |
|
|
291
|
+
| Join `courtyard` while Local Play stands in for `duel` | Set `summer/local_play/queue` to the queue the client joins | Local Play hosts one queue per run; the other fails `unavailable` |
|
|
292
|
+
| Quit right after "Cancel" | Await `Summer.client.leave()` first | The search may keep holding a ticket for the player |
|
|
293
|
+
| Show raw `result.message` for known codes | Map codes to your own words | Codes are stable; messages are diagnostics |
|
|
294
|
+
|
|
295
|
+
## See also
|
|
296
|
+
|
|
297
|
+
- `skill/multiplayer` — how Summer multiplayer fits together
|
|
298
|
+
- `skill/summer-match-results` — ending a match and reporting who won
|
|
299
|
+
- `skill/summer-leaderboards` — ratings and leaderboards for rated queues
|
|
300
|
+
- `skill/summer-parties` — friends entering a queue together
|
|
301
|
+
- `skill/multiplayer-testing` — Local Play options and bots
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
id: skill/summer-matchmaking
|
|
2
|
+
kind: skill
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
summary: "Get players into matches with Summer matchmaking: queues, teams, accept prompts, join progress and failures."
|
|
5
|
+
use_when:
|
|
6
|
+
- "the user wants matchmaking, a lobby, a play button, quick play, ranked or casual modes in a multiplayer Summer game"
|
|
7
|
+
- "declaring queues, player counts, teams (2v2, 5v5), rated queues or a ready check in summer.build.json"
|
|
8
|
+
- "showing search progress, an Accept/Decline prompt, cancel search or join errors to players"
|
|
9
|
+
- "the authority needs to know which team or queue each player is on"
|
|
10
|
+
facets:
|
|
11
|
+
lifecycle:
|
|
12
|
+
- build
|
|
13
|
+
domains:
|
|
14
|
+
- multiplayer
|
|
15
|
+
- ux
|
|
16
|
+
modalities:
|
|
17
|
+
- scripts
|
|
18
|
+
- project
|
|
19
|
+
compatibility:
|
|
20
|
+
engine: ">=4.6"
|
|
21
|
+
related:
|
|
22
|
+
skills:
|
|
23
|
+
- skill/multiplayer
|
|
24
|
+
- skill/multiplayer-project
|
|
25
|
+
- skill/multiplayer-testing
|
|
26
|
+
- skill/summer-match-results
|
|
27
|
+
- skill/summer-leaderboards
|
|
28
|
+
- skill/summer-parties
|
|
29
|
+
source: official
|
|
30
|
+
license: MIT
|
|
31
|
+
status: preview
|
|
32
|
+
recommended: false
|