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,423 @@
1
+ /**
2
+ * replace-node — ONE implementation of `summer_replace_node` for both faces
3
+ * (src/mcp/tools/scene-tools.ts and tool-dispatch.ts), built from ops whose
4
+ * results persist, and verified against the SAVED scene file.
5
+ *
6
+ * Why not the engine's ReplaceNode op for scenes: SceneOps::replace_node
7
+ * (engine modules/1summer_engine/editor/ops/scene_ops.cpp, scene branch and
8
+ * the async _finish_replace_node) instantiates the new PackedScene and hands
9
+ * it to SceneTreeDock::replace_node — the editor's "Change Type" path — which
10
+ * ends in Node::replace_by (scene/main/node.cpp). replace_by finishes with
11
+ * `p_node->set_scene_file_path(get_scene_file_path())`: the new instance is
12
+ * stamped with the OLD node's scene path. The editor draws the new subtree,
13
+ * the receipt says ok, and SaveScene packs the node as
14
+ * `instance=ExtResource(<old scene>)` with a fresh unique_id — the change is
15
+ * gone on reload. A plain
16
+ * node replaced by a scene loses the other way (scene_file_path cleared, the
17
+ * instance's children owned by the instance root and dropped on save), and a
18
+ * type change of an instanced node stays an instance of the old scene.
19
+ *
20
+ * So the scene branch (and a type change of an instanced node) runs as:
21
+ * 0. SaveScene, so the file on disk is exactly the editor state;
22
+ * 1. read + parse the saved .tscn (which children the scene added under the
23
+ * node, which properties it overrides, its sibling index);
24
+ * 2. InstantiateScene / AddNode at the same parent under a temporary name;
25
+ * 3. SetProp every copyable property override (transform, visibility, ...);
26
+ * 4. ReparentNode each scene-added child onto the new node (local
27
+ * transforms kept), then re-parent each deeper scene-added descendant in
28
+ * place — the engine's ReparentNode re-owns only the node it moves, and
29
+ * remove_child clears the owner of everything below it;
30
+ * 5. MoveNode to the old sibling index, RemoveNode the old node, SetProp
31
+ * name back to the original name;
32
+ * 6. SaveScene, read the file back and check it: the node at the path
33
+ * instances the new scene (or has the new type), the parent and every
34
+ * scene-added descendant are there. persisted:false is reported as a
35
+ * failure, never as success.
36
+ * A type change of a plain node keeps the engine's ReplaceNode (it persists:
37
+ * replace_by copies an empty scene path) and gets the same read-back check.
38
+ */
39
+ import { z } from "zod";
40
+ import { resolveSingleOnlyOps, missingEngineOpResult } from "../capability-skew.js";
41
+ import { ToolInputError } from "../tool-errors.js";
42
+ import { asRecord } from "../util/json.js";
43
+ import { executeOpsChunked, safeProjectPath, sceneMutationOps } from "./engine-ops.js";
44
+ import { extractOpError } from "./engine-receipt.js";
45
+ import { REPLACE_NODE_FALLBACK } from "./engine-fallbacks.js";
46
+ import { reownInPlaceOps } from "./scene-batch.js";
47
+ import { isReadbackFailure, readSavedScene as readSceneFile, readbackFailure } from "./scene-readback.js";
48
+ import { decodeGodotString, extResourceId, findTscnNode, isAtOrBelow, isSceneCreatedNode, joinNodePath, normalizeNodePath, normalizeResPath, quotedLiteral, } from "./tscn.js";
49
+ // Mirrors library/tools/replace-node/resource.yaml input_schema (parity-tested).
50
+ export const replaceNodeInputShape = {
51
+ scenePath: z.string().describe("Target scene path, e.g. 'res://main.tscn'"),
52
+ path: z.string().describe("Node path to replace, e.g. './House3/Front/G_f2_door'"),
53
+ type: z.string().optional().describe("New node type, e.g. 'RigidBody3D'. Give exactly one of type or scene."),
54
+ scene: z
55
+ .string()
56
+ .optional()
57
+ .describe("Scene or model to replace with, e.g. 'res://kit/wall_door_02.tscn'. Give exactly one of type or scene."),
58
+ };
59
+ export const replaceNodeInputSchema = z.object(replaceNodeInputShape).strict();
60
+ const failure = readbackFailure;
61
+ const isFailure = isReadbackFailure;
62
+ const TOO_LARGE_HINT = "Replace by hand: summer_remove_node, then summer_instantiate_scene with the same parent and name, then summer_set_prop for the transform.";
63
+ const readSavedScene = (client, scenePath) => readSceneFile(client, scenePath, TOO_LARGE_HINT);
64
+ /** Per-op results of a (possibly chunked) receipt. */
65
+ function opResults(receipt) {
66
+ const results = asRecord(receipt)?.results;
67
+ return Array.isArray(results) ? results.filter((r) => asRecord(r)) : [];
68
+ }
69
+ /** Live child names of `nodePath`, in tree order, from a targeted scene read
70
+ * rooted at that node; null when the engine cannot answer (an older build
71
+ * ignores `root` and answers the scene root instead — detected by path). */
72
+ async function readLiveChildren(client, scenePath, nodePath) {
73
+ try {
74
+ const state = asRecord(await client.getSceneState(scenePath, { root: nodePath, depth: 1, limit: 100_000 }));
75
+ if (!state || state.ok === false)
76
+ return null;
77
+ const data = asRecord(state.data);
78
+ if (!data || normalizeNodePath(String(data.path ?? "")) !== nodePath)
79
+ return null;
80
+ if (!Array.isArray(data.children))
81
+ return null;
82
+ return data.children.map((child) => String(asRecord(child)?.name ?? ""));
83
+ }
84
+ catch {
85
+ return null;
86
+ }
87
+ }
88
+ /** A property override as a SetProp value, or why it cannot be copied. */
89
+ export function propSetValue(prop, parsed) {
90
+ const raw = prop.value.trim();
91
+ if (/\bSubResource\(/.test(raw)) {
92
+ return { reason: "scene-local sub_resource: it cannot be copied by reference; set it again on the new node" };
93
+ }
94
+ const ext = extResourceId(raw);
95
+ if (ext !== undefined) {
96
+ const path = parsed.extResources.get(ext)?.path;
97
+ return path ? { value: path } : { reason: `ext_resource ${ext} is not listed in the scene file` };
98
+ }
99
+ if (/\bExtResource\(/.test(raw))
100
+ return { reason: "the value embeds resource references; set it again on the new node" };
101
+ if (raw === "true")
102
+ return { value: true };
103
+ if (raw === "false")
104
+ return { value: false };
105
+ if (/^-?\d+(?:\.\d+)?(?:e[-+]?\d+)?$/i.test(raw))
106
+ return { value: Number(raw) };
107
+ const text = quotedLiteral(raw);
108
+ if (text !== undefined)
109
+ return { value: text };
110
+ const nodePath = /^NodePath\("((?:[^"\\]|\\.)*)"\)$/.exec(raw);
111
+ if (nodePath)
112
+ return { value: decodeGodotString(nodePath[1]) };
113
+ // Godot literal text (Transform3D(...), Vector3(...), Color(...), arrays,
114
+ // dictionaries): SetProp parses it with str_to_var.
115
+ return { value: raw };
116
+ }
117
+ function tempName(base, taken) {
118
+ const stem = `${base}_SummerReplace`;
119
+ let name = stem;
120
+ for (let i = 2; taken.has(name); i++)
121
+ name = `${stem}${i}`;
122
+ return name;
123
+ }
124
+ function siblingIndexInFile(parsed, node) {
125
+ return parsed.nodes.filter((n) => n.parent === node.parent && n.order < node.order).length;
126
+ }
127
+ function describeNode(node) {
128
+ if (node.instancePath)
129
+ return node.instancePath;
130
+ if (node.instance)
131
+ return `ExtResource("${node.instance}")`;
132
+ return `type ${node.type ?? "?"}`;
133
+ }
134
+ function verifyAgainstSaved(after, plan) {
135
+ const problems = [];
136
+ const warnings = [];
137
+ const node = findTscnNode(after, plan.target);
138
+ const saved = { read_back: plan.scenePath };
139
+ if (!node) {
140
+ problems.push(`the saved file has no node at ${plan.target}`);
141
+ }
142
+ else {
143
+ saved.node = describeNode(node);
144
+ saved.parent = node.parent;
145
+ if (plan.scene !== undefined) {
146
+ if (!node.instance) {
147
+ problems.push(`the saved node is a plain ${node.type ?? "node"}, not an instance of ${plan.scene}`);
148
+ }
149
+ else if (normalizeResPath(node.instancePath ?? "") !== normalizeResPath(plan.scene)) {
150
+ problems.push(`the saved node still instances ${describeNode(node)}, not ${plan.scene}`);
151
+ }
152
+ }
153
+ else if (node.instance) {
154
+ problems.push(`the saved node is still an instance of ${describeNode(node)}`);
155
+ }
156
+ else if (node.type !== plan.type) {
157
+ problems.push(`the saved node has type ${node.type ?? "?"}, not ${plan.type}`);
158
+ }
159
+ if (node.parent !== plan.parent) {
160
+ problems.push(`the saved node's parent is ${node.parent ?? "(none)"}, not ${plan.parent ?? "(none)"}`);
161
+ }
162
+ const index = siblingIndexInFile(after, node);
163
+ saved.index = index;
164
+ if (index !== plan.fileIndex) {
165
+ warnings.push(`sibling order changed: index ${index} among the saved siblings, was ${plan.fileIndex}`);
166
+ }
167
+ if (plan.oldTransform !== undefined) {
168
+ const now = node.props.find((p) => p.key === "transform")?.value;
169
+ const squash = (s) => s.replace(/\s+/g, "");
170
+ if (now === undefined) {
171
+ warnings.push("the saved node lists no transform override (it now uses the new scene's own root transform); check its placement");
172
+ }
173
+ else if (squash(now) !== squash(plan.oldTransform)) {
174
+ warnings.push(`the saved transform differs from the old one: ${now} (was ${plan.oldTransform})`);
175
+ }
176
+ }
177
+ }
178
+ const missing = plan.keptNodes
179
+ .filter((kept) => !after.nodes.some((n) => n.path === kept.path && isSceneCreatedNode(n)))
180
+ .map((kept) => kept.path);
181
+ if (missing.length > 0)
182
+ problems.push(`children missing from the saved file: ${missing.join(", ")}`);
183
+ return { persisted: problems.length === 0, problems, warnings, saved };
184
+ }
185
+ function notPersistedFailure(plan, verification, extra) {
186
+ return failure("not_persisted", `summer_replace_node did NOT persist (persisted:false): ${verification.problems.join("; ")}. ` +
187
+ `The editor may show the change, but ${plan.scenePath} on disk does not, so it reverts on reload. Do not report it as done: ` +
188
+ "inspect the saved file (summer_read_file), then redo it with summer_remove_node + summer_instantiate_scene (same parent and name) + summer_set_prop transform.", { persisted: false, verified: false, scenePath: plan.scenePath, path: plan.target, verification: { ...verification.saved, problems: verification.problems, warnings: verification.warnings }, ...extra });
189
+ }
190
+ /** summer_batch guard (both faces): a raw ReplaceNode with `scene` is the op
191
+ * that saves the OLD scene reference. Refuse it before anything is sent and
192
+ * point at the tool that persists. Null when the ops are fine. */
193
+ export function rawSceneReplaceRefusal(ops) {
194
+ const raw = ops.find((op) => op.op === "ReplaceNode" && typeof op.scene === "string" && op.scene.trim() !== "");
195
+ if (!raw)
196
+ return null;
197
+ return ("summer_batch does not send a raw ReplaceNode with scene: on current engines it shows the new scene in the editor but saves the OLD scene reference, " +
198
+ "so the change reverts on reload. Nothing was sent. Use summer_replace_node (it verifies the saved file), or RemoveNode + InstantiateScene.");
199
+ }
200
+ /**
201
+ * Replace the node at `path` in `scenePath` with `scene` (or a node of
202
+ * `type`) and prove the saved file holds the result.
203
+ */
204
+ export async function replaceNodePersisted(client, args) {
205
+ const scenePath = safeProjectPath(args.scenePath);
206
+ if (!scenePath.endsWith(".tscn")) {
207
+ throw new ToolInputError("summer_replace_node verifies the result in the saved text scene, so scenePath must be a .tscn file. For a binary .scn use summer_remove_node + summer_instantiate_scene.");
208
+ }
209
+ const type = args.type?.trim() || undefined;
210
+ const scene = args.scene?.trim() ? safeProjectPath(args.scene) : undefined;
211
+ if ((type === undefined) === (scene === undefined)) {
212
+ throw new ToolInputError("Give exactly one of type (a node class) or scene (a res:// scene or model path).");
213
+ }
214
+ if (type !== undefined && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(type)) {
215
+ throw new ToolInputError(`Invalid type "${type}": pass a plain node class name such as 'RigidBody3D'.`);
216
+ }
217
+ const target = normalizeNodePath(args.path);
218
+ const singleOnly = resolveSingleOnlyOps(client);
219
+ const send = (ops, options = {}) => executeOpsChunked((chunk) => client.executeIdentityBoundOps(chunk, { groupUndo: true, ...options, scenePath }), ops, singleOnly);
220
+ // 0. Save first: the plan is read from the file, so the file must be the editor state.
221
+ const preSave = await send([{ op: "SaveScene" }]);
222
+ if (extractOpError(preSave))
223
+ return asRecord(preSave) ?? { ok: false, error: "SaveScene failed" };
224
+ // 1. Read the scene as saved.
225
+ const before = await readSavedScene(client, scenePath);
226
+ if (isFailure(before))
227
+ return before;
228
+ const node = findTscnNode(before.parsed, target);
229
+ if (!node) {
230
+ return failure("node_not_found", `No node at "${target}" in ${scenePath} (paths are relative to the scene root, e.g. "World/Door"). Nothing was replaced; read the hierarchy with summer_get_scene_tree.`);
231
+ }
232
+ const keptNodes = before.parsed.nodes.filter((n) => n !== node && isAtOrBelow(n.path, target) && isSceneCreatedNode(n));
233
+ const plan = {
234
+ scenePath,
235
+ target,
236
+ parent: node.parent,
237
+ ...(scene !== undefined ? { scene } : {}),
238
+ ...(type !== undefined ? { type } : {}),
239
+ keptNodes,
240
+ fileIndex: siblingIndexInFile(before.parsed, node),
241
+ };
242
+ const from = describeNode(node);
243
+ // A type change of a plain node: the engine op persists. Verify it anyway.
244
+ if (type !== undefined && !node.instance && !node.instancePlaceholder) {
245
+ const receipt = await send(sceneMutationOps([{ op: "ReplaceNode", path: args.path, type }]));
246
+ if (extractOpError(receipt))
247
+ return asRecord(receipt) ?? { ok: false, error: "ReplaceNode failed" };
248
+ const after = await readSavedScene(client, scenePath);
249
+ if (isFailure(after))
250
+ return { ...after, applied: true, note: "ReplaceNode and SaveScene reported success, but the saved file could not be read back to verify it." };
251
+ const verification = verifyAgainstSaved(after.parsed, plan);
252
+ const extra = { method: "engine_replace_node", replaced: { from, to: `type ${type}` } };
253
+ if (!verification.persisted)
254
+ return notPersistedFailure(plan, verification, extra);
255
+ return {
256
+ ok: true,
257
+ persisted: true,
258
+ verified: true,
259
+ scenePath,
260
+ path: target,
261
+ ...extra,
262
+ verification: { ...verification.saved, ...(verification.warnings.length ? { warnings: verification.warnings } : {}) },
263
+ };
264
+ }
265
+ if (node.parent === null) {
266
+ return failure("root_not_supported", `"${target}" is the scene root. Replacing the root with ${scene ?? `type ${type}`} cannot be expressed as instantiate-and-remove; ` +
267
+ "create a new scene instead (summer_create_scene), or change the root's own properties.");
268
+ }
269
+ const parent = node.parent;
270
+ // Capability pre-flight for every op this path sends.
271
+ for (const op of [scene !== undefined ? "InstantiateScene" : "AddNode", "SetProp", "ReparentNode", "MoveNode", "RemoveNode"]) {
272
+ const missing = missingEngineOpResult(client, op, REPLACE_NODE_FALLBACK);
273
+ if (missing)
274
+ return { ...missing };
275
+ }
276
+ // What the new node takes over, and what cannot travel.
277
+ const props = [];
278
+ const notCopied = [];
279
+ for (const prop of node.props) {
280
+ const converted = propSetValue(prop, before.parsed);
281
+ if ("value" in converted)
282
+ props.push({ key: prop.key, value: converted.value });
283
+ else
284
+ notCopied.push({ key: prop.key, reason: converted.reason });
285
+ }
286
+ const oldTransform = node.props.find((p) => p.key === "transform")?.value;
287
+ if (oldTransform !== undefined)
288
+ plan.oldTransform = oldTransform;
289
+ const directChildren = keptNodes.filter((n) => n.parent === target);
290
+ const deeper = keptNodes
291
+ .filter((n) => n.parent !== target)
292
+ .sort((a, b) => a.order - b.order)
293
+ .map((n) => n.path);
294
+ // Overrides of nodes the OLD instance creates (no type/instance, nearest
295
+ // scene-created ancestor is the old node) belong to the old scene and go.
296
+ const droppedOverrides = before.parsed.nodes
297
+ .filter((n) => !isSceneCreatedNode(n) && isAtOrBelow(n.path, target) && n !== node)
298
+ .filter((n) => !keptNodes.some((k) => n.path.startsWith(`${k.path}/`)))
299
+ .map((n) => n.path);
300
+ const editableDropped = before.parsed.editable.includes(target);
301
+ const connections = before.parsed.connections.filter((c) => c.from === target || c.to === target);
302
+ const notCarriedOver = {
303
+ ...(notCopied.length ? { props: notCopied } : {}),
304
+ ...(node.groups.length ? { groups: node.groups } : {}),
305
+ ...(connections.length
306
+ ? { connections: connections.map((c) => `${c.from}:${c.signal} -> ${c.to}:${c.method}`) }
307
+ : {}),
308
+ ...(droppedOverrides.length ? { overrides_of_old_scene_nodes: droppedOverrides } : {}),
309
+ ...(editableDropped ? { editable_children: true } : {}),
310
+ };
311
+ // 2. Create the replacement next to the old node under a temporary name.
312
+ const siblingNames = new Set(before.parsed.nodes.filter((n) => n.parent === parent).map((n) => n.name));
313
+ const liveSiblings = await readLiveChildren(client, scenePath, parent);
314
+ for (const name of liveSiblings ?? [])
315
+ siblingNames.add(name);
316
+ const tmp = tempName(node.name, siblingNames);
317
+ const createOp = scene !== undefined
318
+ ? { op: "InstantiateScene", parent, scene, name: tmp }
319
+ : { op: "AddNode", parent, type: type, name: tmp };
320
+ const created = await send([createOp]);
321
+ if (extractOpError(created)) {
322
+ return { ...(asRecord(created) ?? {}), ok: false, note: `Nothing was replaced: creating the new node failed. ${scenePath} is unchanged.` };
323
+ }
324
+ const createdMeta = asRecord(opResults(created)[0]?.meta);
325
+ const newPath = normalizeNodePath(typeof createdMeta?.nodePath === "string" && createdMeta.nodePath ? createdMeta.nodePath : joinNodePath(parent, tmp));
326
+ // Remove the temporary node again (nothing else was changed yet).
327
+ const rollbackNew = async (reason, message) => {
328
+ const undo = await send([{ op: "RemoveNode", path: newPath }]);
329
+ const rolledBack = !extractOpError(undo);
330
+ return failure(reason, rolledBack
331
+ ? `${message} The temporary node was removed again; the old node is untouched and ${scenePath} on disk is unchanged.`
332
+ : `${message} Removing the temporary node ${newPath} also failed: remove it with summer_remove_node. ${scenePath} on disk is unchanged (not saved).`, { persisted: false, rolled_back: rolledBack });
333
+ };
334
+ // Children of the new scene that would collide with the moved children.
335
+ const newChildren = await readLiveChildren(client, scenePath, newPath);
336
+ if (newChildren) {
337
+ const clashes = directChildren.filter((c) => newChildren.includes(c.name)).map((c) => c.name);
338
+ if (clashes.length > 0) {
339
+ return rollbackNew("child_name_collision", `The new ${scene ?? `type ${type}`} already has children named ${clashes.join(", ")}, the same names as children added under ${target}; ` +
340
+ "moving them would rename them. Rename those children first (summer_set_prop name), then retry.");
341
+ }
342
+ }
343
+ // Live sibling index of the old node (an instanced or inherited parent has
344
+ // children the file does not list).
345
+ const liveIndex = liveSiblings ? liveSiblings.indexOf(node.name) : -1;
346
+ const oldIndex = liveIndex >= 0 ? liveIndex : plan.fileIndex;
347
+ // 3. Property overrides (each attempted; a failure is reported, not fatal).
348
+ const propsCopied = [];
349
+ if (props.length > 0) {
350
+ const receipt = await send(props.map((p) => ({ op: "SetProp", path: newPath, key: p.key, value: p.value })), { stopOnError: false });
351
+ const results = opResults(receipt);
352
+ props.forEach((p, i) => {
353
+ const r = results[i];
354
+ if (r && r.ok !== false)
355
+ propsCopied.push(p.key);
356
+ else
357
+ notCopied.push({ key: p.key, reason: String(r?.error ?? "SetProp failed") });
358
+ });
359
+ if (notCopied.length)
360
+ notCarriedOver.props = notCopied;
361
+ }
362
+ // 4 + 5. Move children, restore ownership below them, take the old slot, drop the old node, take its name.
363
+ const rebase = (path) => (path === target ? newPath : `${newPath}${path.slice(target.length)}`);
364
+ const structure = [
365
+ ...directChildren.map((c) => ({ op: "ReparentNode", path: c.path, new_parent_path: newPath, keep_global_transform: false })),
366
+ ...reownInPlaceOps(deeper, rebase),
367
+ { op: "MoveNode", path: newPath, new_index: oldIndex },
368
+ { op: "RemoveNode", path: target },
369
+ { op: "SetProp", path: newPath, key: "name", value: node.name },
370
+ ];
371
+ const moved = await send(structure);
372
+ if (extractOpError(moved)) {
373
+ const results = opResults(moved);
374
+ const applied = results.filter((r) => r.ok !== false).length;
375
+ const removedOld = results.some((r, i) => r.ok !== false && structure[i]?.op === "RemoveNode");
376
+ if (applied === 0) {
377
+ return rollbackNew("replace_failed", `Replacement stopped before anything moved: ${extractOpError(moved)}.`);
378
+ }
379
+ return failure("replace_partially_applied", `summer_replace_node stopped after ${applied} of ${structure.length} steps: ${extractOpError(moved)}. ` +
380
+ `Applied in the editor only, NOT saved (${scenePath} on disk still holds the original node). ` +
381
+ (removedOld
382
+ ? `The old node is gone and the new one is at ${newPath}: rename it with summer_set_prop name "${node.name}", then summer_save_scene.`
383
+ : `The new node is at ${newPath} next to the old one: undo with Ctrl+Z in the editor, or move the children back and remove ${newPath}.`), { persisted: false, applied_steps: structure.slice(0, applied).map((op) => String(op.op)) });
384
+ }
385
+ // 6. Save, then read the file back and check it.
386
+ const saved = await send([{ op: "SaveScene" }]);
387
+ if (extractOpError(saved)) {
388
+ return {
389
+ ...(asRecord(saved) ?? {}),
390
+ ok: false,
391
+ persisted: false,
392
+ note: `The replacement is applied in the editor but SaveScene failed, so ${scenePath} on disk still holds the original node. Fix the save error, then call summer_save_scene.`,
393
+ };
394
+ }
395
+ const after = await readSavedScene(client, scenePath);
396
+ if (isFailure(after))
397
+ return { ...after, persisted: false, note: "The replacement was applied and saved, but the file could not be read back to verify it." };
398
+ const verification = verifyAgainstSaved(after.parsed, plan);
399
+ const extra = {
400
+ method: "instantiate_move_remove",
401
+ replaced: { from, to: scene ?? `type ${type}` },
402
+ ...(directChildren.length ? { moved_children: directChildren.map((c) => c.name) } : {}),
403
+ ...(propsCopied.length ? { props_copied: propsCopied } : {}),
404
+ ...(Object.keys(notCarriedOver).length ? { not_carried_over: notCarriedOver } : {}),
405
+ };
406
+ if (!verification.persisted)
407
+ return notPersistedFailure(plan, verification, extra);
408
+ const warnings = [...verification.warnings];
409
+ if (Object.keys(notCarriedOver).length) {
410
+ warnings.push("Some state of the old node did not carry over (see not_carried_over): re-apply what is still wanted.");
411
+ }
412
+ return {
413
+ ok: true,
414
+ persisted: true,
415
+ verified: true,
416
+ scenePath,
417
+ path: target,
418
+ ...extra,
419
+ verification: verification.saved,
420
+ ...(warnings.length ? { warnings } : {}),
421
+ undo: "Sent as separate engine requests: undoing it in the editor takes one Ctrl+Z per step (create, properties, move/remove).",
422
+ };
423
+ }
@@ -186,18 +186,18 @@ export declare const runtimeAnimateArgsSchema: z.ZodObject<{
186
186
  instance?: string | undefined;
187
187
  name?: string | undefined;
188
188
  value?: string | number | boolean | undefined;
189
+ cmd?: "pause" | "play" | "stop" | "state" | "seek" | "speed" | "travel" | "start" | "set_param" | "get_param" | undefined;
190
+ space?: "local" | "global" | "both" | undefined;
189
191
  blend?: number | undefined;
190
192
  state?: string | undefined;
191
193
  position?: number | undefined;
192
194
  speed?: number | undefined;
193
195
  bones?: string[] | undefined;
194
- cmd?: "pause" | "play" | "stop" | "state" | "seek" | "speed" | "travel" | "start" | "set_param" | "get_param" | undefined;
195
196
  from_end?: boolean | undefined;
196
197
  update?: boolean | undefined;
197
198
  reset?: boolean | undefined;
198
199
  param?: string | undefined;
199
200
  playback_path?: string | undefined;
200
- space?: "local" | "global" | "both" | undefined;
201
201
  include_rest?: boolean | undefined;
202
202
  }, {
203
203
  path: string;
@@ -205,18 +205,18 @@ export declare const runtimeAnimateArgsSchema: z.ZodObject<{
205
205
  instance?: string | undefined;
206
206
  name?: string | undefined;
207
207
  value?: string | number | boolean | undefined;
208
+ cmd?: "pause" | "play" | "stop" | "state" | "seek" | "speed" | "travel" | "start" | "set_param" | "get_param" | undefined;
209
+ space?: "local" | "global" | "both" | undefined;
208
210
  blend?: number | undefined;
209
211
  state?: string | undefined;
210
212
  position?: number | undefined;
211
213
  speed?: number | undefined;
212
214
  bones?: string[] | undefined;
213
- cmd?: "pause" | "play" | "stop" | "state" | "seek" | "speed" | "travel" | "start" | "set_param" | "get_param" | undefined;
214
215
  from_end?: boolean | undefined;
215
216
  update?: boolean | undefined;
216
217
  reset?: boolean | undefined;
217
218
  param?: string | undefined;
218
219
  playback_path?: string | undefined;
219
- space?: "local" | "global" | "both" | undefined;
220
220
  include_rest?: boolean | undefined;
221
221
  }>;
222
222
  export type RuntimeAnimateArgs = z.infer<typeof runtimeAnimateArgsSchema>;
@@ -473,6 +473,9 @@ export declare const playGameExtensionSchema: {
473
473
  fixed_fps: z.ZodOptional<z.ZodNumber>;
474
474
  time_scale: z.ZodOptional<z.ZodNumber>;
475
475
  speed: z.ZodOptional<z.ZodNumber>;
476
+ players: z.ZodOptional<z.ZodNumber>;
477
+ spectators: z.ZodOptional<z.ZodNumber>;
478
+ queue: z.ZodOptional<z.ZodString>;
476
479
  focus: z.ZodOptional<z.ZodBoolean>;
477
480
  };
478
481
  export interface PlayGameArgs {
@@ -486,7 +489,13 @@ export interface PlayGameArgs {
486
489
  speed?: number;
487
490
  /** true = today's toolbar-style launch (Game tab + focus). Absent/false = quiet. */
488
491
  focus?: boolean;
492
+ /** Local Play: headless authority + this many clients (0 = one ordinary run). */
493
+ players?: number;
494
+ spectators?: number;
495
+ queue?: string;
489
496
  }
497
+ /** True when the caller asked for Local Play parameters at all. */
498
+ export declare function playRequestsLocalPlay(args: PlayGameArgs): boolean;
490
499
  /**
491
500
  * Quiet is the default whenever an agent drives play — both faces of this tool
492
501
  * (MCP and `summer tool`) ARE agent faces, so the only way to a focus-stealing
@@ -510,6 +519,13 @@ export declare function buildStopGameOp(instance: string): BuiltRuntimeOp;
510
519
  * believe a parallel instance is up.
511
520
  */
512
521
  export declare function withPlayInstanceEcho(result: unknown, args: PlayGameArgs): unknown;
522
+ export declare const PLAY_LOCAL_PLAY_NOT_SUPPORTED = "This Summer Engine build did not echo `local_play` in its PlayGame result \u2014 it predates Local Play and started ONE ordinary client, not a local authority plus the requested players. Update to an engine with Local Play (SummerEngine main after PR #415; restart it after updating).";
523
+ /**
524
+ * Local Play was requested with players > 0: an engine with Local Play echoes
525
+ * `local_play` (applied, or applied:false with a reason). No echo means an
526
+ * engine that ignored the keys and ran one client — say so.
527
+ */
528
+ export declare function withLocalPlayEcho(result: unknown, args: PlayGameArgs): unknown;
513
529
  export declare const PLAY_QUIET_NOT_SUPPORTED = "This Summer Engine build did not echo `agent_quiet` in its PlayGame result \u2014 it predates quiet play and has most likely switched the editor to the Game tab and taken focus. Update Summer Engine (restart it after updating) for launches that leave the user's screen alone.";
514
530
  /**
515
531
  * Quiet was requested: the engine echoes `agent_quiet` when it understood the
@@ -853,11 +853,32 @@ export const playGameExtensionSchema = {
853
853
  .number()
854
854
  .optional()
855
855
  .describe("User time scale applied on session start, (0, 100] — e.g. 0.5 for half speed."),
856
+ players: z
857
+ .number()
858
+ .int()
859
+ .min(0)
860
+ .max(64)
861
+ .optional()
862
+ .describe("Local Play for a Summer multiplayer game (engine with Local Play): start the game's authority headless plus this many clients on this machine, each joining through the game's own Summer.client.join. The scenes and queue come from the project's summer.build.json WorldDefinition; no account, Docker or game flags are needed. Omit it and pass queue to start the queue's minPlayers from summer.build.json; 0 forces one ordinary run. The result's local_play block lists every process and persona, plus warnings when the roster is outside the queue's minPlayers..maxPlayers."),
863
+ spectators: z
864
+ .number()
865
+ .int()
866
+ .min(0)
867
+ .optional()
868
+ .describe("Local Play: also start this many spectator clients (only with players)."),
869
+ queue: z
870
+ .string()
871
+ .optional()
872
+ .describe("Local Play: the summer.build.json queue the local World stands in for (default: the first declared queue)."),
856
873
  focus: z
857
874
  .boolean()
858
875
  .optional()
859
876
  .describe("Default false = QUIET play (PlayGame agent:true): the editor does not switch to the Game tab or grab focus for the embedded game, so the user keeps working. Pass true to launch the way the toolbar Play button does (Game tab + focus) — only when the user is watching and asked to see it."),
860
877
  };
878
+ /** True when the caller asked for Local Play parameters at all. */
879
+ export function playRequestsLocalPlay(args) {
880
+ return args.players !== undefined || args.spectators !== undefined || args.queue !== undefined;
881
+ }
861
882
  /**
862
883
  * Quiet is the default whenever an agent drives play — both faces of this tool
863
884
  * (MCP and `summer tool`) ARE agent faces, so the only way to a focus-stealing
@@ -881,7 +902,8 @@ export function playNeedsOp(args) {
881
902
  args.seed !== undefined ||
882
903
  args.fixed_fps !== undefined ||
883
904
  args.time_scale !== undefined ||
884
- args.speed !== undefined);
905
+ args.speed !== undefined ||
906
+ playRequestsLocalPlay(args));
885
907
  }
886
908
  /** True when the call addresses a non-main instance or an offscreen mode —
887
909
  * the part of PlayGame that only a runtime-control engine understands. */
@@ -914,6 +936,26 @@ export function buildPlayGameOp(args) {
914
936
  if (args.speed !== undefined && (typeof args.speed !== "number" || !Number.isFinite(args.speed) || args.speed <= 0 || args.speed > 100)) {
915
937
  throw new ToolInputError("speed must be a number in (0, 100].");
916
938
  }
939
+ if (playRequestsLocalPlay(args)) {
940
+ if (args.players !== undefined && (!isInt(args.players) || args.players < 0 || args.players > 64)) {
941
+ throw new ToolInputError("players must be an integer 0..64 (0 forces one ordinary run).");
942
+ }
943
+ if (args.spectators !== undefined && (!isInt(args.spectators) || args.spectators < 0)) {
944
+ throw new ToolInputError("spectators must be an integer >= 0.");
945
+ }
946
+ if ((args.players ?? 0) + (args.spectators ?? 0) > 64) {
947
+ throw new ToolInputError("players + spectators must be at most 64.");
948
+ }
949
+ if (args.spectators !== undefined && args.spectators > 0 && args.players === 0) {
950
+ throw new ToolInputError("spectators need players > 0.");
951
+ }
952
+ if (args.queue !== undefined && (typeof args.queue !== "string" || args.queue.trim().length === 0)) {
953
+ throw new ToolInputError("queue must be a queue name declared in summer.build.json.");
954
+ }
955
+ if (instance.length > 0 && instance !== "main") {
956
+ throw new ToolInputError("Local Play (players/spectators/queue) runs the editor's main game; it does not combine with an offscreen instance.");
957
+ }
958
+ }
917
959
  const op = { op: "PlayGame" };
918
960
  if (typeof args.scene === "string" && args.scene.trim().length > 0)
919
961
  op.scene = args.scene.trim();
@@ -935,6 +977,12 @@ export function buildPlayGameOp(args) {
935
977
  op.time_scale = args.time_scale;
936
978
  if (args.speed !== undefined)
937
979
  op.speed = args.speed;
980
+ if (args.players !== undefined)
981
+ op.players = args.players;
982
+ if (args.spectators !== undefined)
983
+ op.spectators = args.spectators;
984
+ if (args.queue !== undefined)
985
+ op.queue = args.queue.trim();
938
986
  return { kind: "PlayGame", op, timeoutMs: PLAY_INSTANCE_TIMEOUT_MS };
939
987
  }
940
988
  export function buildStopGameOp(instance) {
@@ -963,6 +1011,23 @@ export function withPlayInstanceEcho(result, args) {
963
1011
  warning: `This Summer Engine build did not echo \`instance\` in its PlayGame result — it predates instance-aware play and has most likely started the MAIN embedded game, ignoring instance:'${args.instance ?? ""}' / mode:'${args.mode ?? "embedded"}'. Verify with summer_is_running or summer_game_control action:'instances' before addressing that instance, and update Summer Engine for parallel instances.`,
964
1012
  };
965
1013
  }
1014
+ export const PLAY_LOCAL_PLAY_NOT_SUPPORTED = "This Summer Engine build did not echo `local_play` in its PlayGame result — it predates Local Play and started ONE ordinary client, not a local authority plus the requested players. Update to an engine with Local Play (SummerEngine main after PR #415; restart it after updating).";
1015
+ /**
1016
+ * Local Play was requested with players > 0: an engine with Local Play echoes
1017
+ * `local_play` (applied, or applied:false with a reason). No echo means an
1018
+ * engine that ignored the keys and ran one client — say so.
1019
+ */
1020
+ export function withLocalPlayEcho(result, args) {
1021
+ if (!playRequestsLocalPlay(args) || args.players === 0 || !result || typeof result !== "object")
1022
+ return result;
1023
+ if (extractOpError(result))
1024
+ return result;
1025
+ const envelope = result;
1026
+ const payload = envelope.results?.[0] ?? envelope;
1027
+ if (payload.local_play && typeof payload.local_play === "object")
1028
+ return result;
1029
+ return { ...envelope, local_play_note: PLAY_LOCAL_PLAY_NOT_SUPPORTED };
1030
+ }
966
1031
  export const PLAY_QUIET_NOT_SUPPORTED = "This Summer Engine build did not echo `agent_quiet` in its PlayGame result — it predates quiet play and has most likely switched the editor to the Game tab and taken focus. Update Summer Engine (restart it after updating) for launches that leave the user's screen alone.";
967
1032
  /**
968
1033
  * Quiet was requested: the engine echoes `agent_quiet` when it understood the
@@ -1007,5 +1072,5 @@ export async function playGame(client, args) {
1007
1072
  return missing;
1008
1073
  }
1009
1074
  const result = await client.executeOps([op], undefined, timeoutMs);
1010
- return withPlayPostureEcho(withPlayInstanceEcho(withRuntimeFailureHints(withOldEngineHint(result, "PlayGame", PLAY_INSTANCE_FALLBACK)), args), args);
1075
+ return withLocalPlayEcho(withPlayPostureEcho(withPlayInstanceEcho(withRuntimeFailureHints(withOldEngineHint(result, "PlayGame", PLAY_INSTANCE_FALLBACK)), args), args), args);
1011
1076
  }