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.
Files changed (413) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +27 -5
  3. package/.codex-plugin/plugin.json +28 -6
  4. package/.cursor-plugin/plugin.json +27 -5
  5. package/.factory-plugin/plugin.json +27 -5
  6. package/.opencode/plugins/summer.js +1 -1
  7. package/CHANGELOG.md +120 -0
  8. package/README.md +22 -7
  9. package/assets/audit/scene_audit.gd +4136 -0
  10. package/assets/seeing/seeing_probe.gd +1873 -0
  11. package/dist/cli/commands/setup.js +6 -19
  12. package/dist/cli/commands/skills.js +57 -32
  13. package/dist/core/api-client.d.ts +9 -1
  14. package/dist/core/api-client.js +21 -4
  15. package/dist/core/capabilities/agent-playbook.js +46 -0
  16. package/dist/core/capabilities/audit/accept.d.ts +55 -0
  17. package/dist/core/capabilities/audit/accept.js +147 -0
  18. package/dist/core/capabilities/audit/args.d.ts +95 -0
  19. package/dist/core/capabilities/audit/args.js +89 -0
  20. package/dist/core/capabilities/audit/audit.d.ts +56 -0
  21. package/dist/core/capabilities/audit/audit.js +287 -0
  22. package/dist/core/capabilities/audit/frame.d.ts +14 -0
  23. package/dist/core/capabilities/audit/frame.js +35 -0
  24. package/dist/core/capabilities/audit/gaps.d.ts +96 -0
  25. package/dist/core/capabilities/audit/gaps.js +257 -0
  26. package/dist/core/capabilities/audit/judge.d.ts +162 -0
  27. package/dist/core/capabilities/audit/judge.js +1134 -0
  28. package/dist/core/capabilities/audit/math.d.ts +110 -0
  29. package/dist/core/capabilities/audit/math.js +263 -0
  30. package/dist/core/capabilities/audit/summary.d.ts +74 -0
  31. package/dist/core/capabilities/audit/summary.js +212 -0
  32. package/dist/core/capabilities/board.d.ts +31 -0
  33. package/dist/core/capabilities/board.js +43 -0
  34. package/dist/core/capabilities/camera-view.d.ts +4 -2
  35. package/dist/core/capabilities/camera-view.js +6 -3
  36. package/dist/core/capabilities/connect-signal.d.ts +85 -0
  37. package/dist/core/capabilities/connect-signal.js +285 -0
  38. package/dist/core/capabilities/engine-fallbacks.d.ts +5 -0
  39. package/dist/core/capabilities/engine-fallbacks.js +5 -0
  40. package/dist/core/capabilities/engine-ops.d.ts +5 -1
  41. package/dist/core/capabilities/engine-ops.js +0 -3
  42. package/dist/core/capabilities/engine-receipt.d.ts +12 -0
  43. package/dist/core/capabilities/engine-receipt.js +26 -0
  44. package/dist/core/capabilities/file-read.d.ts +64 -0
  45. package/dist/core/capabilities/file-read.js +325 -0
  46. package/dist/core/capabilities/game-task-plan.js +4 -2
  47. package/dist/core/capabilities/grep.d.ts +60 -0
  48. package/dist/core/capabilities/grep.js +190 -0
  49. package/dist/core/capabilities/inspect-node.d.ts +56 -0
  50. package/dist/core/capabilities/inspect-node.js +225 -0
  51. package/dist/core/capabilities/inspect-resource.d.ts +61 -0
  52. package/dist/core/capabilities/inspect-resource.js +376 -0
  53. package/dist/core/capabilities/math3d.d.ts +28 -0
  54. package/dist/core/capabilities/math3d.js +88 -0
  55. package/dist/core/capabilities/motion-generation.d.ts +62 -0
  56. package/dist/core/capabilities/motion-generation.js +138 -0
  57. package/dist/core/capabilities/placement-batch.d.ts +102 -0
  58. package/dist/core/capabilities/placement-batch.js +483 -0
  59. package/dist/core/capabilities/placement-math.d.ts +70 -0
  60. package/dist/core/capabilities/placement-math.js +209 -0
  61. package/dist/core/capabilities/placement-script.d.ts +42 -0
  62. package/dist/core/capabilities/placement-script.js +1096 -0
  63. package/dist/core/capabilities/placement.d.ts +485 -0
  64. package/dist/core/capabilities/placement.js +1731 -0
  65. package/dist/core/capabilities/project-context.d.ts +33 -2
  66. package/dist/core/capabilities/project-context.js +87 -4
  67. package/dist/core/capabilities/replace-node.d.ts +91 -0
  68. package/dist/core/capabilities/replace-node.js +423 -0
  69. package/dist/core/capabilities/runtime-control.d.ts +20 -4
  70. package/dist/core/capabilities/runtime-control.js +67 -2
  71. package/dist/core/capabilities/scene-batch.d.ts +63 -0
  72. package/dist/core/capabilities/scene-batch.js +394 -0
  73. package/dist/core/capabilities/scene-readback.d.ts +36 -0
  74. package/dist/core/capabilities/scene-readback.js +54 -0
  75. package/dist/core/capabilities/seeing/args.d.ts +348 -0
  76. package/dist/core/capabilities/seeing/args.js +178 -0
  77. package/dist/core/capabilities/seeing/candidates.d.ts +127 -0
  78. package/dist/core/capabilities/seeing/candidates.js +282 -0
  79. package/dist/core/capabilities/seeing/math.d.ts +134 -0
  80. package/dist/core/capabilities/seeing/math.js +269 -0
  81. package/dist/core/capabilities/seeing/probe.d.ts +62 -0
  82. package/dist/core/capabilities/seeing/probe.js +188 -0
  83. package/dist/core/capabilities/seeing/scoring.d.ts +235 -0
  84. package/dist/core/capabilities/seeing/scoring.js +639 -0
  85. package/dist/core/capabilities/seeing/seeing.d.ts +233 -0
  86. package/dist/core/capabilities/seeing/seeing.js +1341 -0
  87. package/dist/core/capabilities/seeing/shot-store.d.ts +52 -0
  88. package/dist/core/capabilities/seeing/shot-store.js +204 -0
  89. package/dist/core/capabilities/surface-snap-mesh.d.ts +88 -0
  90. package/dist/core/capabilities/surface-snap-mesh.js +425 -0
  91. package/dist/core/capabilities/surface-snap.d.ts +77 -0
  92. package/dist/core/capabilities/surface-snap.js +461 -0
  93. package/dist/core/capabilities/tool-dispatch.js +231 -72
  94. package/dist/core/capabilities/tscn.d.ts +85 -0
  95. package/dist/core/capabilities/tscn.js +391 -0
  96. package/dist/core/capabilities/world-snapshot.d.ts +58 -0
  97. package/dist/core/capabilities/world-snapshot.js +150 -0
  98. package/dist/core/capability-skew.d.ts +5 -5
  99. package/dist/core/capability-skew.js +14 -6
  100. package/dist/core/headless/worker-engine-client.d.ts +1 -1
  101. package/dist/core/headless/worker-engine-client.js +2 -2
  102. package/dist/core/library-read.d.ts +37 -5
  103. package/dist/core/library-read.js +264 -33
  104. package/dist/core/library-search.d.ts +2 -2
  105. package/dist/core/skills-registry.d.ts +9 -1
  106. package/dist/core/skills-registry.js +25 -2
  107. package/dist/installer/agent-config.d.ts +9 -4
  108. package/dist/installer/agent-config.js +194 -392
  109. package/dist/installer/agent-table.d.ts +119 -0
  110. package/dist/installer/agent-table.js +619 -0
  111. package/dist/installer/setup.js +4 -2
  112. package/dist/installer/skill-locations.d.ts +8 -17
  113. package/dist/installer/skill-locations.js +22 -101
  114. package/dist/installer/version-check.d.ts +5 -1
  115. package/dist/installer/version-check.js +38 -28
  116. package/dist/mcp/server.js +9 -3
  117. package/dist/mcp/tools/asset-tools.js +21 -3
  118. package/dist/mcp/tools/audit-tools.d.ts +23 -0
  119. package/dist/mcp/tools/audit-tools.js +60 -0
  120. package/dist/mcp/tools/debug-tools.js +3 -1
  121. package/dist/mcp/tools/file-tools.js +17 -4
  122. package/dist/mcp/tools/generate-tools.js +74 -66
  123. package/dist/mcp/tools/library-tools.js +2 -0
  124. package/dist/mcp/tools/perception-tools.js +6 -19
  125. package/dist/mcp/tools/placement-tools.d.ts +24 -0
  126. package/dist/mcp/tools/placement-tools.js +91 -0
  127. package/dist/mcp/tools/project-tools.js +18 -15
  128. package/dist/mcp/tools/scene-tools.js +71 -83
  129. package/dist/mcp/tools/seeing-tools.d.ts +24 -0
  130. package/dist/mcp/tools/seeing-tools.js +64 -0
  131. package/dist/mcp/tools/spatial-tools.js +17 -9
  132. package/dist/mcp/tools/visual-tools.js +59 -7
  133. package/gemini-extension.json +26 -4
  134. package/integrations/README.md +48 -19
  135. package/integrations/amp/README.md +12 -0
  136. package/integrations/amp/manifest-target.json +3 -0
  137. package/integrations/antigravity/README.md +12 -0
  138. package/integrations/antigravity/manifest-target.json +3 -0
  139. package/integrations/claude-desktop/README.md +12 -0
  140. package/integrations/claude-desktop/manifest-target.json +3 -0
  141. package/integrations/cline/README.md +8 -9
  142. package/integrations/cline-cli/README.md +12 -0
  143. package/integrations/cline-cli/manifest-target.json +3 -0
  144. package/integrations/copilot-jetbrains/README.md +12 -0
  145. package/integrations/copilot-jetbrains/manifest-target.json +3 -0
  146. package/integrations/crush/README.md +12 -0
  147. package/integrations/crush/manifest-target.json +3 -0
  148. package/integrations/github-copilot/README.md +8 -8
  149. package/integrations/goose/README.md +12 -0
  150. package/integrations/goose/manifest-target.json +3 -0
  151. package/integrations/grok-build/README.md +12 -0
  152. package/integrations/grok-build/manifest-target.json +3 -0
  153. package/integrations/hermes/README.md +12 -0
  154. package/integrations/hermes/manifest-target.json +3 -0
  155. package/integrations/junie/README.md +12 -0
  156. package/integrations/junie/manifest-target.json +3 -0
  157. package/integrations/kilo-code/README.md +6 -10
  158. package/integrations/kimi-code/README.md +12 -0
  159. package/integrations/kimi-code/manifest-target.json +3 -0
  160. package/integrations/kiro/README.md +12 -0
  161. package/integrations/kiro/manifest-target.json +3 -0
  162. package/integrations/lm-studio/README.md +8 -8
  163. package/integrations/mistral-vibe/README.md +12 -0
  164. package/integrations/mistral-vibe/manifest-target.json +3 -0
  165. package/integrations/opencode/README.md +9 -12
  166. package/integrations/qoder/README.md +12 -0
  167. package/integrations/qoder/manifest-target.json +3 -0
  168. package/integrations/qwen-code/README.md +12 -0
  169. package/integrations/qwen-code/manifest-target.json +3 -0
  170. package/integrations/roo-code/README.md +8 -7
  171. package/integrations/rovo-dev/README.md +12 -0
  172. package/integrations/rovo-dev/manifest-target.json +3 -0
  173. package/integrations/trae/README.md +12 -0
  174. package/integrations/trae/manifest-target.json +3 -0
  175. package/integrations/visual-studio/README.md +12 -0
  176. package/integrations/visual-studio/manifest-target.json +3 -0
  177. package/integrations/vscode-copilot/README.md +9 -9
  178. package/integrations/warp/README.md +12 -0
  179. package/integrations/warp/manifest-target.json +3 -0
  180. package/integrations/windsurf/README.md +8 -9
  181. package/integrations/zed/README.md +12 -0
  182. package/integrations/zed/manifest-target.json +3 -0
  183. package/library/references/mcp-tools-reference/mcp-tools-reference.md +59 -15
  184. package/library/references/mcp-tools-reference/resource.yaml +1 -1
  185. package/library/references/summer-folder/resource.yaml +1 -1
  186. package/library/references/summer-folder/summer-folder.md +1 -1
  187. package/library/skills/3d-lighting/SKILL.md +1 -1
  188. package/library/skills/README.md +264 -0
  189. package/library/skills/adaptive-music/SKILL.md +1 -1
  190. package/library/skills/agent-playtesting/SKILL.md +1 -1
  191. package/library/skills/ai-characters-in-games/SKILL.md +220 -0
  192. package/library/skills/ai-characters-in-games/resource.yaml +30 -0
  193. package/library/skills/ambient-bed/SKILL.md +1 -1
  194. package/library/skills/animated-loop/SKILL.md +1 -1
  195. package/library/skills/animation-tree/SKILL.md +1 -1
  196. package/library/skills/art-direction/SKILL.md +1 -1
  197. package/library/skills/asset-strategy/SKILL.md +2 -1
  198. package/library/skills/asset-strategy/resource.yaml +2 -1
  199. package/library/skills/audio-direction/SKILL.md +1 -1
  200. package/library/skills/auto-fire-targeting/SKILL.md +1 -1
  201. package/library/skills/brainstorm-game/SKILL.md +1 -1
  202. package/library/skills/brainstorming/SKILL.md +1 -1
  203. package/library/skills/browse-templates/SKILL.md +3 -3
  204. package/library/skills/browse-templates/resource.yaml +1 -1
  205. package/library/skills/camera-rigs/SKILL.md +128 -0
  206. package/library/skills/camera-rigs/resource.yaml +29 -0
  207. package/library/skills/celeste-momentum-platforming/SKILL.md +1 -1
  208. package/library/skills/character-animation-wiring/SKILL.md +1 -1
  209. package/library/skills/character-model/SKILL.md +1 -1
  210. package/library/skills/character-movement/SKILL.md +326 -0
  211. package/library/skills/character-movement/references/default-third-person-controller.gd +201 -0
  212. package/library/skills/character-movement/references/default-third-person-verification-probe.gd +154 -0
  213. package/library/skills/character-movement/references/movement-3d.md +136 -0
  214. package/library/skills/character-movement/references/platformer-feel.md +83 -0
  215. package/library/skills/character-movement/resource.yaml +40 -0
  216. package/library/skills/character-portrait/SKILL.md +1 -1
  217. package/library/skills/cinematic-cutscene/SKILL.md +1 -1
  218. package/library/skills/combat-basics/SKILL.md +159 -0
  219. package/library/skills/combat-basics/references/projectiles-and-aoe.md +100 -0
  220. package/library/skills/combat-basics/resource.yaml +34 -0
  221. package/library/skills/concept-art/SKILL.md +1 -1
  222. package/library/skills/create-asset-sheet/SKILL.md +1 -1
  223. package/library/skills/debug/SKILL.md +1 -1
  224. package/library/skills/debugging-game-feel/SKILL.md +1 -1
  225. package/library/skills/design-level/SKILL.md +1 -1
  226. package/library/skills/design-mechanic/SKILL.md +1 -1
  227. package/library/skills/design-npc/SKILL.md +1 -1
  228. package/library/skills/diagnosing-perf-regressions/SKILL.md +1 -1
  229. package/library/skills/dispatching-parallel-agents/SKILL.md +1 -1
  230. package/library/skills/driving-the-editor-ui/SKILL.md +1 -1
  231. package/library/skills/environment-kit/SKILL.md +1 -1
  232. package/library/skills/export-and-ship/SKILL.md +1 -1
  233. package/library/skills/fabricating-assets/SKILL.md +1 -1
  234. package/library/skills/facial-and-lipsync/SKILL.md +1 -1
  235. package/library/skills/fps-controller/SKILL.md +9 -7
  236. package/library/skills/fps-controller/resource.yaml +4 -1
  237. package/library/skills/game-feel/SKILL.md +1 -1
  238. package/library/skills/gameskill/SKILL.md +1 -1
  239. package/library/skills/gdscript-patterns/SKILL.md +1 -1
  240. package/library/skills/generate-motion/SKILL.md +9 -9
  241. package/library/skills/generate-motion/resource.yaml +2 -1
  242. package/library/skills/godogen-visual-proof-game-generation/SKILL.md +1 -1
  243. package/library/skills/headless-scripting/SKILL.md +1 -1
  244. package/library/skills/in-game-purchases/SKILL.md +254 -0
  245. package/library/skills/in-game-purchases/resource.yaml +29 -0
  246. package/library/skills/instantiate-asset-pack/SKILL.md +1 -1
  247. package/library/skills/investigating-bugs/SKILL.md +1 -1
  248. package/library/skills/lumera-single-image-scene-reconstruction/SKILL.md +1 -1
  249. package/library/skills/make-game/SKILL.md +25 -3
  250. package/library/skills/make-game/references/2d-platformer-polish.md +93 -0
  251. package/library/skills/make-game/references/2d-platformer.md +117 -0
  252. package/library/skills/make-game/references/2d-rpg-content.md +234 -0
  253. package/library/skills/make-game/references/2d-rpg.md +241 -0
  254. package/library/skills/make-game/references/2d-survivors-progression.md +201 -0
  255. package/library/skills/make-game/references/2d-survivors.md +237 -0
  256. package/library/skills/make-game/references/3d-fps-systems.md +216 -0
  257. package/library/skills/make-game/references/3d-fps.md +177 -0
  258. package/library/skills/make-game/references/3d-horror-systems.md +186 -0
  259. package/library/skills/make-game/references/3d-horror.md +201 -0
  260. package/library/skills/make-game/references/3d-third-person-systems.md +220 -0
  261. package/library/skills/make-game/references/3d-third-person.md +215 -0
  262. package/library/skills/make-game/resource.yaml +6 -1
  263. package/library/skills/multiplayer/SKILL.md +150 -0
  264. package/library/skills/multiplayer/resource.yaml +33 -0
  265. package/library/skills/multiplayer-movement/SKILL.md +510 -0
  266. package/library/skills/multiplayer-movement/files.md +501 -0
  267. package/library/skills/multiplayer-movement/resource.yaml +30 -0
  268. package/library/skills/multiplayer-project/SKILL.md +521 -0
  269. package/library/skills/multiplayer-project/files.md +448 -0
  270. package/library/skills/multiplayer-project/resource.yaml +36 -0
  271. package/library/skills/multiplayer-publish/SKILL.md +229 -0
  272. package/library/skills/multiplayer-publish/export-errors.md +89 -0
  273. package/library/skills/multiplayer-publish/resource.yaml +28 -0
  274. package/library/skills/multiplayer-state/SKILL.md +438 -0
  275. package/library/skills/multiplayer-state/files.md +592 -0
  276. package/library/skills/multiplayer-state/resource.yaml +35 -0
  277. package/library/skills/multiplayer-testing/SKILL.md +236 -0
  278. package/library/skills/multiplayer-testing/resource.yaml +39 -0
  279. package/library/skills/music-track/SKILL.md +1 -1
  280. package/library/skills/navigate-summer/SKILL.md +1 -1
  281. package/library/skills/new-project/SKILL.md +1 -1
  282. package/library/skills/organic-model/SKILL.md +1 -1
  283. package/library/skills/pixel-art/SKILL.md +1 -1
  284. package/library/skills/play/SKILL.md +1 -1
  285. package/library/skills/playtesting-a-feature/SKILL.md +1 -1
  286. package/library/skills/procedural-animation/SKILL.md +2 -2
  287. package/library/skills/procedural-animation/resource.yaml +1 -1
  288. package/library/skills/prop-model/SKILL.md +1 -1
  289. package/library/skills/psx-retro-rendering/SKILL.md +1 -1
  290. package/library/skills/realtime-wet-surfaces/SKILL.md +2 -2
  291. package/library/skills/remote-deploy/SKILL.md +1 -1
  292. package/library/skills/retarget/SKILL.md +1 -1
  293. package/library/skills/running-in-the-cloud/SKILL.md +1 -1
  294. package/library/skills/save-load/SKILL.md +156 -0
  295. package/library/skills/save-load/resource.yaml +27 -0
  296. package/library/skills/scene-composition/SKILL.md +1 -1
  297. package/library/skills/scene-hierarchy-design/SKILL.md +1 -1
  298. package/library/skills/scene-scripting/SKILL.md +1 -1
  299. package/library/skills/scene-to-level/SKILL.md +1 -1
  300. package/library/skills/skill-create/SKILL.md +1 -1
  301. package/library/skills/skill-improve/SKILL.md +1 -1
  302. package/library/skills/skill-test/SKILL.md +1 -1
  303. package/library/skills/skintokens-auto-rigging/SKILL.md +1 -1
  304. package/library/skills/skybox-panorama/SKILL.md +1 -1
  305. package/library/skills/sound-effect/SKILL.md +1 -1
  306. package/library/skills/spatial-placement/SKILL.md +134 -3
  307. package/library/skills/spatial-placement/references/kit-placement-tools.md +364 -0
  308. package/library/skills/spatial-placement/resource.yaml +16 -2
  309. package/library/skills/sprite-sheet/SKILL.md +1 -1
  310. package/library/skills/summer-analytics/SKILL.md +147 -0
  311. package/library/skills/summer-analytics/resource.yaml +28 -0
  312. package/library/skills/summer-friends/SKILL.md +226 -0
  313. package/library/skills/summer-friends/resource.yaml +28 -0
  314. package/library/skills/summer-leaderboards/SKILL.md +231 -0
  315. package/library/skills/summer-leaderboards/resource.yaml +30 -0
  316. package/library/skills/summer-match-results/SKILL.md +286 -0
  317. package/library/skills/summer-match-results/resource.yaml +32 -0
  318. package/library/skills/summer-matchmaking/SKILL.md +301 -0
  319. package/library/skills/summer-matchmaking/resource.yaml +32 -0
  320. package/library/skills/summer-parties/SKILL.md +253 -0
  321. package/library/skills/summer-parties/resource.yaml +30 -0
  322. package/library/skills/summer-player-data/SKILL.md +322 -0
  323. package/library/skills/summer-player-data/resource.yaml +30 -0
  324. package/library/skills/summer-store/SKILL.md +271 -0
  325. package/library/skills/summer-store/resource.yaml +29 -0
  326. package/library/skills/summer-world-chat/SKILL.md +195 -0
  327. package/library/skills/summer-world-chat/resource.yaml +30 -0
  328. package/library/skills/summer-world-saves/SKILL.md +284 -0
  329. package/library/skills/summer-world-saves/resource.yaml +29 -0
  330. package/library/skills/text-to-motion/SKILL.md +99 -0
  331. package/library/skills/text-to-motion/prompts-and-controls.md +34 -0
  332. package/library/skills/text-to-motion/resource.yaml +41 -0
  333. package/library/skills/text-to-motion/rigging-for-text-motion.md +54 -0
  334. package/library/skills/tileable-texture/SKILL.md +1 -1
  335. package/library/skills/trailer-shot/SKILL.md +1 -1
  336. package/library/skills/tune-performance/SKILL.md +1 -1
  337. package/library/skills/ui-basics/SKILL.md +10 -1
  338. package/library/skills/ui-graphics/SKILL.md +1 -1
  339. package/library/skills/use-widget-asset/SKILL.md +1 -1
  340. package/library/skills/using-summer/SKILL.md +2 -2
  341. package/library/skills/using-summer/resource.yaml +2 -2
  342. package/library/skills/vehicle-model/SKILL.md +1 -1
  343. package/library/skills/verification-before-completion/SKILL.md +1 -1
  344. package/library/skills/verifying-scenes/SKILL.md +76 -2
  345. package/library/skills/verifying-scenes/resource.yaml +13 -2
  346. package/library/skills/vfx-dissolve/SKILL.md +1 -1
  347. package/library/skills/vfx-fire/SKILL.md +1 -1
  348. package/library/skills/vfx-hit-spark/SKILL.md +1 -1
  349. package/library/skills/vfx-lightning/SKILL.md +1 -1
  350. package/library/skills/vfx-magic-glow/SKILL.md +1 -1
  351. package/library/skills/vfx-muzzle-flash/SKILL.md +1 -1
  352. package/library/skills/vfx-smoke/SKILL.md +1 -1
  353. package/library/skills/vfx-water-ripple/SKILL.md +1 -1
  354. package/library/skills/voice-line/SKILL.md +1 -1
  355. package/library/skills/web-and-touch-input/SKILL.md +38 -0
  356. package/library/skills/web-and-touch-input/resource.yaml +28 -0
  357. package/library/skills/web-compatibility-look/SKILL.md +62 -0
  358. package/library/skills/web-compatibility-look/resource.yaml +30 -0
  359. package/library/skills/world-building-3d/SKILL.md +8 -1
  360. package/library/skills/world-building-3d/references/spatial-tools.md +1 -1
  361. package/library/skills/world-building-3d/resource.yaml +2 -1
  362. package/library/skills/writing-plans/SKILL.md +1 -1
  363. package/library/skills/writing-skills/SKILL.md +1 -1
  364. package/library/templates/3d-lan-multiplayer-starter/resource.yaml +2 -2
  365. package/library/tools/attach-to-surface/resource.yaml +142 -0
  366. package/library/tools/batch/resource.yaml +9 -3
  367. package/library/tools/connect-ports/resource.yaml +120 -0
  368. package/library/tools/connect-signal/resource.yaml +3 -2
  369. package/library/tools/debug-views/resource.yaml +94 -0
  370. package/library/tools/frame-nodes/resource.yaml +104 -0
  371. package/library/tools/frame-shot/resource.yaml +134 -0
  372. package/library/tools/generate-motion/resource.yaml +44 -9
  373. package/library/tools/get-board/resource.yaml +50 -0
  374. package/library/tools/get-project-context/resource.yaml +13 -2
  375. package/library/tools/grep/resource.yaml +77 -0
  376. package/library/tools/inspect-asset/resource.yaml +71 -0
  377. package/library/tools/inspect-node/resource.yaml +9 -2
  378. package/library/tools/inspect-resource/resource.yaml +16 -5
  379. package/library/tools/instantiate-scene/resource.yaml +27 -2
  380. package/library/tools/measure/resource.yaml +96 -0
  381. package/library/tools/place-adjacent/resource.yaml +113 -0
  382. package/library/tools/play/resource.yaml +15 -2
  383. package/library/tools/raycast/resource.yaml +101 -0
  384. package/library/tools/read-file/resource.yaml +36 -2
  385. package/library/tools/read-library/resource.yaml +2 -2
  386. package/library/tools/repeat-along/resource.yaml +129 -0
  387. package/library/tools/replace-node/resource.yaml +11 -5
  388. package/library/tools/scene-audit/resource.yaml +144 -0
  389. package/library/tools/screenshot/resource.yaml +13 -2
  390. package/library/tools/search-assets/resource.yaml +18 -0
  391. package/library/tools/shot-sheet/resource.yaml +110 -0
  392. package/library/tools/snap-to-surface/resource.yaml +4 -1
  393. package/library/tools/world-snapshot/resource.yaml +24 -3
  394. package/library/tools/zoom/resource.yaml +105 -0
  395. package/package.json +3 -3
  396. package/registry/generated/aliases.json +17 -3
  397. package/registry/generated/counts.json +3 -3
  398. package/registry/generated/gemini-extension.json +26 -4
  399. package/registry/generated/index.json +2154 -469
  400. package/registry/generated/marketplace.claude.json +2 -2
  401. package/registry/generated/plugin.claude.json +27 -5
  402. package/registry/generated/plugin.codex.json +28 -6
  403. package/registry/generated/plugin.cursor.json +27 -5
  404. package/registry/generated/plugin.factory.json +27 -5
  405. package/registry/generated/skills-index.md +264 -0
  406. package/registry/generated/skills-registry.json +901 -206
  407. package/registry/generated/templates-registry.json +2 -2
  408. package/library/skills/host-authoritative-state/SKILL.md +0 -332
  409. package/library/skills/host-authoritative-state/resource.yaml +0 -28
  410. package/library/skills/peer-to-peer-multiplayer/SKILL.md +0 -373
  411. package/library/skills/peer-to-peer-multiplayer/resource.yaml +0 -28
  412. package/library/skills/setup-multiplayer/SKILL.md +0 -336
  413. package/library/skills/setup-multiplayer/resource.yaml +0 -28
