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.
Files changed (366) 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 +102 -0
  8. package/README.md +18 -3
  9. package/assets/audit/scene_audit.gd +4136 -0
  10. package/assets/seeing/seeing_probe.gd +1873 -0
  11. package/dist/cli/commands/skills.js +20 -2
  12. package/dist/core/api-client.d.ts +9 -1
  13. package/dist/core/api-client.js +21 -4
  14. package/dist/core/capabilities/agent-playbook.js +46 -0
  15. package/dist/core/capabilities/audit/accept.d.ts +55 -0
  16. package/dist/core/capabilities/audit/accept.js +147 -0
  17. package/dist/core/capabilities/audit/args.d.ts +95 -0
  18. package/dist/core/capabilities/audit/args.js +89 -0
  19. package/dist/core/capabilities/audit/audit.d.ts +56 -0
  20. package/dist/core/capabilities/audit/audit.js +287 -0
  21. package/dist/core/capabilities/audit/frame.d.ts +14 -0
  22. package/dist/core/capabilities/audit/frame.js +35 -0
  23. package/dist/core/capabilities/audit/gaps.d.ts +96 -0
  24. package/dist/core/capabilities/audit/gaps.js +257 -0
  25. package/dist/core/capabilities/audit/judge.d.ts +162 -0
  26. package/dist/core/capabilities/audit/judge.js +1134 -0
  27. package/dist/core/capabilities/audit/math.d.ts +110 -0
  28. package/dist/core/capabilities/audit/math.js +263 -0
  29. package/dist/core/capabilities/audit/summary.d.ts +74 -0
  30. package/dist/core/capabilities/audit/summary.js +212 -0
  31. package/dist/core/capabilities/board.d.ts +31 -0
  32. package/dist/core/capabilities/board.js +43 -0
  33. package/dist/core/capabilities/camera-view.d.ts +4 -2
  34. package/dist/core/capabilities/camera-view.js +6 -3
  35. package/dist/core/capabilities/connect-signal.d.ts +85 -0
  36. package/dist/core/capabilities/connect-signal.js +285 -0
  37. package/dist/core/capabilities/engine-fallbacks.d.ts +5 -0
  38. package/dist/core/capabilities/engine-fallbacks.js +5 -0
  39. package/dist/core/capabilities/engine-ops.d.ts +5 -1
  40. package/dist/core/capabilities/engine-ops.js +0 -3
  41. package/dist/core/capabilities/engine-receipt.d.ts +12 -0
  42. package/dist/core/capabilities/engine-receipt.js +26 -0
  43. package/dist/core/capabilities/file-read.d.ts +64 -0
  44. package/dist/core/capabilities/file-read.js +325 -0
  45. package/dist/core/capabilities/game-task-plan.js +4 -2
  46. package/dist/core/capabilities/grep.d.ts +60 -0
  47. package/dist/core/capabilities/grep.js +190 -0
  48. package/dist/core/capabilities/inspect-node.d.ts +56 -0
  49. package/dist/core/capabilities/inspect-node.js +225 -0
  50. package/dist/core/capabilities/inspect-resource.d.ts +61 -0
  51. package/dist/core/capabilities/inspect-resource.js +376 -0
  52. package/dist/core/capabilities/math3d.d.ts +28 -0
  53. package/dist/core/capabilities/math3d.js +88 -0
  54. package/dist/core/capabilities/motion-generation.d.ts +62 -0
  55. package/dist/core/capabilities/motion-generation.js +138 -0
  56. package/dist/core/capabilities/placement-batch.d.ts +102 -0
  57. package/dist/core/capabilities/placement-batch.js +483 -0
  58. package/dist/core/capabilities/placement-math.d.ts +70 -0
  59. package/dist/core/capabilities/placement-math.js +209 -0
  60. package/dist/core/capabilities/placement-script.d.ts +42 -0
  61. package/dist/core/capabilities/placement-script.js +1096 -0
  62. package/dist/core/capabilities/placement.d.ts +485 -0
  63. package/dist/core/capabilities/placement.js +1731 -0
  64. package/dist/core/capabilities/project-context.d.ts +33 -2
  65. package/dist/core/capabilities/project-context.js +87 -4
  66. package/dist/core/capabilities/replace-node.d.ts +91 -0
  67. package/dist/core/capabilities/replace-node.js +423 -0
  68. package/dist/core/capabilities/runtime-control.d.ts +18 -2
  69. package/dist/core/capabilities/runtime-control.js +67 -2
  70. package/dist/core/capabilities/scene-batch.d.ts +63 -0
  71. package/dist/core/capabilities/scene-batch.js +394 -0
  72. package/dist/core/capabilities/scene-readback.d.ts +36 -0
  73. package/dist/core/capabilities/scene-readback.js +54 -0
  74. package/dist/core/capabilities/seeing/args.d.ts +348 -0
  75. package/dist/core/capabilities/seeing/args.js +178 -0
  76. package/dist/core/capabilities/seeing/candidates.d.ts +127 -0
  77. package/dist/core/capabilities/seeing/candidates.js +282 -0
  78. package/dist/core/capabilities/seeing/math.d.ts +134 -0
  79. package/dist/core/capabilities/seeing/math.js +269 -0
  80. package/dist/core/capabilities/seeing/probe.d.ts +62 -0
  81. package/dist/core/capabilities/seeing/probe.js +188 -0
  82. package/dist/core/capabilities/seeing/scoring.d.ts +235 -0
  83. package/dist/core/capabilities/seeing/scoring.js +639 -0
  84. package/dist/core/capabilities/seeing/seeing.d.ts +233 -0
  85. package/dist/core/capabilities/seeing/seeing.js +1341 -0
  86. package/dist/core/capabilities/seeing/shot-store.d.ts +52 -0
  87. package/dist/core/capabilities/seeing/shot-store.js +204 -0
  88. package/dist/core/capabilities/surface-snap-mesh.d.ts +88 -0
  89. package/dist/core/capabilities/surface-snap-mesh.js +425 -0
  90. package/dist/core/capabilities/surface-snap.d.ts +77 -0
  91. package/dist/core/capabilities/surface-snap.js +461 -0
  92. package/dist/core/capabilities/tool-dispatch.js +231 -72
  93. package/dist/core/capabilities/tscn.d.ts +85 -0
  94. package/dist/core/capabilities/tscn.js +391 -0
  95. package/dist/core/capabilities/world-snapshot.d.ts +58 -0
  96. package/dist/core/capabilities/world-snapshot.js +150 -0
  97. package/dist/core/capability-skew.d.ts +5 -5
  98. package/dist/core/capability-skew.js +14 -6
  99. package/dist/core/headless/worker-engine-client.d.ts +1 -1
  100. package/dist/core/headless/worker-engine-client.js +2 -2
  101. package/dist/core/library-read.d.ts +37 -5
  102. package/dist/core/library-read.js +264 -33
  103. package/dist/core/library-search.d.ts +2 -2
  104. package/dist/installer/agent-config.js +5 -3
  105. package/dist/installer/agent-table.d.ts +16 -0
  106. package/dist/installer/agent-table.js +52 -17
  107. package/dist/mcp/server.js +9 -3
  108. package/dist/mcp/tools/asset-tools.js +21 -3
  109. package/dist/mcp/tools/audit-tools.d.ts +23 -0
  110. package/dist/mcp/tools/audit-tools.js +60 -0
  111. package/dist/mcp/tools/debug-tools.js +3 -1
  112. package/dist/mcp/tools/file-tools.js +17 -4
  113. package/dist/mcp/tools/generate-tools.js +74 -66
  114. package/dist/mcp/tools/library-tools.js +2 -0
  115. package/dist/mcp/tools/perception-tools.js +6 -19
  116. package/dist/mcp/tools/placement-tools.d.ts +24 -0
  117. package/dist/mcp/tools/placement-tools.js +91 -0
  118. package/dist/mcp/tools/project-tools.js +18 -15
  119. package/dist/mcp/tools/scene-tools.js +71 -83
  120. package/dist/mcp/tools/seeing-tools.d.ts +24 -0
  121. package/dist/mcp/tools/seeing-tools.js +64 -0
  122. package/dist/mcp/tools/spatial-tools.js +17 -9
  123. package/dist/mcp/tools/visual-tools.js +59 -7
  124. package/gemini-extension.json +26 -4
  125. package/integrations/amp/README.md +1 -1
  126. package/integrations/crush/README.md +1 -1
  127. package/integrations/grok-build/README.md +1 -1
  128. package/integrations/hermes/README.md +1 -1
  129. package/integrations/kimi-code/README.md +1 -1
  130. package/integrations/mistral-vibe/README.md +1 -1
  131. package/integrations/opencode/README.md +1 -1
  132. package/integrations/rovo-dev/README.md +1 -1
  133. package/integrations/vscode-copilot/README.md +1 -1
  134. package/integrations/warp/README.md +1 -1
  135. package/integrations/windsurf/README.md +1 -1
  136. package/library/references/mcp-tools-reference/mcp-tools-reference.md +59 -15
  137. package/library/references/mcp-tools-reference/resource.yaml +1 -1
  138. package/library/references/summer-folder/resource.yaml +1 -1
  139. package/library/references/summer-folder/summer-folder.md +1 -1
  140. package/library/skills/3d-lighting/SKILL.md +1 -1
  141. package/library/skills/README.md +36 -14
  142. package/library/skills/adaptive-music/SKILL.md +1 -1
  143. package/library/skills/agent-playtesting/SKILL.md +1 -1
  144. package/library/skills/ai-characters-in-games/SKILL.md +220 -0
  145. package/library/skills/ai-characters-in-games/resource.yaml +30 -0
  146. package/library/skills/ambient-bed/SKILL.md +1 -1
  147. package/library/skills/animated-loop/SKILL.md +1 -1
  148. package/library/skills/animation-tree/SKILL.md +1 -1
  149. package/library/skills/art-direction/SKILL.md +1 -1
  150. package/library/skills/asset-strategy/SKILL.md +2 -1
  151. package/library/skills/asset-strategy/resource.yaml +2 -1
  152. package/library/skills/audio-direction/SKILL.md +1 -1
  153. package/library/skills/auto-fire-targeting/SKILL.md +1 -1
  154. package/library/skills/brainstorm-game/SKILL.md +1 -1
  155. package/library/skills/brainstorming/SKILL.md +1 -1
  156. package/library/skills/browse-templates/SKILL.md +3 -3
  157. package/library/skills/browse-templates/resource.yaml +1 -1
  158. package/library/skills/camera-rigs/SKILL.md +128 -0
  159. package/library/skills/camera-rigs/resource.yaml +29 -0
  160. package/library/skills/celeste-momentum-platforming/SKILL.md +1 -1
  161. package/library/skills/character-animation-wiring/SKILL.md +1 -1
  162. package/library/skills/character-model/SKILL.md +1 -1
  163. package/library/skills/character-movement/SKILL.md +326 -0
  164. package/library/skills/character-movement/references/default-third-person-controller.gd +201 -0
  165. package/library/skills/character-movement/references/default-third-person-verification-probe.gd +154 -0
  166. package/library/skills/character-movement/references/movement-3d.md +136 -0
  167. package/library/skills/character-movement/references/platformer-feel.md +83 -0
  168. package/library/skills/character-movement/resource.yaml +40 -0
  169. package/library/skills/character-portrait/SKILL.md +1 -1
  170. package/library/skills/cinematic-cutscene/SKILL.md +1 -1
  171. package/library/skills/combat-basics/SKILL.md +159 -0
  172. package/library/skills/combat-basics/references/projectiles-and-aoe.md +100 -0
  173. package/library/skills/combat-basics/resource.yaml +34 -0
  174. package/library/skills/concept-art/SKILL.md +1 -1
  175. package/library/skills/create-asset-sheet/SKILL.md +1 -1
  176. package/library/skills/debug/SKILL.md +1 -1
  177. package/library/skills/debugging-game-feel/SKILL.md +1 -1
  178. package/library/skills/design-level/SKILL.md +1 -1
  179. package/library/skills/design-mechanic/SKILL.md +1 -1
  180. package/library/skills/design-npc/SKILL.md +1 -1
  181. package/library/skills/diagnosing-perf-regressions/SKILL.md +1 -1
  182. package/library/skills/dispatching-parallel-agents/SKILL.md +1 -1
  183. package/library/skills/driving-the-editor-ui/SKILL.md +1 -1
  184. package/library/skills/environment-kit/SKILL.md +1 -1
  185. package/library/skills/export-and-ship/SKILL.md +1 -1
  186. package/library/skills/fabricating-assets/SKILL.md +1 -1
  187. package/library/skills/facial-and-lipsync/SKILL.md +1 -1
  188. package/library/skills/fps-controller/SKILL.md +9 -7
  189. package/library/skills/fps-controller/resource.yaml +4 -1
  190. package/library/skills/game-feel/SKILL.md +1 -1
  191. package/library/skills/gameskill/SKILL.md +1 -1
  192. package/library/skills/gdscript-patterns/SKILL.md +1 -1
  193. package/library/skills/generate-motion/SKILL.md +9 -9
  194. package/library/skills/generate-motion/resource.yaml +2 -1
  195. package/library/skills/godogen-visual-proof-game-generation/SKILL.md +1 -1
  196. package/library/skills/headless-scripting/SKILL.md +1 -1
  197. package/library/skills/in-game-purchases/SKILL.md +254 -0
  198. package/library/skills/in-game-purchases/resource.yaml +29 -0
  199. package/library/skills/instantiate-asset-pack/SKILL.md +1 -1
  200. package/library/skills/investigating-bugs/SKILL.md +1 -1
  201. package/library/skills/lumera-single-image-scene-reconstruction/SKILL.md +1 -1
  202. package/library/skills/make-game/SKILL.md +25 -3
  203. package/library/skills/make-game/references/2d-platformer-polish.md +93 -0
  204. package/library/skills/make-game/references/2d-platformer.md +117 -0
  205. package/library/skills/make-game/references/2d-rpg-content.md +234 -0
  206. package/library/skills/make-game/references/2d-rpg.md +241 -0
  207. package/library/skills/make-game/references/2d-survivors-progression.md +201 -0
  208. package/library/skills/make-game/references/2d-survivors.md +237 -0
  209. package/library/skills/make-game/references/3d-fps-systems.md +216 -0
  210. package/library/skills/make-game/references/3d-fps.md +177 -0
  211. package/library/skills/make-game/references/3d-horror-systems.md +186 -0
  212. package/library/skills/make-game/references/3d-horror.md +201 -0
  213. package/library/skills/make-game/references/3d-third-person-systems.md +220 -0
  214. package/library/skills/make-game/references/3d-third-person.md +215 -0
  215. package/library/skills/make-game/resource.yaml +6 -1
  216. package/library/skills/multiplayer/SKILL.md +150 -0
  217. package/library/skills/multiplayer/resource.yaml +33 -0
  218. package/library/skills/multiplayer-movement/SKILL.md +510 -0
  219. package/library/skills/multiplayer-movement/files.md +501 -0
  220. package/library/skills/multiplayer-movement/resource.yaml +30 -0
  221. package/library/skills/multiplayer-project/SKILL.md +521 -0
  222. package/library/skills/multiplayer-project/files.md +448 -0
  223. package/library/skills/multiplayer-project/resource.yaml +36 -0
  224. package/library/skills/multiplayer-publish/SKILL.md +229 -0
  225. package/library/skills/multiplayer-publish/export-errors.md +89 -0
  226. package/library/skills/multiplayer-publish/resource.yaml +28 -0
  227. package/library/skills/multiplayer-state/SKILL.md +438 -0
  228. package/library/skills/multiplayer-state/files.md +592 -0
  229. package/library/skills/multiplayer-state/resource.yaml +35 -0
  230. package/library/skills/multiplayer-testing/SKILL.md +236 -0
  231. package/library/skills/multiplayer-testing/resource.yaml +39 -0
  232. package/library/skills/music-track/SKILL.md +1 -1
  233. package/library/skills/navigate-summer/SKILL.md +1 -1
  234. package/library/skills/new-project/SKILL.md +1 -1
  235. package/library/skills/organic-model/SKILL.md +1 -1
  236. package/library/skills/pixel-art/SKILL.md +1 -1
  237. package/library/skills/play/SKILL.md +1 -1
  238. package/library/skills/playtesting-a-feature/SKILL.md +1 -1
  239. package/library/skills/procedural-animation/SKILL.md +2 -2
  240. package/library/skills/procedural-animation/resource.yaml +1 -1
  241. package/library/skills/prop-model/SKILL.md +1 -1
  242. package/library/skills/psx-retro-rendering/SKILL.md +1 -1
  243. package/library/skills/realtime-wet-surfaces/SKILL.md +2 -2
  244. package/library/skills/remote-deploy/SKILL.md +1 -1
  245. package/library/skills/retarget/SKILL.md +1 -1
  246. package/library/skills/running-in-the-cloud/SKILL.md +1 -1
  247. package/library/skills/save-load/SKILL.md +156 -0
  248. package/library/skills/save-load/resource.yaml +27 -0
  249. package/library/skills/scene-composition/SKILL.md +1 -1
  250. package/library/skills/scene-hierarchy-design/SKILL.md +1 -1
  251. package/library/skills/scene-scripting/SKILL.md +1 -1
  252. package/library/skills/scene-to-level/SKILL.md +1 -1
  253. package/library/skills/skill-create/SKILL.md +1 -1
  254. package/library/skills/skill-improve/SKILL.md +1 -1
  255. package/library/skills/skill-test/SKILL.md +1 -1
  256. package/library/skills/skintokens-auto-rigging/SKILL.md +1 -1
  257. package/library/skills/skybox-panorama/SKILL.md +1 -1
  258. package/library/skills/sound-effect/SKILL.md +1 -1
  259. package/library/skills/spatial-placement/SKILL.md +134 -3
  260. package/library/skills/spatial-placement/references/kit-placement-tools.md +364 -0
  261. package/library/skills/spatial-placement/resource.yaml +16 -2
  262. package/library/skills/sprite-sheet/SKILL.md +1 -1
  263. package/library/skills/summer-analytics/SKILL.md +147 -0
  264. package/library/skills/summer-analytics/resource.yaml +28 -0
  265. package/library/skills/summer-friends/SKILL.md +226 -0
  266. package/library/skills/summer-friends/resource.yaml +28 -0
  267. package/library/skills/summer-leaderboards/SKILL.md +231 -0
  268. package/library/skills/summer-leaderboards/resource.yaml +30 -0
  269. package/library/skills/summer-match-results/SKILL.md +286 -0
  270. package/library/skills/summer-match-results/resource.yaml +32 -0
  271. package/library/skills/summer-matchmaking/SKILL.md +301 -0
  272. package/library/skills/summer-matchmaking/resource.yaml +32 -0
  273. package/library/skills/summer-parties/SKILL.md +253 -0
  274. package/library/skills/summer-parties/resource.yaml +30 -0
  275. package/library/skills/summer-player-data/SKILL.md +322 -0
  276. package/library/skills/summer-player-data/resource.yaml +30 -0
  277. package/library/skills/summer-store/SKILL.md +271 -0
  278. package/library/skills/summer-store/resource.yaml +29 -0
  279. package/library/skills/summer-world-chat/SKILL.md +195 -0
  280. package/library/skills/summer-world-chat/resource.yaml +30 -0
  281. package/library/skills/summer-world-saves/SKILL.md +284 -0
  282. package/library/skills/summer-world-saves/resource.yaml +29 -0
  283. package/library/skills/text-to-motion/SKILL.md +99 -0
  284. package/library/skills/text-to-motion/prompts-and-controls.md +34 -0
  285. package/library/skills/text-to-motion/resource.yaml +41 -0
  286. package/library/skills/text-to-motion/rigging-for-text-motion.md +54 -0
  287. package/library/skills/tileable-texture/SKILL.md +1 -1
  288. package/library/skills/trailer-shot/SKILL.md +1 -1
  289. package/library/skills/tune-performance/SKILL.md +1 -1
  290. package/library/skills/ui-basics/SKILL.md +10 -1
  291. package/library/skills/ui-graphics/SKILL.md +1 -1
  292. package/library/skills/use-widget-asset/SKILL.md +1 -1
  293. package/library/skills/using-summer/SKILL.md +2 -2
  294. package/library/skills/using-summer/resource.yaml +2 -2
  295. package/library/skills/vehicle-model/SKILL.md +1 -1
  296. package/library/skills/verification-before-completion/SKILL.md +1 -1
  297. package/library/skills/verifying-scenes/SKILL.md +76 -2
  298. package/library/skills/verifying-scenes/resource.yaml +13 -2
  299. package/library/skills/vfx-dissolve/SKILL.md +1 -1
  300. package/library/skills/vfx-fire/SKILL.md +1 -1
  301. package/library/skills/vfx-hit-spark/SKILL.md +1 -1
  302. package/library/skills/vfx-lightning/SKILL.md +1 -1
  303. package/library/skills/vfx-magic-glow/SKILL.md +1 -1
  304. package/library/skills/vfx-muzzle-flash/SKILL.md +1 -1
  305. package/library/skills/vfx-smoke/SKILL.md +1 -1
  306. package/library/skills/vfx-water-ripple/SKILL.md +1 -1
  307. package/library/skills/voice-line/SKILL.md +1 -1
  308. package/library/skills/web-and-touch-input/SKILL.md +38 -0
  309. package/library/skills/web-and-touch-input/resource.yaml +28 -0
  310. package/library/skills/web-compatibility-look/SKILL.md +62 -0
  311. package/library/skills/web-compatibility-look/resource.yaml +30 -0
  312. package/library/skills/world-building-3d/SKILL.md +8 -1
  313. package/library/skills/world-building-3d/references/spatial-tools.md +1 -1
  314. package/library/skills/world-building-3d/resource.yaml +2 -1
  315. package/library/skills/writing-plans/SKILL.md +1 -1
  316. package/library/skills/writing-skills/SKILL.md +1 -1
  317. package/library/templates/3d-lan-multiplayer-starter/resource.yaml +2 -2
  318. package/library/tools/attach-to-surface/resource.yaml +142 -0
  319. package/library/tools/batch/resource.yaml +9 -3
  320. package/library/tools/connect-ports/resource.yaml +120 -0
  321. package/library/tools/connect-signal/resource.yaml +3 -2
  322. package/library/tools/debug-views/resource.yaml +94 -0
  323. package/library/tools/frame-nodes/resource.yaml +104 -0
  324. package/library/tools/frame-shot/resource.yaml +134 -0
  325. package/library/tools/generate-motion/resource.yaml +44 -9
  326. package/library/tools/get-board/resource.yaml +50 -0
  327. package/library/tools/get-project-context/resource.yaml +13 -2
  328. package/library/tools/grep/resource.yaml +77 -0
  329. package/library/tools/inspect-asset/resource.yaml +71 -0
  330. package/library/tools/inspect-node/resource.yaml +9 -2
  331. package/library/tools/inspect-resource/resource.yaml +16 -5
  332. package/library/tools/instantiate-scene/resource.yaml +27 -2
  333. package/library/tools/measure/resource.yaml +96 -0
  334. package/library/tools/place-adjacent/resource.yaml +113 -0
  335. package/library/tools/play/resource.yaml +15 -2
  336. package/library/tools/raycast/resource.yaml +101 -0
  337. package/library/tools/read-file/resource.yaml +36 -2
  338. package/library/tools/read-library/resource.yaml +2 -2
  339. package/library/tools/repeat-along/resource.yaml +129 -0
  340. package/library/tools/replace-node/resource.yaml +11 -5
  341. package/library/tools/scene-audit/resource.yaml +144 -0
  342. package/library/tools/screenshot/resource.yaml +13 -2
  343. package/library/tools/search-assets/resource.yaml +18 -0
  344. package/library/tools/shot-sheet/resource.yaml +110 -0
  345. package/library/tools/snap-to-surface/resource.yaml +4 -1
  346. package/library/tools/world-snapshot/resource.yaml +24 -3
  347. package/library/tools/zoom/resource.yaml +105 -0
  348. package/package.json +1 -1
  349. package/registry/generated/aliases.json +17 -3
  350. package/registry/generated/counts.json +3 -3
  351. package/registry/generated/gemini-extension.json +26 -4
  352. package/registry/generated/index.json +2154 -469
  353. package/registry/generated/marketplace.claude.json +2 -2
  354. package/registry/generated/plugin.claude.json +27 -5
  355. package/registry/generated/plugin.codex.json +28 -6
  356. package/registry/generated/plugin.cursor.json +27 -5
  357. package/registry/generated/plugin.factory.json +27 -5
  358. package/registry/generated/skills-index.md +36 -14
  359. package/registry/generated/skills-registry.json +421 -124
  360. package/registry/generated/templates-registry.json +2 -2
  361. package/library/skills/host-authoritative-state/SKILL.md +0 -332
  362. package/library/skills/host-authoritative-state/resource.yaml +0 -28
  363. package/library/skills/peer-to-peer-multiplayer/SKILL.md +0 -373
  364. package/library/skills/peer-to-peer-multiplayer/resource.yaml +0 -28
  365. package/library/skills/setup-multiplayer/SKILL.md +0 -336
  366. package/library/skills/setup-multiplayer/resource.yaml +0 -28