@@ -0,0 +1,253 @@
1
+ ---
2
+ name: summer-parties
3
+ description: "Let a Summer party play together: the leader presses Play, members follow into the same match. Hosted only."
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_get_script_errors summer_project_setting summer_play summer_stop summer_get_diagnostics
9
+ paths: ["**/*.gd", "summer.build.json"]
10
+ ---
11
+
12
+ # /summer-parties — play together with a Summer party
13
+
14
+ ## Overview
15
+
16
+ A Summer party belongs to the player's Summer account, not to your game. The
17
+ Summer app creates parties, sends invites, kicks, transfers leadership and runs
18
+ party chat. Your game sees a read-only view through `Summer.client.party`:
19
+
20
+ - who is in the party, who leads, and which members have **this** game open;
21
+ - small text data your game shares with the party;
22
+ - whether the leader started a search in one of your queues.
23
+
24
+ To bring the party into a match, the **leader** joins
25
+ `SummerJoinTarget.party_queue(queue)`. Every other member joins the **same
26
+ target** when `search_started` fires. Summer puts them all in one match, each
27
+ with their own Session. The match itself is ordinary: the authority sees
28
+ normal `join` Commands and Sessions.
29
+
30
+ **Hosted only.** `is_available()` is `false` and every call fails with
31
+ `capability_unavailable`:
32
+
33
+ - in Local Play and editor runs;
34
+ - for guests;
35
+ - for accounts the platform keeps out of social features (`age_restricted`, under 13);
36
+ - in Summer apps without the `player.party@1` capability.
37
+
38
+ Outside a party, or when parties are unavailable, getters are empty and
39
+ `is_in_party()` is `false`. So the same Play button works everywhere: no party
40
+ means an ordinary `SummerJoinTarget.queue(...)` join. Build that path first.
41
+
42
+ This skill extends the Courtyard game from `skill/multiplayer-project`
43
+ (`network/game.gd` as `Game`, `client/main.gd` with `command()`).
44
+
45
+ ## Steps
46
+
47
+ ### 1. Join from a Play button, not from `_ready`
48
+
49
+ A party member must not join on their own the moment the game starts. Move the
50
+ join out of `client/main.gd`'s `_ready` into `enter(target)`, and keep every
51
+ signal connection from `skill/multiplayer-movement` and `skill/multiplayer-state`:
52
+
53
+ ```gdscript
54
+ func _ready() -> void:
55
+ spawner = Game.build_network(self, PlayerArchetype)
56
+ # Connect before joining, or the first values arrive unseen.
57
+ spawner.state_group_created.connect(_on_group_created)
58
+ spawner.entity_spawned.connect(_on_entity_spawned)
59
+ spawner.event_received.connect(_on_event)
60
+ var initialized: SummerResult = await Summer.initialize(Game.GAME_ID).get_result_or_completed_signal()
61
+ if not initialized.ok:
62
+ push_error(initialized.message)
63
+ return
64
+ $Party.start()
65
+ # With parties, the Play button calls $Party.play(). Without them (Local
66
+ # Play, guests, apps without parties) the player plays solo right away.
67
+ if not Summer.client.party.is_available():
68
+ await enter(SummerJoinTarget.queue(Game.QUEUE))
69
+
70
+
71
+ ## Joins a match through `target`, then enters it. True once the player is in.
72
+ func enter(target: SummerJoinTarget) -> bool:
73
+ var join := Summer.client.join(target)
74
+ # Hosted matchmaking may ask the player to accept a match.
75
+ join.acceptance_required.connect(func(proposal: SummerMatchmakingProposal) -> void: proposal.accept())
76
+ var joined: SummerResult = await join.get_result_or_completed_signal()
77
+ if not joined.ok:
78
+ push_warning("join failed: %s %s" % [joined.code, joined.message])
79
+ return false
80
+ if not spawner.is_network_ready():
81
+ await spawner.network_ready
82
+ var entered := await command({"c": "join"})
83
+ if not entered.ok:
84
+ push_error("could not enter the match: %s" % entered.error)
85
+ return false
86
+ print("COURTYARD_CLIENT_JOINED")
87
+ return true
88
+ ```
89
+
90
+ Move the Local Play bot hook from `skill/multiplayer-testing` into `enter()`,
91
+ after the joined line.
92
+
93
+ ### 2. Add the party node
94
+
95
+ `client/party.gd`, added as a child `Party` of `Main` in `client/main.tscn`.
96
+ It is client-only: authority scenes never touch `Summer.client`.
97
+
98
+ ```gdscript
99
+ extends Node
100
+ ## Plays with the player's Summer party. The leader presses Play; every member
101
+ ## follows into the same match. Without a party, Play joins the queue alone.
102
+
103
+ const Game = preload("res://network/game.gd")
104
+
105
+ signal roster_changed(lines: PackedStringArray)
106
+ signal status(text: String)
107
+
108
+
109
+ ## Main calls this right after Summer.initialize succeeds: a child's _ready
110
+ ## runs before that, while Summer.client is still null.
111
+ func start() -> void:
112
+ var party := Summer.client.party
113
+ party.search_started.connect(_on_search_started)
114
+ party.changed.connect(_render)
115
+ _render()
116
+
117
+
118
+ ## The Play button.
119
+ func play() -> void:
120
+ var party := Summer.client.party
121
+ if not party.is_in_party():
122
+ await get_parent().enter(SummerJoinTarget.queue(Game.QUEUE))
123
+ elif party.is_leader():
124
+ await get_parent().enter(SummerJoinTarget.party_queue(Game.QUEUE))
125
+ else:
126
+ status.emit("Waiting for %s to press Play." % party.get_leader().display_name)
127
+
128
+
129
+ ## The leader started a search: members follow with the same target.
130
+ func _on_search_started(queue: StringName) -> void:
131
+ if not Summer.client.party.is_leader():
132
+ await get_parent().enter(SummerJoinTarget.party_queue(queue))
133
+
134
+
135
+ func _render() -> void:
136
+ var lines := PackedStringArray()
137
+ for member in Summer.client.party.get_members():
138
+ var tags := []
139
+ if member.leader:
140
+ tags.append("leader")
141
+ if not member.in_game:
142
+ tags.append("not in game")
143
+ if member.get_data("ready") == "yes":
144
+ tags.append("ready")
145
+ lines.append("%s %s" % [member.display_name, ", ".join(tags)])
146
+ roster_changed.emit(lines)
147
+
148
+
149
+ ## The Invite button: show it only when the Summer app offers parties.
150
+ func invite() -> void:
151
+ var shown: SummerResult = await Summer.client.party.show_invite_dialog().get_result_or_completed_signal()
152
+ if not shown.ok:
153
+ status.emit("Invites are not available right now.")
154
+
155
+
156
+ ## A member's Ready toggle, visible to the whole party.
157
+ func set_ready(ready: bool) -> void:
158
+ var saved: SummerResult = await Summer.client.party.set_member_data("ready", "yes" if ready else "").get_result_or_completed_signal()
159
+ if not saved.ok:
160
+ status.emit("Could not update ready: %s" % saved.code)
161
+ ```
162
+
163
+ Connect `roster_changed` to a `Label` or `ItemList`, and `status` to a status
164
+ line. Show the buttons by state:
165
+
166
+ | Button | Show when |
167
+ | --- | --- |
168
+ | Play | not in a party, or leader. A member sees "waiting for the leader" instead |
169
+ | Invite friends | `Summer.client.party.is_available()` |
170
+ | Party | `is_available()` and `is_in_party()`: `show_party_dialog()` opens leave, kick and leadership in the Summer app |
171
+ | Ready | a member in a party |
172
+
173
+ ### 3. Share choices with party data
174
+
175
+ Party data is plain text that your game reads from every member's copy of the
176
+ party. Use it for lobby-style choices before the match starts:
177
+
178
+ - **Leader's choices** (map, mode): `party.set_data(key, value)`, read with `party.get_data(key)`. Only the leader can write it; a member gets `not_party_leader`.
179
+ - **Each member's own choices** (ready, character): `party.set_member_data(key, value)`, read with `member.get_data(key)`.
180
+
181
+ Limits:
182
+
183
+ | What | Limit |
184
+ | --- | --- |
185
+ | Keys | 1–32 characters from `a-z 0-9 _ . -` |
186
+ | Values | at most 256 UTF-8 bytes of text, no control characters; an empty value deletes the key |
187
+ | Leader keys | 16 per game |
188
+ | Member keys | 8 per member per game |
189
+
190
+ Values pass the Summer chat text filter, so `text_not_allowed` is possible.
191
+ Writes return `SummerOperation`. A conflict is retried once by the engine, then
192
+ fails with retryable `party_version_conflict`. React to `data_changed(key)` and
193
+ `member_data_changed(member, key)`, or just re-render on `changed`.
194
+
195
+ Party data is a lobby convenience, not game state. Once the match starts, the
196
+ authority owns everything (`skill/multiplayer-state`). It never sees party data;
197
+ send choices that matter in your `join` Command and validate them there.
198
+
199
+ ### 4. Handle join failures
200
+
201
+ `enter()` returns `false` with the code in the warning. For a party join:
202
+
203
+ | Code | Who | What to show |
204
+ | --- | --- | --- |
205
+ | `party_member_offline` (retryable) | leader | "Everyone in your party needs the game open." `member.in_game` tells you who |
206
+ | `party_not_searching` (retryable) | member | Nothing; wait for the next `search_started` |
207
+ | `party_not_found` | anyone | The party ended; fall back to a solo Play |
208
+ | `capability_unavailable` | anyone | Parties aren't offered here; use the solo path |
209
+
210
+ A member can follow only while the leader's search is running: from
211
+ `search_started` until `search_ended`.
212
+
213
+ ### 5. Test
214
+
215
+ Local Play cannot create a party, so `is_available()` is `false` and each
216
+ client joins solo from `_ready`. Test that path locally, then the party path
217
+ on Summer staging with real accounts.
218
+
219
+ ```
220
+ summer_project_setting name="summer/local_play/players" value=2
221
+ summer_play
222
+ summer_get_diagnostics
223
+ summer_stop
224
+ ```
225
+
226
+ ## Checklist
227
+
228
+ - [ ] Local Play: `Summer.client.party.is_available()` is `false`, Play joins through the plain queue, and no script errors appear.
229
+ - [ ] Invite and Party buttons are hidden when `is_available()` is `false`.
230
+ - [ ] A member's Play button doesn't join on its own.
231
+ - [ ] Hosted: the leader presses Play, and every member with the game open lands in the same match.
232
+ - [ ] A member who closes the game makes the leader's Play fail with `party_member_offline`, and the message names who is missing.
233
+ - [ ] Ready and leader choices appear on every member's screen.
234
+
235
+ ## Common mistakes
236
+
237
+ | Don't | Do | Why |
238
+ | --- | --- | --- |
239
+ | Join in `_ready` while parties are available | Join from Play via `enter(target)` | A member joining alone splits the party |
240
+ | Members join `SummerJoinTarget.queue(...)` | Members join `party_queue(queue)` on `search_started` | Only the party target attaches them to the leader's search |
241
+ | Show Invite when `is_available()` is false | Hide it | Local Play, guests and under-13 accounts have no parties |
242
+ | Build your own invite list or party chat | `show_invite_dialog()` / `show_party_dialog()` | The Summer app owns invites, kicks, leadership and chat |
243
+ | Hold `SummerPartyMember` objects | Key UI by `member.user_id` and re-read on `changed` | Each snapshot replaces the member objects |
244
+ | Trust party data on the authority | Send choices in the `join` Command and validate | The authority never sees party data |
245
+ | Put secrets or long text in party data | Short public choices (`map`, `ready`) | 256 bytes, filtered, visible to every member |
246
+ | `await op.completed` | `await op.get_result_or_completed_signal()` | Fast failures complete before you can connect |
247
+
248
+ ## See also
249
+
250
+ - `skill/summer-matchmaking`: queues, sizes, teams and the accept prompt
251
+ - `skill/summer-friends`: friends list, profiles and messages
252
+ - `skill/multiplayer-project`: the client entry scene this extends
253
+ - `skill/multiplayer-testing`: Local Play, and what needs staging
@@ -0,0 +1,30 @@
1
+ id: skill/summer-parties
2
+ kind: skill
3
+ version: 1.0.0
4
+ summary: "Let a Summer party play together: the leader presses Play, members follow into the same match. Hosted only."
5
+ use_when:
6
+ - "friends in a Summer party should land in the same multiplayer match: party play, play with friends, squad up"
7
+ - "adding invite-friends and party buttons, a party roster or ready checks to a Summer game's menu"
8
+ - "a party join fails with party_member_offline, party_not_searching or capability_unavailable"
9
+ facets:
10
+ lifecycle:
11
+ - build
12
+ domains:
13
+ - multiplayer
14
+ - ui
15
+ modalities:
16
+ - scripts
17
+ compatibility:
18
+ engine: ">=4.6"
19
+ related:
20
+ skills:
21
+ - skill/multiplayer
22
+ - skill/multiplayer-project
23
+ - skill/multiplayer-testing
24
+ - skill/summer-matchmaking
25
+ - skill/summer-friends
26
+ - skill/summer-world-chat
27
+ source: official
28
+ license: MIT
29
+ status: preview
30
+ recommended: false
@@ -0,0 +1,322 @@
1
+ ---
2
+ name: summer-player-data
3
+ description: "Save a player's progress so it follows them to every match: authority load and commit, autosave, safe retries."
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_get_script_errors summer_project_setting summer_play summer_stop summer_get_diagnostics
9
+ paths: ["**/*.gd", "world.json", "summer.build.json"]
10
+ ---
11
+
12
+ # /summer-player-data — progress that follows the player
13
+
14
+ ## Overview
15
+
16
+ Player data is what Summer remembers about **one player in one game**:
17
+ coins, unlocks, level, settings. It belongs to the player, not to a match.
18
+ A match World ends and its state is gone. The player's data comes back in
19
+ the next match, on any server and any device.
20
+
21
+ - **Only the authority writes it.** Clients never write player data.
22
+ - **Two slots per player**, written separately:
23
+ - `public`: the authority reads and writes it. The player's own client can read it too, for example to show coins in the menu.
24
+ - `secret`: only the authority reads or writes it, for hidden values such as anti-cheat counters or hidden ratings. It never reaches a client.
25
+ - Nobody else can read either slot.
26
+ - **Each slot is one JSON object of at most 64 KiB.**
27
+ - Allowed values: `bool`, `int`, finite `float`, `String`, `Array` and `Dictionary` with `String` keys, nested at most 16 deep.
28
+ - `Vector3`, `Color` and `null` fail before anything is sent.
29
+ - Whole numbers load back as `int`.
30
+
31
+ **Where it works:**
32
+
33
+ | | Local Play | Hosted |
34
+ |---|---|---|
35
+ | Authority `load` / `commit_public` / `commit_secret` | Yes: a file store under `user://summer_local_player_data/<game id>/<persona>/` | Yes |
36
+ | Client `Summer.client.player_data.load()` (own public slot) | Yes, from the same files | Yes |
37
+
38
+ Local Play's store keeps the latest value plus five earlier revisions, with
39
+ the same validation and `save_id` rules as hosted. Delete a persona's folder
40
+ to start that player fresh. Only hosted play proves durability.
41
+
42
+ This skill extends the game from `skill/multiplayer-project` and
43
+ `skill/multiplayer-state`: the authority's `_join` Command handler, the
44
+ private `mine` State group, and the `wallets` it keeps.
45
+
46
+ ## Steps
47
+
48
+ ### 1. Decide what to save
49
+
50
+ Save only what must outlive the match, and save it as a plain document:
51
+
52
+ | Save it | Don't |
53
+ |---|---|
54
+ | Coins, unlocks, best score, cosmetics owned, settings | Positions, health, the round's score: that is match state |
55
+ | A secret anti-cheat counter (`commit_secret`) | Anything another player needs to see: that is a State group |
56
+
57
+ Keep values that must change together in **one** slot: there is no
58
+ transaction across the two slots.
59
+
60
+ ### 2. Load when the player arrives
61
+
62
+ The authority starts loading on `session_joined`, before the client's
63
+ `join` Command arrives. Add to `authority/main.gd`:
64
+
65
+ ```gdscript
66
+ const AUTOSAVE_SECONDS := 5.0
67
+ ## session_id -> {"session", "state": loading|ready|failed|superseded, "data",
68
+ ## "dirty", "saving", "saves", "save_prefix", "left"}
69
+ var profiles := {}
70
+ ```
71
+
72
+ In `_ready()`, next to the other `world` signals, before `Summer.initialize`
73
+ (connect `session_joined` only once if `skill/summer-match-results` already
74
+ did):
75
+
76
+ ```gdscript
77
+ world.session_joined.connect(_on_session_joined)
78
+ world.draining.connect(func(_deadline: int) -> void: _save_all())
79
+ var autosave := Timer.new()
80
+ autosave.wait_time = AUTOSAVE_SECONDS
81
+ autosave.timeout.connect(_save_all)
82
+ add_child(autosave)
83
+ autosave.start()
84
+ ```
85
+
86
+ If `_on_session_joined` already exists (`skill/summer-match-results` adds
87
+ one), put this body at its end instead of defining it twice.
88
+
89
+ ```gdscript
90
+ ## Reads the player's saved progress as soon as their Session arrives.
91
+ func _on_session_joined(session: SummerSession) -> void:
92
+ # save_ids must never repeat for this player, in this World or any later one.
93
+ var prefix := str(int(Time.get_unix_time_from_system() * 1000.0))
94
+ var profile := {"session": session, "state": "loading", "data": {}, "dirty": false, "saving": false, "saves": 0, "save_prefix": prefix, "left": false}
95
+ profiles[session.session_id] = profile
96
+ for attempt in 3:
97
+ var loaded := Summer.authority.player_data.load(session)
98
+ var result: SummerResult = await loaded.get_result_or_completed_signal()
99
+ if result.ok:
100
+ profile.data = loaded.public_record.data
101
+ profile.state = "ready"
102
+ return
103
+ if not result.retryable:
104
+ break
105
+ await get_tree().create_timer(1.0 + attempt).timeout
106
+ # Unknown is not empty: never start a fresh save over data you could not read.
107
+ profile.state = "failed"
108
+ push_warning("player data load failed for %s" % session.session_id)
109
+ ```
110
+
111
+ `loaded.secret_record.data` holds the secret slot. A slot that was never
112
+ written has `exists == false` and empty `data`.
113
+
114
+ ### 3. Make `join` wait for the load
115
+
116
+ In `_join`, right after the seat check:
117
+
118
+ ```gdscript
119
+ # The client retries "join" while this player's saved progress loads.
120
+ var profile: Dictionary = profiles.get(session.session_id, {"state": "loading"})
121
+ if profile.state != "ready":
122
+ request.refuse(&"loading" if profile.state == "loading" else &"profile_unavailable")
123
+ return
124
+ ```
125
+
126
+ Start the player from their saved values instead of zero, in the block that
127
+ creates their private group:
128
+
129
+ ```gdscript
130
+ if not mine_groups.has(session.session_id):
131
+ wallets[session.session_id] = int(profile.data.get("coins", 0))
132
+ mine_groups[session.session_id] = spawner.create_state_group(Game.MINE, StringName("mine%d" % seat), Game.pack({"coins": wallets[session.session_id]}), null, null, session)
133
+ ```
134
+
135
+ On the client, in `client/main.gd`, retry `join` while the answer is
136
+ `loading`:
137
+
138
+ ```gdscript
139
+ var entered := await command({"c": "join"})
140
+ while not entered.ok and entered.error == "loading":
141
+ await get_tree().create_timer(0.5).timeout
142
+ entered = await command({"c": "join"})
143
+ ```
144
+
145
+ `profile_unavailable` means the load failed. Show "Couldn't load your
146
+ progress", and do **not** let the player play on a blank profile that would
147
+ overwrite their real one.
148
+
149
+ ### 4. Mark changes, autosave, flush on leave
150
+
151
+ Wherever progress changes (here, after the wallet changes in `_collect`):
152
+
153
+ ```gdscript
154
+ _progress_changed(session.session_id, {"coins": wallets[session.session_id]})
155
+ ```
156
+
157
+ At the top of `_on_session_left`:
158
+
159
+ ```gdscript
160
+ _player_left(session.session_id)
161
+ ```
162
+
163
+ The saving code:
164
+
165
+ ```gdscript
166
+ ## Records new progress. The autosave writes it a few seconds later.
167
+ func _progress_changed(session_id: String, changes: Dictionary) -> void:
168
+ var profile: Dictionary = profiles.get(session_id, {})
169
+ if profile.get("state", "") == "ready":
170
+ profile.data.merge(changes, true)
171
+ profile.dirty = true
172
+
173
+
174
+ func _save_all() -> void:
175
+ for session_id in profiles.keys():
176
+ _save(session_id)
177
+
178
+
179
+ ## One commit per player at a time. Each new save gets a new save_id; a retry
180
+ ## of the same save reuses its id and data, so it is never applied twice.
181
+ func _save(session_id: String) -> void:
182
+ var profile: Dictionary = profiles.get(session_id, {})
183
+ if profile.is_empty() or profile.saving or not profile.dirty or profile.state != "ready":
184
+ return
185
+ profile.saving = true
186
+ while profile.dirty and profile.state == "ready":
187
+ profile.dirty = false
188
+ profile.saves += 1
189
+ var save_id := "%s-%d" % [profile.save_prefix, profile.saves]
190
+ if not await _commit(profile, save_id, profile.data.duplicate(true)):
191
+ break
192
+ profile.saving = false
193
+ if profile.left and not profile.dirty:
194
+ profiles.erase(session_id) # gone and fully saved
195
+
196
+
197
+ func _commit(profile: Dictionary, save_id: String, data: Dictionary) -> bool:
198
+ for attempt in 4:
199
+ var commit := Summer.authority.player_data.commit_public(profile.session, save_id, data)
200
+ var result: SummerResult = await commit.get_result_or_completed_signal()
201
+ if result.ok:
202
+ return true
203
+ if result.code == &"player_data_superseded":
204
+ profile.state = "superseded" # a newer Session owns this player now
205
+ return false
206
+ if not result.retryable:
207
+ push_error("player data save %s failed: %s" % [save_id, result.code])
208
+ return false
209
+ await get_tree().create_timer(1.0 + attempt).timeout # same save_id, same data
210
+ profile.dirty = true # still unsaved; the next autosave tries again
211
+ return false
212
+
213
+
214
+ ## Commits stay open after a player leaves, so flush their progress now.
215
+ func _player_left(session_id: String) -> void:
216
+ var profile: Dictionary = profiles.get(session_id, {})
217
+ if profile.is_empty():
218
+ return
219
+ profile.left = true
220
+ if profile.dirty:
221
+ _save(session_id)
222
+ elif not profile.saving:
223
+ profiles.erase(session_id)
224
+ ```
225
+
226
+ Why it is shaped this way:
227
+
228
+ - **Autosave, not only save-on-leave.** A crash delivers no signal. With a 5 s autosave a crash loses at most 5 s.
229
+ - **One commit per player and slot at a time.** A second concurrent commit fails with `operation_in_progress`.
230
+ - **A new `save_id` for each new save, the same `save_id` for a retry.**
231
+ - Resending a `save_id` with the same data returns the original revision (`commit.replayed`).
232
+ - The same `save_id` with different data fails with `player_data_save_id_conflict`.
233
+ - Build the id from the time the Session joined plus a counter. Session ids are not enough: Local Play reuses them on every run.
234
+ - **`player_data_superseded` is final.** The player joined a newer World, and only their newest Session may write.
235
+ - **`draining`** fires before the World shuts down. Commits are still accepted until the stop deadline, so flush everyone then.
236
+ - **Under Local Play a player who quits only disconnects.** Their seat is held and `session_left` arrives when the World ends, so the autosave is what saves them.
237
+
238
+ Use `commit_secret` in the same way for the secret slot. Each slot has its own
239
+ revisions and `save_id` space.
240
+
241
+ ### 5. Show saved progress in the menu (optional)
242
+
243
+ The client reads its own public slot, even before joining a match:
244
+
245
+ ```gdscript
246
+ ## The client can read its own public slot, for example to show progress in
247
+ ## the menu. It never writes player data.
248
+ func _show_saved_progress() -> void:
249
+ var saved := Summer.client.player_data.load()
250
+ var result: SummerResult = await saved.get_result_or_completed_signal()
251
+ if result.ok:
252
+ $Menu/Coins.text = "Coins: %d" % int(saved.public_record.data.get("coins", 0))
253
+ ```
254
+
255
+ ### 6. Keep the publish checks passing
256
+
257
+ The source-graph check in `skill/multiplayer-publish` (step 4b) flags every
258
+ call ending in `load(` without a string literal inside, so
259
+ `Summer.authority.player_data.load(session)` and
260
+ `Summer.client.player_data.load()` report
261
+ `SUMMER_COMPOSITION_DYNAMIC_PATH_UNDECLARED`. They load no file. Declare one
262
+ dependency edge per file that makes such a call, in `source-domains.json`:
263
+
264
+ ```json
265
+ "dependencies": [
266
+ { "from": "authority/main.gd", "to": "network/game.gd" },
267
+ { "from": "client/main.gd", "to": "network/game.gd" }
268
+ ]
269
+ ```
270
+
271
+ ### 7. Test persistence: run Local Play twice
272
+
273
+ Run the same session twice. Local Play personas (`player-1`, `player-2`)
274
+ keep their files between runs, so run 2 must start where run 1 ended.
275
+
276
+ ```
277
+ summer_project_setting name="summer/local_play/players" value=2
278
+ summer_play
279
+ summer_get_diagnostics
280
+ summer_stop
281
+ summer_play
282
+ summer_get_diagnostics
283
+ summer_stop
284
+ ```
285
+
286
+ From a terminal: `<summer> --path . --summer-local-play 2 --summer-local-play-headless --summer-local-play-timeout 20 -- --bot={client}`,
287
+ twice (see `skill/multiplayer-testing` for bot mode). Print markers such as
288
+ `print("PROFILE_LOADED coins=", ...)` and `print("SAVED ", save_id, " revision=", commit.revision)` while testing, then check them.
289
+
290
+ Files: `~/Library/Application Support/Godot/app_userdata/<project name>/summer_local_player_data/<game id>/<persona>/public.json`
291
+ on macOS (the project's `user://` folder elsewhere). Delete a persona's
292
+ folder to start over.
293
+
294
+ ## Checklist
295
+
296
+ - [ ] Run 1: a player earns something, and the authority logs a successful save within the autosave interval.
297
+ - [ ] Run 2: the authority loads the saved value at join; the client menu shows it.
298
+ - [ ] A load failure refuses `join` with `profile_unavailable`; nothing is written over the old data.
299
+ - [ ] No `player_data_save_id_conflict` in the logs, even on the second run.
300
+ - [ ] The saved document stays well under 64 KiB at its largest. Test the maximum.
301
+
302
+ ## Common mistakes
303
+
304
+ | Don't | Do | Why |
305
+ |---|---|---|
306
+ | Write player data from the client | Commit on the authority | Clients have no write API; the authority is the trust boundary |
307
+ | Treat a failed load as a new player | Refuse `join` with `profile_unavailable` | The next save would wipe their real progress |
308
+ | Save only when the player leaves | Autosave dirty players every few seconds, and flush on `session_left` and `draining` | Crashes deliver no signal |
309
+ | Reuse one `save_id` for new data | A new id per save, the same id per retry | Same id plus different data is `player_data_save_id_conflict` |
310
+ | Build `save_id` from the session id alone | Join time plus a counter | Local Play reuses session ids across runs |
311
+ | Start a second commit while one is pending | One commit per player and slot at a time | The second fails with `operation_in_progress` |
312
+ | Keep retrying after `player_data_superseded` | Stop saving through that Session | A newer World owns the player now |
313
+ | Store `Vector3`, `Color` or `null` | Plain JSON: numbers, strings, arrays, dictionaries | Unsupported types fail with `player_data_unsupported_type` |
314
+ | Save the match (positions, round score) | Save only what outlives the match | The World ends; `skill/summer-world-saves` covers Worlds that persist |
315
+ | Truncate data that grew past 64 KiB | Refuse to grow; test the maximum size | `player_data_too_large` rejects the whole commit |
316
+
317
+ ## See also
318
+
319
+ - `skill/multiplayer-state` — the wallet and private State group this skill saves
320
+ - `skill/summer-world-saves` — saving a whole persistent World instead of one player
321
+ - `skill/multiplayer-testing` — bots and repeated Local Play runs
322
+ - `skill/summer-leaderboards` — scores other players can see
@@ -0,0 +1,30 @@
1
+ id: skill/summer-player-data
2
+ kind: skill
3
+ version: 1.0.0
4
+ summary: "Save a player's progress so it follows them to every match: authority load and commit, autosave, safe retries."
5
+ use_when:
6
+ - "saving player progress, coins, unlocks, levels or settings in a multiplayer Summer game so they persist between matches"
7
+ - "a player's progress resets every match, or saves fail with save_id_conflict, superseded or operation_in_progress"
8
+ - "showing a player's saved progress in the menu before they join a match"
9
+ facets:
10
+ lifecycle:
11
+ - build
12
+ domains:
13
+ - multiplayer
14
+ - scripting
15
+ modalities:
16
+ - scripts
17
+ compatibility:
18
+ engine: ">=4.6"
19
+ related:
20
+ skills:
21
+ - skill/multiplayer
22
+ - skill/multiplayer-state
23
+ - skill/multiplayer-testing
24
+ - skill/summer-world-saves
25
+ - skill/summer-leaderboards
26
+ - skill/summer-store
27
+ source: official
28
+ license: MIT
29
+ status: stable
30
+ recommended: false