@@ -0,0 +1,438 @@
1
+ ---
2
+ name: multiplayer-state
3
+ description: "Authority-owned game state in Summer multiplayer: shared and private State groups, validated Commands, and Events."
4
+ license: MIT
5
+ compatibility: [Cursor, Claude Code, Windsurf, Codex]
6
+ category: multiplayer-and-networking
7
+ user-invocable: true
8
+ allowed-tools: Read Grep Glob Edit Write summer_read_file summer_write_file summer_get_script_errors summer_project_setting summer_play summer_stop summer_get_diagnostics
9
+ paths: ["network/**", "client/**", "authority/**"]
10
+ ---
11
+
12
+ # /multiplayer-state — what the authority owns, and how players ask
13
+
14
+ Step 3 of the multiplayer sequence. It continues Courtyard from
15
+ `multiplayer-movement`: players walk to coins, collect them, and see a
16
+ shared scoreboard, a private wallet and a sparkle. [files.md](files.md) has
17
+ every file this step changes, complete, as it ran under Local Play.
18
+
19
+ ## How it works
20
+
21
+ Except for each player's own pose, **the authority owns every fact**: score,
22
+ health, doors, inventories, rounds, who won. Clients never change shared
23
+ state. They **ask** with a Command; the authority checks the request against
24
+ the sender's verified Session and the current state, changes the state, and
25
+ answers.
26
+
27
+ | The fact | Primitive | Who sees it |
28
+ |---|---|---|
29
+ | Score, round timer, a door, who holds the flag | Authority-written State group | Everyone (`SummerWorldAudience`) |
30
+ | My wallet, my hand of cards, my inventory | Authority-written State group created for that player's Session | Only that player (`SummerTargetSessionAudience`) |
31
+ | "Collect this", "buy that", "open the door", "ready" | Command | The authority, which accepts or refuses |
32
+ | A sparkle, a sound, a hit flash | Event | Everyone, once, best-effort |
33
+ | A player's position | Owner-written pose (`multiplayer-movement`) | Everyone |
34
+
35
+ State is retained: a player who joins late receives the current value.
36
+ Events are not.
37
+
38
+ Before writing code, list the game's facts in this table and confirm it with
39
+ the user.
40
+
41
+ ## 1. Declare the streams
42
+
43
+ Add three streams to `network/composition.tres`: `Match` (shared document),
44
+ `Mine` (private document) and `Fx` (an Event):
45
+
46
+ ```ini
47
+ [sub_resource type="SummerNetworkField" id="MatchBytes"]
48
+ field_name = &"b"
49
+ type = 20
50
+
51
+ [sub_resource type="SummerNetworkSchema" id="MatchSchema"]
52
+ schema_id = &"courtyard.match.v1"
53
+ state_fields = Array[SummerNetworkField]([SubResource("MatchBytes")])
54
+
55
+ [sub_resource type="SummerWorldAudience" id="MatchAudience"]
56
+ max_expansion = 8
57
+
58
+ [sub_resource type="SummerNetworkStateStream" id="Match"]
59
+ stream_id = &"courtyard.match"
60
+ state_schema = SubResource("MatchSchema")
61
+ audience_policy = SubResource("MatchAudience")
62
+ max_audience_count = 8
63
+ max_keys = 1
64
+ max_state_bytes = 4096
65
+ max_total_bytes = 4096
66
+
67
+ [sub_resource type="SummerNetworkField" id="MineBytes"]
68
+ field_name = &"b"
69
+ type = 20
70
+
71
+ [sub_resource type="SummerNetworkSchema" id="MineSchema"]
72
+ schema_id = &"courtyard.mine.v1"
73
+ state_fields = Array[SummerNetworkField]([SubResource("MineBytes")])
74
+
75
+ [sub_resource type="SummerTargetSessionAudience" id="OnlyThatPlayer"]
76
+ max_expansion = 1
77
+
78
+ [sub_resource type="SummerNetworkStateStream" id="Mine"]
79
+ stream_id = &"courtyard.mine"
80
+ state_schema = SubResource("MineSchema")
81
+ audience_policy = SubResource("OnlyThatPlayer")
82
+ max_audience_count = 1
83
+ max_keys = 8
84
+ max_state_bytes = 1024
85
+ max_total_bytes = 8192
86
+
87
+ [sub_resource type="SummerNetworkField" id="FxPosition"]
88
+ field_name = &"position"
89
+ type = 9
90
+
91
+ [sub_resource type="SummerNetworkSchema" id="FxSchema"]
92
+ schema_id = &"courtyard.fx.v1"
93
+ state_fields = Array[SummerNetworkField]([SubResource("FxPosition")])
94
+
95
+ [sub_resource type="SummerWorldAudience" id="FxAudience"]
96
+ max_expansion = 8
97
+
98
+ [sub_resource type="SummerNetworkOccurrenceStream" id="Fx"]
99
+ stream_id = &"courtyard.fx"
100
+ kind = 2
101
+ command_scope = 0
102
+ event_scope = 2
103
+ event_delivery = 2
104
+ transient_ttl_ticks = 30
105
+ max_outcome_history = 0
106
+ command_timeout_msec = 0
107
+ audience_policy = SubResource("FxAudience")
108
+ max_audience_count = 8
109
+ payload_schema = SubResource("FxSchema")
110
+ ```
111
+
112
+ and list them in the `[resource]`:
113
+
114
+ ```ini
115
+ [resource]
116
+ network_version = "1.0.0"
117
+ state_streams = Array[SummerNetworkStateStream]([SubResource("Motion"), SubResource("Match"), SubResource("Mine")])
118
+ occurrence_streams = Array[SummerNetworkOccurrenceStream]([SubResource("Commands"), SubResource("Fx")])
119
+ ```
120
+
121
+ Update `load_steps` in the first line. [files.md](files.md) has the complete
122
+ file.
123
+
124
+ - **Documents travel as JSON in a byte field** (`type = 20`). A network
125
+ `String` holds at most 256 bytes, which a document outgrows fast.
126
+ - **Audiences:** `max_expansion` and `max_audience_count` default to 0,
127
+ which reaches nobody. Use the player count for World audiences and 1 for
128
+ a private one.
129
+ - **Event streams** set `kind = 2`, `command_scope = 0`, `event_scope = 2`
130
+ (World), `event_delivery = 2` (Transient), a `transient_ttl_ticks`, and
131
+ **`max_outcome_history = 0` and `command_timeout_msec = 0`**. Those two
132
+ default to non-zero. With the defaults, the Spawner logs `cannot attach`
133
+ and the whole network stays down.
134
+
135
+ Add the stream ids to `network/game.gd`:
136
+
137
+ ```gdscript
138
+ const CMD := &"courtyard.cmd"
139
+ const MATCH := &"courtyard.match" # one document every player sees
140
+ const MINE := &"courtyard.mine" # one private document per player
141
+ const FX := &"courtyard.fx" # one-off effects, never stored
142
+ ```
143
+
144
+ ## 2. The authority decides
145
+
146
+ `authority/main.gd`:
147
+
148
+ ```gdscript
149
+ extends Node
150
+ ## The match server. It runs headless and owns every rule.
151
+
152
+ const Game = preload("res://network/game.gd")
153
+ const PlayerArchetype = preload("res://authority/player_archetype.tres")
154
+ ## How far a player may move in one burst after a network stall, in metres.
155
+ const BURST := Game.MAX_SPEED * 0.5
156
+ const REACH := 2.0 # how close a player must stand to collect a coin
157
+
158
+ var spawner: SummerNetworkSpawner
159
+ var world: SummerNetworkWorld
160
+ var seat_of := {} # session_id -> seat, held until the Session leaves
161
+ var allowance := {} # seat -> metres the player may still move
162
+ var refilled_at := {} # seat -> msec of the last allowance refill
163
+
164
+ var match_group: SummerNetworkStateGroup
165
+ var match_doc := {"coins": {"0": [10.0, 0.5, 0.0], "1": [-10.0, 0.5, 0.0], "2": [0.0, 0.5, 10.0]}, "scores": {}}
166
+ var mine_groups := {} # session_id -> private group
167
+ var wallets := {} # session_id -> coins this player holds
168
+ var receipts := {} # session_id -> {request id: result}
169
+
170
+
171
+ func _ready() -> void:
172
+ spawner = Game.build_network(self, PlayerArchetype)
173
+ world = $World
174
+ spawner.entity_spawned.connect(_on_player_spawned)
175
+ spawner.command_received.connect(_on_command)
176
+ world.session_left.connect(_on_session_left)
177
+ var initialized: SummerResult = await Summer.initialize(Game.GAME_ID).get_result_or_completed_signal()
178
+ if not initialized.ok:
179
+ push_error(initialized.message)
180
+ return
181
+ if not world.is_ready():
182
+ await world.binding_ready
183
+ if not spawner.is_network_ready():
184
+ await spawner.network_ready
185
+ match_group = spawner.create_state_group(Game.MATCH, &"match", Game.pack(match_doc))
186
+ print("COURTYARD_AUTHORITY_READY")
187
+
188
+
189
+ ## A seat is a player's place in this World for their whole stay, across
190
+ ## reconnects. Returns -1 when the World is full.
191
+ func _seat(session: SummerSession) -> int:
192
+ if seat_of.has(session.session_id):
193
+ return seat_of[session.session_id]
194
+ var taken := seat_of.values()
195
+ for seat in Game.MAX_PLAYERS:
196
+ if seat not in taken:
197
+ seat_of[session.session_id] = seat
198
+ return seat
199
+ return -1
200
+
201
+
202
+ ## The Spawner spawns one player entity per joined player, once that player's
203
+ ## network is ready (and again after a reconnect). Its owner becomes the only
204
+ ## writer of the pose; the group ends with the entity.
205
+ func _on_player_spawned(entity: SummerNetworkEntity) -> void:
206
+ var seat := _seat(entity.session)
207
+ if seat < 0:
208
+ push_warning("no free seat")
209
+ return
210
+ allowance[seat] = BURST
211
+ refilled_at[seat] = Time.get_ticks_msec()
212
+ var group := spawner.create_state_group(Game.MOTION, Game.motion_key(seat), Game.spawn_pose(seat, entity.entity_id), entity.session, entity)
213
+ group.set_acceptance_policy(_accept_motion.bind(seat))
214
+ print("COURTYARD_PLAYER_SPAWNED seat=%d entity=%d" % [seat, entity.entity_id])
215
+
216
+
217
+ ## Runs on every pose an owner submits. True accepts it unchanged; false
218
+ ## refuses it, and the owner snaps back to the last accepted pose.
219
+ func _accept_motion(candidate: Dictionary, current: Dictionary, seat: int) -> bool:
220
+ if candidate.entity != current.entity:
221
+ return false # a player can move, never become someone else
222
+ var position: Vector3 = candidate.position
223
+ if not Game.in_bounds(position) or not is_finite(candidate.yaw):
224
+ return false
225
+ # A distance allowance refilled at MAX_SPEED: bursts after a stall pass,
226
+ # speed hacks and teleports do not.
227
+ var now := Time.get_ticks_msec()
228
+ var budget := minf(float(allowance[seat]) + Game.MAX_SPEED * (now - int(refilled_at[seat])) / 1000.0, BURST)
229
+ refilled_at[seat] = now
230
+ var distance := position.distance_to(current.position)
231
+ if distance > budget:
232
+ allowance[seat] = budget
233
+ return false
234
+ allowance[seat] = budget - distance
235
+ return true
236
+
237
+
238
+ ## Moves a player on purpose (respawn, portal). The owner snaps to it.
239
+ func teleport(seat: int, position: Vector3) -> void:
240
+ var group := spawner.get_state_group(Game.MOTION, Game.motion_key(seat))
241
+ if group != null and group.active:
242
+ var pose := group.get_state()
243
+ pose.position = position
244
+ allowance[seat] = BURST
245
+ group.reset(pose)
246
+
247
+
248
+ func _on_command(request: SummerNetworkCommandRequest) -> void:
249
+ var command := Game.unpack(request.payload)
250
+ var session := request.get_session()
251
+ var rid := str(command.get("rid", ""))
252
+ # A retry of a request already answered gets the same answer, applied once.
253
+ var answered: Dictionary = receipts.get(session.session_id, {})
254
+ if rid != "" and answered.has(rid):
255
+ request.accept({}, Game.pack(answered[rid]))
256
+ return
257
+ match str(command.get("c", "")):
258
+ "join":
259
+ _join(request)
260
+ "collect":
261
+ _collect(request, rid, str(command.get("coin", "")))
262
+ _:
263
+ request.refuse(&"unknown_command")
264
+
265
+
266
+ ## A client sends "join" once its network is ready: set up what the player
267
+ ## owns besides their body.
268
+ func _join(request: SummerNetworkCommandRequest) -> void:
269
+ var session := request.get_session()
270
+ var seat := _seat(session)
271
+ if seat < 0:
272
+ request.refuse(&"match_full")
273
+ return
274
+ if not mine_groups.has(session.session_id):
275
+ wallets[session.session_id] = 0
276
+ # Private state: only this Session receives it, only the authority writes it.
277
+ mine_groups[session.session_id] = spawner.create_state_group(Game.MINE, StringName("mine%d" % seat), Game.pack({"coins": 0}), null, null, session)
278
+ request.accept({}, Game.pack({"seat": seat}))
279
+ print("COURTYARD_PLAYER_JOINED seat=%d" % seat)
280
+
281
+
282
+ ## Checks everything before changing anything, then changes state only
283
+ ## through the groups so every client, late joiners included, agrees.
284
+ func _collect(request: SummerNetworkCommandRequest, rid: String, coin: String) -> void:
285
+ var session := request.get_session()
286
+ if rid == "":
287
+ request.refuse(&"missing_request_id")
288
+ return
289
+ if not mine_groups.has(session.session_id):
290
+ request.refuse(&"not_joined")
291
+ return
292
+ if not match_doc.coins.has(coin):
293
+ request.refuse(&"coin_gone")
294
+ return
295
+ # The player's latest accepted pose, never a position from the payload.
296
+ var body := spawner.get_state_group(Game.MOTION, Game.motion_key(seat_of[session.session_id]))
297
+ if body == null or not body.active:
298
+ request.refuse(&"not_spawned")
299
+ return
300
+ var at: Array = match_doc.coins[coin]
301
+ if (body.get_state().position as Vector3).distance_to(Vector3(at[0], at[1], at[2])) > REACH:
302
+ request.refuse(&"too_far")
303
+ return
304
+ match_doc.coins.erase(coin)
305
+ var name := session.player.display_name
306
+ match_doc.scores[name] = int(match_doc.scores.get(name, 0)) + 1
307
+ match_group.reset(Game.pack(match_doc))
308
+ wallets[session.session_id] += 1
309
+ (mine_groups[session.session_id] as SummerNetworkStateGroup).reset(Game.pack({"coins": wallets[session.session_id]}))
310
+ spawner.publish_event_by_policy(Game.FX, {"position": Vector3(at[0], at[1], at[2])})
311
+ var result := {"coin": coin, "coins": wallets[session.session_id]}
312
+ var answered: Dictionary = receipts.get(session.session_id, {})
313
+ answered[rid] = result
314
+ receipts[session.session_id] = answered
315
+ request.accept({}, Game.pack(result))
316
+
317
+
318
+ ## The Session ended for good: free the seat and everything the player had.
319
+ func _on_session_left(session: SummerSession, reason: String) -> void:
320
+ var seat: int = seat_of.get(session.session_id, -1)
321
+ seat_of.erase(session.session_id)
322
+ var mine: SummerNetworkStateGroup = mine_groups.get(session.session_id)
323
+ if mine != null and mine.active:
324
+ mine.retire()
325
+ mine_groups.erase(session.session_id)
326
+ wallets.erase(session.session_id)
327
+ receipts.erase(session.session_id)
328
+ print("COURTYARD_PLAYER_LEFT seat=%d reason=%s" % [seat, reason])
329
+ ```
330
+
331
+ The rules that matter:
332
+
333
+ - **Identity comes from `request.get_session()`**, never from the payload.
334
+ - **Positions come from the accepted pose group**, never from the payload.
335
+ Commands set `command_tick_dispatch`, so the tick's poses are installed
336
+ before the handler runs.
337
+ - **Check every precondition before changing anything:** joined, item still
338
+ there, in reach, can afford it.
339
+ - **Refuse with a stable reason id** (`&"too_far"`). The client turns it into
340
+ text.
341
+ - **Every action carries a request id.** Keep each player's answers, and
342
+ answer a retry with the stored result. A Command that timed out on the
343
+ client may already have been applied, and a blind resend would apply it
344
+ twice.
345
+ - **Change state only through groups.** `reset(document)` replaces the value
346
+ for everyone in its audience, late joiners included.
347
+ - **Retire a player's private group in `session_left`.** Authority-written
348
+ groups outlive a disconnect, so a reconnecting player gets their wallet
349
+ back.
350
+
351
+ ## 3. The client asks and shows
352
+
353
+ `client/main.gd` keeps the documents the authority publishes and asks for
354
+ coins. Add the two documents:
355
+
356
+ ```gdscript
357
+ var match_doc := {} # what the authority published for everyone
358
+ var mine := {} # what the authority published for this player only
359
+ ```
360
+
361
+ connect Events with the other signals in `_ready()`:
362
+
363
+ ```gdscript
364
+ spawner.event_received.connect(_on_event)
365
+ ```
366
+
367
+ route the two document streams in `_on_group_created`, and add the handlers:
368
+
369
+ ```gdscript
370
+ func _on_group_created(group: SummerNetworkStateGroup) -> void:
371
+ match group.get_stream_id():
372
+ Game.MOTION:
373
+ _attach_pose(group, int(group.get_state().entity))
374
+ Game.MATCH, Game.MINE:
375
+ # The baseline is already installed when this fires; later
376
+ # changes arrive through state_installed.
377
+ _on_document(group, group.get_state())
378
+ group.state_installed.connect(func(state: Dictionary, _revision: int, _reset: bool) -> void: _on_document(group, state))
379
+ ```
380
+
381
+ ```gdscript
382
+ func _on_document(group: SummerNetworkStateGroup, state: Dictionary) -> void:
383
+ if group.get_stream_id() == Game.MATCH:
384
+ match_doc = Game.unpack(state)
385
+ else:
386
+ mine = Game.unpack(state)
387
+
388
+
389
+ func _on_event(event: SummerNetworkEvent) -> void:
390
+ if event.stream_id == Game.FX:
391
+ print("COURTYARD_SPARKLE at %s" % event.payload.position)
392
+
393
+
394
+ ## Asks the authority for a coin. The request id makes a retry safe.
395
+ func collect(coin: String) -> Dictionary:
396
+ return await command({"c": "collect", "coin": coin, "rid": "collect-%s-%d" % [coin, Time.get_ticks_usec()]})
397
+ ```
398
+
399
+ - `state_group_created` fires with the group's first value already
400
+ installed. Read it with `group.get_state()` right there; `state_installed`
401
+ reports only later changes.
402
+ - The client never edits `match_doc` or `mine`. It shows what the authority
403
+ published. For instant feedback, show a pending state ("collecting…")
404
+ until the Command answers.
405
+ - JSON numbers come back as floats (`"coins": 1.0`). Convert with `int()`
406
+ before showing or comparing.
407
+ - Use Events for effects only. A Transient Event can be lost on a bad
408
+ network; anything that must arrive belongs in State.
409
+
410
+ ## 4. Test it
411
+
412
+ Run Local Play with 2 players and network emulation (`multiplayer-testing`):
413
+
414
+ - [ ] Both clients receive the shared document; each receives only its own private document.
415
+ - [ ] An accepted Command changes the shared document on every client.
416
+ - [ ] A refused Command changes nothing and returns its reason id (`too_far`, `coin_gone`).
417
+ - [ ] Sending a Command again with the same request id returns the same answer and applies once.
418
+ - [ ] A client that joins late sees the current scores at once.
419
+
420
+ ## Common mistakes
421
+
422
+ | Don't | Do | Why |
423
+ |---|---|---|
424
+ | Change shared state on the client "for responsiveness" | Show a pending state until the Command answers | The authority is the only source of truth |
425
+ | `@rpc("any_peer")` handlers | A Command checked on the authority | Peer ids are not verified players |
426
+ | Trust `player_id` or a position from the payload | `request.get_session()` and the accepted pose | Clients can send anything |
427
+ | JSON in a `String` field | A `PACKED_BYTE_ARRAY` field | Strings stop at 256 bytes |
428
+ | Leave `max_expansion` / `max_audience_count` at 0 | The player count, or 1 for private | 0 reaches nobody |
429
+ | Default bounds on an Event stream | `max_outcome_history = 0`, `command_timeout_msec = 0` | Otherwise `cannot attach`: no networking at all |
430
+ | Wait for `state_installed` to get the first value | `group.get_state()` in `state_group_created` | The first value is already installed |
431
+ | Resend a timed-out Command with a new request id | The same request id | It may already have been applied |
432
+ | Send something important as a Transient Event | Put it in State | Transient Events can be lost |
433
+ | Compare JSON numbers to ints directly | `int(value)` | JSON numbers come back as floats |
434
+ | Keep a private group after `session_left` | `retire()` it | Its key stays taken |
435
+
436
+ ## Next
437
+
438
+ `multiplayer-testing`: bots and a bad network. Then `multiplayer-publish`.