summer-engine 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +27 -5
- package/.codex-plugin/plugin.json +28 -6
- package/.cursor-plugin/plugin.json +27 -5
- package/.factory-plugin/plugin.json +27 -5
- package/.opencode/plugins/summer.js +1 -1
- package/CHANGELOG.md +102 -0
- package/README.md +18 -3
- package/assets/audit/scene_audit.gd +4136 -0
- package/assets/seeing/seeing_probe.gd +1873 -0
- package/dist/cli/commands/skills.js +20 -2
- package/dist/core/api-client.d.ts +9 -1
- package/dist/core/api-client.js +21 -4
- package/dist/core/capabilities/agent-playbook.js +46 -0
- package/dist/core/capabilities/audit/accept.d.ts +55 -0
- package/dist/core/capabilities/audit/accept.js +147 -0
- package/dist/core/capabilities/audit/args.d.ts +95 -0
- package/dist/core/capabilities/audit/args.js +89 -0
- package/dist/core/capabilities/audit/audit.d.ts +56 -0
- package/dist/core/capabilities/audit/audit.js +287 -0
- package/dist/core/capabilities/audit/frame.d.ts +14 -0
- package/dist/core/capabilities/audit/frame.js +35 -0
- package/dist/core/capabilities/audit/gaps.d.ts +96 -0
- package/dist/core/capabilities/audit/gaps.js +257 -0
- package/dist/core/capabilities/audit/judge.d.ts +162 -0
- package/dist/core/capabilities/audit/judge.js +1134 -0
- package/dist/core/capabilities/audit/math.d.ts +110 -0
- package/dist/core/capabilities/audit/math.js +263 -0
- package/dist/core/capabilities/audit/summary.d.ts +74 -0
- package/dist/core/capabilities/audit/summary.js +212 -0
- package/dist/core/capabilities/board.d.ts +31 -0
- package/dist/core/capabilities/board.js +43 -0
- package/dist/core/capabilities/camera-view.d.ts +4 -2
- package/dist/core/capabilities/camera-view.js +6 -3
- package/dist/core/capabilities/connect-signal.d.ts +85 -0
- package/dist/core/capabilities/connect-signal.js +285 -0
- package/dist/core/capabilities/engine-fallbacks.d.ts +5 -0
- package/dist/core/capabilities/engine-fallbacks.js +5 -0
- package/dist/core/capabilities/engine-ops.d.ts +5 -1
- package/dist/core/capabilities/engine-ops.js +0 -3
- package/dist/core/capabilities/engine-receipt.d.ts +12 -0
- package/dist/core/capabilities/engine-receipt.js +26 -0
- package/dist/core/capabilities/file-read.d.ts +64 -0
- package/dist/core/capabilities/file-read.js +325 -0
- package/dist/core/capabilities/game-task-plan.js +4 -2
- package/dist/core/capabilities/grep.d.ts +60 -0
- package/dist/core/capabilities/grep.js +190 -0
- package/dist/core/capabilities/inspect-node.d.ts +56 -0
- package/dist/core/capabilities/inspect-node.js +225 -0
- package/dist/core/capabilities/inspect-resource.d.ts +61 -0
- package/dist/core/capabilities/inspect-resource.js +376 -0
- package/dist/core/capabilities/math3d.d.ts +28 -0
- package/dist/core/capabilities/math3d.js +88 -0
- package/dist/core/capabilities/motion-generation.d.ts +62 -0
- package/dist/core/capabilities/motion-generation.js +138 -0
- package/dist/core/capabilities/placement-batch.d.ts +102 -0
- package/dist/core/capabilities/placement-batch.js +483 -0
- package/dist/core/capabilities/placement-math.d.ts +70 -0
- package/dist/core/capabilities/placement-math.js +209 -0
- package/dist/core/capabilities/placement-script.d.ts +42 -0
- package/dist/core/capabilities/placement-script.js +1096 -0
- package/dist/core/capabilities/placement.d.ts +485 -0
- package/dist/core/capabilities/placement.js +1731 -0
- package/dist/core/capabilities/project-context.d.ts +33 -2
- package/dist/core/capabilities/project-context.js +87 -4
- package/dist/core/capabilities/replace-node.d.ts +91 -0
- package/dist/core/capabilities/replace-node.js +423 -0
- package/dist/core/capabilities/runtime-control.d.ts +18 -2
- package/dist/core/capabilities/runtime-control.js +67 -2
- package/dist/core/capabilities/scene-batch.d.ts +63 -0
- package/dist/core/capabilities/scene-batch.js +394 -0
- package/dist/core/capabilities/scene-readback.d.ts +36 -0
- package/dist/core/capabilities/scene-readback.js +54 -0
- package/dist/core/capabilities/seeing/args.d.ts +348 -0
- package/dist/core/capabilities/seeing/args.js +178 -0
- package/dist/core/capabilities/seeing/candidates.d.ts +127 -0
- package/dist/core/capabilities/seeing/candidates.js +282 -0
- package/dist/core/capabilities/seeing/math.d.ts +134 -0
- package/dist/core/capabilities/seeing/math.js +269 -0
- package/dist/core/capabilities/seeing/probe.d.ts +62 -0
- package/dist/core/capabilities/seeing/probe.js +188 -0
- package/dist/core/capabilities/seeing/scoring.d.ts +235 -0
- package/dist/core/capabilities/seeing/scoring.js +639 -0
- package/dist/core/capabilities/seeing/seeing.d.ts +233 -0
- package/dist/core/capabilities/seeing/seeing.js +1341 -0
- package/dist/core/capabilities/seeing/shot-store.d.ts +52 -0
- package/dist/core/capabilities/seeing/shot-store.js +204 -0
- package/dist/core/capabilities/surface-snap-mesh.d.ts +88 -0
- package/dist/core/capabilities/surface-snap-mesh.js +425 -0
- package/dist/core/capabilities/surface-snap.d.ts +77 -0
- package/dist/core/capabilities/surface-snap.js +461 -0
- package/dist/core/capabilities/tool-dispatch.js +231 -72
- package/dist/core/capabilities/tscn.d.ts +85 -0
- package/dist/core/capabilities/tscn.js +391 -0
- package/dist/core/capabilities/world-snapshot.d.ts +58 -0
- package/dist/core/capabilities/world-snapshot.js +150 -0
- package/dist/core/capability-skew.d.ts +5 -5
- package/dist/core/capability-skew.js +14 -6
- package/dist/core/headless/worker-engine-client.d.ts +1 -1
- package/dist/core/headless/worker-engine-client.js +2 -2
- package/dist/core/library-read.d.ts +37 -5
- package/dist/core/library-read.js +264 -33
- package/dist/core/library-search.d.ts +2 -2
- package/dist/installer/agent-config.js +5 -3
- package/dist/installer/agent-table.d.ts +16 -0
- package/dist/installer/agent-table.js +52 -17
- package/dist/mcp/server.js +9 -3
- package/dist/mcp/tools/asset-tools.js +21 -3
- package/dist/mcp/tools/audit-tools.d.ts +23 -0
- package/dist/mcp/tools/audit-tools.js +60 -0
- package/dist/mcp/tools/debug-tools.js +3 -1
- package/dist/mcp/tools/file-tools.js +17 -4
- package/dist/mcp/tools/generate-tools.js +74 -66
- package/dist/mcp/tools/library-tools.js +2 -0
- package/dist/mcp/tools/perception-tools.js +6 -19
- package/dist/mcp/tools/placement-tools.d.ts +24 -0
- package/dist/mcp/tools/placement-tools.js +91 -0
- package/dist/mcp/tools/project-tools.js +18 -15
- package/dist/mcp/tools/scene-tools.js +71 -83
- package/dist/mcp/tools/seeing-tools.d.ts +24 -0
- package/dist/mcp/tools/seeing-tools.js +64 -0
- package/dist/mcp/tools/spatial-tools.js +17 -9
- package/dist/mcp/tools/visual-tools.js +59 -7
- package/gemini-extension.json +26 -4
- package/integrations/amp/README.md +1 -1
- package/integrations/crush/README.md +1 -1
- package/integrations/grok-build/README.md +1 -1
- package/integrations/hermes/README.md +1 -1
- package/integrations/kimi-code/README.md +1 -1
- package/integrations/mistral-vibe/README.md +1 -1
- package/integrations/opencode/README.md +1 -1
- package/integrations/rovo-dev/README.md +1 -1
- package/integrations/vscode-copilot/README.md +1 -1
- package/integrations/warp/README.md +1 -1
- package/integrations/windsurf/README.md +1 -1
- package/library/references/mcp-tools-reference/mcp-tools-reference.md +59 -15
- package/library/references/mcp-tools-reference/resource.yaml +1 -1
- package/library/references/summer-folder/resource.yaml +1 -1
- package/library/references/summer-folder/summer-folder.md +1 -1
- package/library/skills/3d-lighting/SKILL.md +1 -1
- package/library/skills/README.md +36 -14
- package/library/skills/adaptive-music/SKILL.md +1 -1
- package/library/skills/agent-playtesting/SKILL.md +1 -1
- package/library/skills/ai-characters-in-games/SKILL.md +220 -0
- package/library/skills/ai-characters-in-games/resource.yaml +30 -0
- package/library/skills/ambient-bed/SKILL.md +1 -1
- package/library/skills/animated-loop/SKILL.md +1 -1
- package/library/skills/animation-tree/SKILL.md +1 -1
- package/library/skills/art-direction/SKILL.md +1 -1
- package/library/skills/asset-strategy/SKILL.md +2 -1
- package/library/skills/asset-strategy/resource.yaml +2 -1
- package/library/skills/audio-direction/SKILL.md +1 -1
- package/library/skills/auto-fire-targeting/SKILL.md +1 -1
- package/library/skills/brainstorm-game/SKILL.md +1 -1
- package/library/skills/brainstorming/SKILL.md +1 -1
- package/library/skills/browse-templates/SKILL.md +3 -3
- package/library/skills/browse-templates/resource.yaml +1 -1
- package/library/skills/camera-rigs/SKILL.md +128 -0
- package/library/skills/camera-rigs/resource.yaml +29 -0
- package/library/skills/celeste-momentum-platforming/SKILL.md +1 -1
- package/library/skills/character-animation-wiring/SKILL.md +1 -1
- package/library/skills/character-model/SKILL.md +1 -1
- package/library/skills/character-movement/SKILL.md +326 -0
- package/library/skills/character-movement/references/default-third-person-controller.gd +201 -0
- package/library/skills/character-movement/references/default-third-person-verification-probe.gd +154 -0
- package/library/skills/character-movement/references/movement-3d.md +136 -0
- package/library/skills/character-movement/references/platformer-feel.md +83 -0
- package/library/skills/character-movement/resource.yaml +40 -0
- package/library/skills/character-portrait/SKILL.md +1 -1
- package/library/skills/cinematic-cutscene/SKILL.md +1 -1
- package/library/skills/combat-basics/SKILL.md +159 -0
- package/library/skills/combat-basics/references/projectiles-and-aoe.md +100 -0
- package/library/skills/combat-basics/resource.yaml +34 -0
- package/library/skills/concept-art/SKILL.md +1 -1
- package/library/skills/create-asset-sheet/SKILL.md +1 -1
- package/library/skills/debug/SKILL.md +1 -1
- package/library/skills/debugging-game-feel/SKILL.md +1 -1
- package/library/skills/design-level/SKILL.md +1 -1
- package/library/skills/design-mechanic/SKILL.md +1 -1
- package/library/skills/design-npc/SKILL.md +1 -1
- package/library/skills/diagnosing-perf-regressions/SKILL.md +1 -1
- package/library/skills/dispatching-parallel-agents/SKILL.md +1 -1
- package/library/skills/driving-the-editor-ui/SKILL.md +1 -1
- package/library/skills/environment-kit/SKILL.md +1 -1
- package/library/skills/export-and-ship/SKILL.md +1 -1
- package/library/skills/fabricating-assets/SKILL.md +1 -1
- package/library/skills/facial-and-lipsync/SKILL.md +1 -1
- package/library/skills/fps-controller/SKILL.md +9 -7
- package/library/skills/fps-controller/resource.yaml +4 -1
- package/library/skills/game-feel/SKILL.md +1 -1
- package/library/skills/gameskill/SKILL.md +1 -1
- package/library/skills/gdscript-patterns/SKILL.md +1 -1
- package/library/skills/generate-motion/SKILL.md +9 -9
- package/library/skills/generate-motion/resource.yaml +2 -1
- package/library/skills/godogen-visual-proof-game-generation/SKILL.md +1 -1
- package/library/skills/headless-scripting/SKILL.md +1 -1
- package/library/skills/in-game-purchases/SKILL.md +254 -0
- package/library/skills/in-game-purchases/resource.yaml +29 -0
- package/library/skills/instantiate-asset-pack/SKILL.md +1 -1
- package/library/skills/investigating-bugs/SKILL.md +1 -1
- package/library/skills/lumera-single-image-scene-reconstruction/SKILL.md +1 -1
- package/library/skills/make-game/SKILL.md +25 -3
- package/library/skills/make-game/references/2d-platformer-polish.md +93 -0
- package/library/skills/make-game/references/2d-platformer.md +117 -0
- package/library/skills/make-game/references/2d-rpg-content.md +234 -0
- package/library/skills/make-game/references/2d-rpg.md +241 -0
- package/library/skills/make-game/references/2d-survivors-progression.md +201 -0
- package/library/skills/make-game/references/2d-survivors.md +237 -0
- package/library/skills/make-game/references/3d-fps-systems.md +216 -0
- package/library/skills/make-game/references/3d-fps.md +177 -0
- package/library/skills/make-game/references/3d-horror-systems.md +186 -0
- package/library/skills/make-game/references/3d-horror.md +201 -0
- package/library/skills/make-game/references/3d-third-person-systems.md +220 -0
- package/library/skills/make-game/references/3d-third-person.md +215 -0
- package/library/skills/make-game/resource.yaml +6 -1
- package/library/skills/multiplayer/SKILL.md +150 -0
- package/library/skills/multiplayer/resource.yaml +33 -0
- package/library/skills/multiplayer-movement/SKILL.md +510 -0
- package/library/skills/multiplayer-movement/files.md +501 -0
- package/library/skills/multiplayer-movement/resource.yaml +30 -0
- package/library/skills/multiplayer-project/SKILL.md +521 -0
- package/library/skills/multiplayer-project/files.md +448 -0
- package/library/skills/multiplayer-project/resource.yaml +36 -0
- package/library/skills/multiplayer-publish/SKILL.md +229 -0
- package/library/skills/multiplayer-publish/export-errors.md +89 -0
- package/library/skills/multiplayer-publish/resource.yaml +28 -0
- package/library/skills/multiplayer-state/SKILL.md +438 -0
- package/library/skills/multiplayer-state/files.md +592 -0
- package/library/skills/multiplayer-state/resource.yaml +35 -0
- package/library/skills/multiplayer-testing/SKILL.md +236 -0
- package/library/skills/multiplayer-testing/resource.yaml +39 -0
- package/library/skills/music-track/SKILL.md +1 -1
- package/library/skills/navigate-summer/SKILL.md +1 -1
- package/library/skills/new-project/SKILL.md +1 -1
- package/library/skills/organic-model/SKILL.md +1 -1
- package/library/skills/pixel-art/SKILL.md +1 -1
- package/library/skills/play/SKILL.md +1 -1
- package/library/skills/playtesting-a-feature/SKILL.md +1 -1
- package/library/skills/procedural-animation/SKILL.md +2 -2
- package/library/skills/procedural-animation/resource.yaml +1 -1
- package/library/skills/prop-model/SKILL.md +1 -1
- package/library/skills/psx-retro-rendering/SKILL.md +1 -1
- package/library/skills/realtime-wet-surfaces/SKILL.md +2 -2
- package/library/skills/remote-deploy/SKILL.md +1 -1
- package/library/skills/retarget/SKILL.md +1 -1
- package/library/skills/running-in-the-cloud/SKILL.md +1 -1
- package/library/skills/save-load/SKILL.md +156 -0
- package/library/skills/save-load/resource.yaml +27 -0
- package/library/skills/scene-composition/SKILL.md +1 -1
- package/library/skills/scene-hierarchy-design/SKILL.md +1 -1
- package/library/skills/scene-scripting/SKILL.md +1 -1
- package/library/skills/scene-to-level/SKILL.md +1 -1
- package/library/skills/skill-create/SKILL.md +1 -1
- package/library/skills/skill-improve/SKILL.md +1 -1
- package/library/skills/skill-test/SKILL.md +1 -1
- package/library/skills/skintokens-auto-rigging/SKILL.md +1 -1
- package/library/skills/skybox-panorama/SKILL.md +1 -1
- package/library/skills/sound-effect/SKILL.md +1 -1
- package/library/skills/spatial-placement/SKILL.md +134 -3
- package/library/skills/spatial-placement/references/kit-placement-tools.md +364 -0
- package/library/skills/spatial-placement/resource.yaml +16 -2
- package/library/skills/sprite-sheet/SKILL.md +1 -1
- package/library/skills/summer-analytics/SKILL.md +147 -0
- package/library/skills/summer-analytics/resource.yaml +28 -0
- package/library/skills/summer-friends/SKILL.md +226 -0
- package/library/skills/summer-friends/resource.yaml +28 -0
- package/library/skills/summer-leaderboards/SKILL.md +231 -0
- package/library/skills/summer-leaderboards/resource.yaml +30 -0
- package/library/skills/summer-match-results/SKILL.md +286 -0
- package/library/skills/summer-match-results/resource.yaml +32 -0
- package/library/skills/summer-matchmaking/SKILL.md +301 -0
- package/library/skills/summer-matchmaking/resource.yaml +32 -0
- package/library/skills/summer-parties/SKILL.md +253 -0
- package/library/skills/summer-parties/resource.yaml +30 -0
- package/library/skills/summer-player-data/SKILL.md +322 -0
- package/library/skills/summer-player-data/resource.yaml +30 -0
- package/library/skills/summer-store/SKILL.md +271 -0
- package/library/skills/summer-store/resource.yaml +29 -0
- package/library/skills/summer-world-chat/SKILL.md +195 -0
- package/library/skills/summer-world-chat/resource.yaml +30 -0
- package/library/skills/summer-world-saves/SKILL.md +284 -0
- package/library/skills/summer-world-saves/resource.yaml +29 -0
- package/library/skills/text-to-motion/SKILL.md +99 -0
- package/library/skills/text-to-motion/prompts-and-controls.md +34 -0
- package/library/skills/text-to-motion/resource.yaml +41 -0
- package/library/skills/text-to-motion/rigging-for-text-motion.md +54 -0
- package/library/skills/tileable-texture/SKILL.md +1 -1
- package/library/skills/trailer-shot/SKILL.md +1 -1
- package/library/skills/tune-performance/SKILL.md +1 -1
- package/library/skills/ui-basics/SKILL.md +10 -1
- package/library/skills/ui-graphics/SKILL.md +1 -1
- package/library/skills/use-widget-asset/SKILL.md +1 -1
- package/library/skills/using-summer/SKILL.md +2 -2
- package/library/skills/using-summer/resource.yaml +2 -2
- package/library/skills/vehicle-model/SKILL.md +1 -1
- package/library/skills/verification-before-completion/SKILL.md +1 -1
- package/library/skills/verifying-scenes/SKILL.md +76 -2
- package/library/skills/verifying-scenes/resource.yaml +13 -2
- package/library/skills/vfx-dissolve/SKILL.md +1 -1
- package/library/skills/vfx-fire/SKILL.md +1 -1
- package/library/skills/vfx-hit-spark/SKILL.md +1 -1
- package/library/skills/vfx-lightning/SKILL.md +1 -1
- package/library/skills/vfx-magic-glow/SKILL.md +1 -1
- package/library/skills/vfx-muzzle-flash/SKILL.md +1 -1
- package/library/skills/vfx-smoke/SKILL.md +1 -1
- package/library/skills/vfx-water-ripple/SKILL.md +1 -1
- package/library/skills/voice-line/SKILL.md +1 -1
- package/library/skills/web-and-touch-input/SKILL.md +38 -0
- package/library/skills/web-and-touch-input/resource.yaml +28 -0
- package/library/skills/web-compatibility-look/SKILL.md +62 -0
- package/library/skills/web-compatibility-look/resource.yaml +30 -0
- package/library/skills/world-building-3d/SKILL.md +8 -1
- package/library/skills/world-building-3d/references/spatial-tools.md +1 -1
- package/library/skills/world-building-3d/resource.yaml +2 -1
- package/library/skills/writing-plans/SKILL.md +1 -1
- package/library/skills/writing-skills/SKILL.md +1 -1
- package/library/templates/3d-lan-multiplayer-starter/resource.yaml +2 -2
- package/library/tools/attach-to-surface/resource.yaml +142 -0
- package/library/tools/batch/resource.yaml +9 -3
- package/library/tools/connect-ports/resource.yaml +120 -0
- package/library/tools/connect-signal/resource.yaml +3 -2
- package/library/tools/debug-views/resource.yaml +94 -0
- package/library/tools/frame-nodes/resource.yaml +104 -0
- package/library/tools/frame-shot/resource.yaml +134 -0
- package/library/tools/generate-motion/resource.yaml +44 -9
- package/library/tools/get-board/resource.yaml +50 -0
- package/library/tools/get-project-context/resource.yaml +13 -2
- package/library/tools/grep/resource.yaml +77 -0
- package/library/tools/inspect-asset/resource.yaml +71 -0
- package/library/tools/inspect-node/resource.yaml +9 -2
- package/library/tools/inspect-resource/resource.yaml +16 -5
- package/library/tools/instantiate-scene/resource.yaml +27 -2
- package/library/tools/measure/resource.yaml +96 -0
- package/library/tools/place-adjacent/resource.yaml +113 -0
- package/library/tools/play/resource.yaml +15 -2
- package/library/tools/raycast/resource.yaml +101 -0
- package/library/tools/read-file/resource.yaml +36 -2
- package/library/tools/read-library/resource.yaml +2 -2
- package/library/tools/repeat-along/resource.yaml +129 -0
- package/library/tools/replace-node/resource.yaml +11 -5
- package/library/tools/scene-audit/resource.yaml +144 -0
- package/library/tools/screenshot/resource.yaml +13 -2
- package/library/tools/search-assets/resource.yaml +18 -0
- package/library/tools/shot-sheet/resource.yaml +110 -0
- package/library/tools/snap-to-surface/resource.yaml +4 -1
- package/library/tools/world-snapshot/resource.yaml +24 -3
- package/library/tools/zoom/resource.yaml +105 -0
- package/package.json +1 -1
- package/registry/generated/aliases.json +17 -3
- package/registry/generated/counts.json +3 -3
- package/registry/generated/gemini-extension.json +26 -4
- package/registry/generated/index.json +2154 -469
- package/registry/generated/marketplace.claude.json +2 -2
- package/registry/generated/plugin.claude.json +27 -5
- package/registry/generated/plugin.codex.json +28 -6
- package/registry/generated/plugin.cursor.json +27 -5
- package/registry/generated/plugin.factory.json +27 -5
- package/registry/generated/skills-index.md +36 -14
- package/registry/generated/skills-registry.json +421 -124
- package/registry/generated/templates-registry.json +2 -2
- package/library/skills/host-authoritative-state/SKILL.md +0 -332
- package/library/skills/host-authoritative-state/resource.yaml +0 -28
- package/library/skills/peer-to-peer-multiplayer/SKILL.md +0 -373
- package/library/skills/peer-to-peer-multiplayer/resource.yaml +0 -28
- package/library/skills/setup-multiplayer/SKILL.md +0 -336
- package/library/skills/setup-multiplayer/resource.yaml +0 -28
|
@@ -15,6 +15,8 @@ export const READ_LIBRARY_DESCRIPTION = "Load one library entry by id (<kind>/<s
|
|
|
15
15
|
"Skills: the SKILL.md body plus metadata (status, use_when, related) and how to invoke the skill in your host (bare slug). " +
|
|
16
16
|
"Tools: how to call it (MCP name, `summer tool <slug> --args`, engine requirement, authority) plus the descriptor. " +
|
|
17
17
|
"Templates: the pinned repo @ commit and tree digest (or built-in) and the `summer create <slug>` command. References: the markdown body. " +
|
|
18
|
+
"Linked files: a body ends with its relative links and the id that loads each; a file an entry links loads by <entry id>/<link as written> " +
|
|
19
|
+
"(e.g. skill/spatial-placement/references/kit-placement-tools.md), by the relative path alone when one entry ships it, inside library/ only. " +
|
|
18
20
|
"part: 'skill' = body only, 'resource' = the resource.yaml descriptor only, 'all' (default) = both. " +
|
|
19
21
|
"The LAST line of every load is the feedback footer `— entry_id: <id>@<content-hash>. If this entry is wrong, stale, or you deviate from it, report via summer_library_feedback.` " +
|
|
20
22
|
"— copy that entry_id verbatim into summer_library_feedback once you have verified the outcome in-engine. " +
|
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { withEngine, missingEngineOpResult, withOldEngineHint } from "./with-engine.js";
|
|
3
3
|
// engine_lacks_op fallbacks: ONE copy for every face (E2E 2026-09-03 F-16).
|
|
4
4
|
import { RUNTIME_NODE_FALLBACK, RUNTIME_TREE_FALLBACK, SNAPSHOT_DIFF_FALLBACK, WORLD_SNAPSHOT_FALLBACK, } from "../../core/capabilities/engine-fallbacks.js";
|
|
5
|
+
import { WORLD_SNAPSHOT_DEFAULT_MAX_NODES, buildWorldSnapshotOp, shapeWorldSnapshot, worldSnapshotInputShape, } from "../../core/capabilities/world-snapshot.js";
|
|
5
6
|
/**
|
|
6
7
|
* Perception tools. Two signals, two jobs: these ops return STRUCTURED state —
|
|
7
8
|
* exact node paths, transforms, world AABBs, counts — while summer_screenshot
|
|
@@ -40,28 +41,14 @@ export function registerPerceptionTools(server) {
|
|
|
40
41
|
|
|
41
42
|
THE LOOP: summer_world_snapshot (note snapshot_id) -> mutate (summer_run_script / scene tools / imports) -> summer_snapshot_diff from_id:<that id> -> summer_screenshot. The diff proves exactly what changed structurally; the screenshot proves it looks right. This is how you catch a node that silently vanished on save, a transform that landed at the origin, or an AABB clipping through the floor.
|
|
42
43
|
|
|
43
|
-
Node lists are path-sorted and truncated DETERMINISTICALLY (result carries total_nodes + truncated) so two snapshots stay diffable without phantom adds/removes. The engine retains the last 8 snapshots per session, keyed by snapshot_id. Use this instead of summer_get_scene_tree when you need transforms/AABBs/fingerprints or a diffable baseline; the tree read remains the hierarchy-shaped view. On an engine build that predates GetWorldSnapshot the result is a structured engine_lacks_op failure naming the fallback
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
.optional()
|
|
47
|
-
.describe("Scene to snapshot, e.g. 'res://main.tscn'. Omit for the currently edited scene."),
|
|
48
|
-
max_nodes: z
|
|
49
|
-
.number()
|
|
50
|
-
.int()
|
|
51
|
-
.positive()
|
|
52
|
-
.optional()
|
|
53
|
-
.describe("Node cap (engine default 4000). The result declares truncation — never assume a capped list is complete."),
|
|
54
|
-
}, async ({ scene_path, max_nodes }) => withEngine(async (client) => {
|
|
44
|
+
Node lists are path-sorted and truncated DETERMINISTICALLY (result carries total_nodes + truncated) so two snapshots stay diffable without phantom adds/removes. The engine retains the last 8 snapshots per session, keyed by snapshot_id. Use this instead of summer_get_scene_tree when you need transforms/AABBs/fingerprints or a diffable baseline; the tree read remains the hierarchy-shaped view. On an engine build that predates GetWorldSnapshot the result is a structured engine_lacks_op failure naming the fallback.
|
|
45
|
+
|
|
46
|
+
KEEP IT SMALL (about 250 bytes per node): at most ${WORLD_SNAPSHOT_DEFAULT_MAX_NODES} nodes are listed by default. path_prefix reads one subtree ('House3', 'Lane2/Props'), classes keeps some node classes, fields keeps some per-node fields (e.g. ['pos','aabb']), offset/next_offset page the list. A filtered read adds matched_nodes and matched_counts (class counts of the subtree). counts and total_nodes always describe the whole scene, and snapshot_id always covers the whole scene, so summer_snapshot_diff still sees every change.`, worldSnapshotInputShape, async (args) => withEngine(async (client) => {
|
|
55
47
|
const missing = missingEngineOpResult(client, "GetWorldSnapshot", WORLD_SNAPSHOT_FALLBACK);
|
|
56
48
|
if (missing)
|
|
57
49
|
return missing;
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
op.scene_path = scene_path;
|
|
61
|
-
if (max_nodes !== undefined)
|
|
62
|
-
op.max_nodes = max_nodes;
|
|
63
|
-
const result = await client.executeOps([op]);
|
|
64
|
-
return withFailureReasonHint(withOldEngineHint(result, "GetWorldSnapshot", WORLD_SNAPSHOT_FALLBACK), {
|
|
50
|
+
const result = await client.executeOps([buildWorldSnapshotOp(args)]);
|
|
51
|
+
return withFailureReasonHint(withOldEngineHint(shapeWorldSnapshot(result, args), "GetWorldSnapshot", WORLD_SNAPSHOT_FALLBACK), {
|
|
65
52
|
no_scene: "No scene is open to snapshot. Call summer_get_project_context, then summer_open_main_scene (or summer_open_scene with a known .tscn path), then retry.",
|
|
66
53
|
scene_not_loaded: "scene_path resolves only a LIVE editor tab or an already-cached scene — never a disk load. Open that scene first (summer_open_scene), or omit scene_path to snapshot the currently edited scene.",
|
|
67
54
|
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { type PlacementClient, type PlacementResult } from "../../core/capabilities/placement.js";
|
|
3
|
+
type Content = {
|
|
4
|
+
type: "text";
|
|
5
|
+
text: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function renderPlacementResult(body: PlacementResult | Record<string, unknown>): {
|
|
8
|
+
content: Content[];
|
|
9
|
+
isError?: true;
|
|
10
|
+
};
|
|
11
|
+
/** withEngine wrapper that renders the placement result verbatim. */
|
|
12
|
+
export declare function runPlacement<T>(run: (client: PlacementClient, args: T) => Promise<PlacementResult>): (args: T) => Promise<{
|
|
13
|
+
content: ({
|
|
14
|
+
type: "text";
|
|
15
|
+
text: string;
|
|
16
|
+
} | {
|
|
17
|
+
type: "image";
|
|
18
|
+
data: string;
|
|
19
|
+
mimeType: string;
|
|
20
|
+
})[];
|
|
21
|
+
isError?: boolean;
|
|
22
|
+
}>;
|
|
23
|
+
export declare function registerPlacementTools(server: McpServer): void;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { withEngine } from "./with-engine.js";
|
|
2
|
+
import { attachToSurface, attachToSurfaceArgsSchema, connectPorts, connectPortsArgsSchema, inspectAsset, inspectAssetArgsSchema, measure, measureArgsSchema, placeAdjacent, placeAdjacentArgsSchema, raycast, raycastArgsSchema, repeatAlong, repeatAlongArgsSchema, } from "../../core/capabilities/placement.js";
|
|
3
|
+
/**
|
|
4
|
+
* Kit-placement tools: measure an asset before instancing it, place pieces
|
|
5
|
+
* relative to each other, mount them on surfaces, repeat them along a line,
|
|
6
|
+
* join ports, and query the scene with arbitrary rays and per-axis measures.
|
|
7
|
+
*
|
|
8
|
+
* The implementations live in core/capabilities/placement.ts (shared with
|
|
9
|
+
* `summer tool <slug>`). They use only existing engine ops: the placement
|
|
10
|
+
* probe runs through RunSceneScript (read-only, undo "none", no checkpoint)
|
|
11
|
+
* and mutations are SetProp / SnapToSurface / InstantiateScene with the usual
|
|
12
|
+
* scene target, undo step and final SaveScene.
|
|
13
|
+
*
|
|
14
|
+
* Results are rendered here, not by withEngine's failure renderer: a placement
|
|
15
|
+
* failure carries structured fields (failure_reason, what already applied,
|
|
16
|
+
* the measured hit) that a plain error string would drop.
|
|
17
|
+
*/
|
|
18
|
+
const RESULT_LIMIT_BYTES = 5 * 1024;
|
|
19
|
+
export function renderPlacementResult(body) {
|
|
20
|
+
let text = JSON.stringify(body);
|
|
21
|
+
if (Buffer.byteLength(text, "utf8") > RESULT_LIMIT_BYTES) {
|
|
22
|
+
text = JSON.stringify({
|
|
23
|
+
ok: false,
|
|
24
|
+
tool: body.tool,
|
|
25
|
+
failure_reason: "result_exceeded_byte_limit",
|
|
26
|
+
error: "The placement result was larger than 5 KB and was not forwarded. Narrow the request (fewer nodes or copies).",
|
|
27
|
+
...(body.ok === true ? { mutationApplied: body.saved === true, retrySafe: false } : {}),
|
|
28
|
+
});
|
|
29
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
30
|
+
}
|
|
31
|
+
return body.ok === true ? { content: [{ type: "text", text }] } : { content: [{ type: "text", text }], isError: true };
|
|
32
|
+
}
|
|
33
|
+
/** withEngine wrapper that renders the placement result verbatim. */
|
|
34
|
+
export function runPlacement(run) {
|
|
35
|
+
return async (args) => withEngine(async (client) => ({ placementResult: await run(client, args) }), {
|
|
36
|
+
onResult: (wrapped) => renderPlacementResult(wrapped.placementResult),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export function registerPlacementTools(server) {
|
|
40
|
+
server.tool("summer_inspect_asset", `Measure a 3D asset file (.tscn/.scn/.glb/.gltf, or a Mesh resource) WITHOUT adding it to any scene: it is loaded and instanced off-scene in the editor, measured, and freed. Call it once per kit piece before placing it, instead of guessing size, origin or facing.
|
|
41
|
+
|
|
42
|
+
Returns (all in the asset root's own frame, the frame position/rotation apply in), the facing evidence first:
|
|
43
|
+
- summary {aabb, origin {fraction, label e.g. "x:center y:min z:min"}, plane_pairs, port_like_loops [loop ids], triangles, mesh_count, anchor_count, collision_count, warnings?}. plane_pairs: the 2 largest pairs of opposite planes (a pair under 1% of the largest one's area is left out), each {larger, opposite (null for a single sheet), separation}; every plane {axis (e.g. "+z", only when the normal is within about 1 degree of it), normal, offset, area, one_sided}. one_sided true = its material culls back faces, so the plane is invisible from behind its normal: a single sheet that is one_sided faces along its normal and its back is the opposite axis. An oblique normal (no axis) means a baked yaw or a 45-degree corner face. warnings says when the triangle budget cut the analysis and which maxTriangles covers the whole mesh
|
|
44
|
+
- aabb, origin, meshes [{path, tris, min, max}], triangles total
|
|
45
|
+
- planes: the 6 largest planar face groups {normal, offset, area, tris, cull_back, one_sided}; normals follow the triangle winding (outward faces). You decide the front; the tool does not label facing
|
|
46
|
+
- open_loops: open boundary loops {id, index, mesh, center, direction (outward), radius, vertices, max_dev} in a stable order: grouped by the piece axis the direction is nearest (+X, -X, +Y, -Y, +Z, -Z; ~X/~Y/~Z when undecided), the outermost along that axis first, then by centre; never by radius. id names the loop by that order: "+Y" is the outermost loop facing +Y, "+Y#2" the next. summer_connect_ports accepts the id (or the index); the same id names the same loop on the placed node in any pose. detail "summary" (default) lists only port-like loops (radius over 2 cm with a partner loop facing more than 60 degrees away: the ends of a pipe, duct or bend; or the one opening of an end piece, such as an outlet's socket, on its bounding-box face) and says how many it omitted; detail "full" lists every loop, including the outline of flat sheets
|
|
47
|
+
- anchors: Marker3D nodes {name, path, position, forward (-Z), up}
|
|
48
|
+
- collision: CollisionShape3D nodes {path, shape, size/radius/height/faces, center, min, max}
|
|
49
|
+
- analysis {triangles_analyzed, triangle_budget, truncated}; a "truncated" object when a list was cut to fit 5 KB
|
|
50
|
+
|
|
51
|
+
maxTriangles 100-300000 (default 60000). Evidence is mesh_triangles. Uses the existing RunSceneScript op (in the live editor, read-only); an engine without it answers engine_lacks_op.`, inspectAssetArgsSchema.shape, runPlacement(inspectAsset));
|
|
52
|
+
server.tool("summer_place_adjacent", `Move one node so its bounds face sits against another node's bounds face along one axis: facade modules edge to edge, a storey stacked on the one below, a cornice on a wall. Optionally line up the other two axes (min, center or max), per axis.
|
|
53
|
+
|
|
54
|
+
Example: next module to the right, same base height, same front plane: {axis:"x", side:"max", gap:0, alignOtherAxes:{y:"min", z:"max"}}.
|
|
55
|
+
|
|
56
|
+
Bounds are the visible GeometryInstance3D AABBs (the definition summer_align_distribute_3d uses; evidence visual_aabb). space "local" uses the reference's own axes for rotated facades. When the subject is inside the reference, its geometry is left out of the reference's bounds. One SetProp on position, one undo step, then the scene is saved; a fresh read verifies the achieved gap and residuals (verify). Returns {moved_by, position, verify:{gap, residuals}}. The scene must be open in the editor (any tab).`, placeAdjacentArgsSchema.shape, runPlacement(placeAdjacent));
|
|
57
|
+
server.tool("summer_attach_to_surface", `Mount a piece on a surface: turn it so its given LOCAL backAxis faces into the surface (opposite the hit normal) with its upAxis kept toward worldUp, then seat its measured back face (the extreme of its visible bounds along backAxis, not its origin) at standoff from the surface. Pipes, gutters, lamps, AC units, signs, fire escapes. Place the piece near its mount first (summer_instantiate_scene with position); this tool turns it and pushes it onto the surface.
|
|
58
|
+
|
|
59
|
+
Find the surface with surface (a node: the ray runs from the subject's origin to the nearest point of that node's bounds) or ray {origin, direction} (both: the ray, and hits on other nodes are skipped). The ray is physics first; if physics finds nothing it falls back to visual AABBs and says so (the normal is then an AABB face normal). Get backAxis from summer_inspect_asset (the piece's back plane normal), not from a guess.
|
|
60
|
+
|
|
61
|
+
placeAt "current" (default): the piece keeps its height and its place along the surface and only moves along the surface normal. placeAt "hit": it also slides so the centre of its back face lands on the ray hit point.
|
|
62
|
+
|
|
63
|
+
Steps (existing ops): a read-only probe (RunSceneScript) reads the piece's bounds in its own axes and casts the ray; one SetProp turns the piece and puts its back face 5 cm in front of the planned seat; SnapToSurface sweeps it along -normal (at most standoff + 0.3) and seats it at standoff (its own physics/visual_aabb evidence). Before saving, the seat is checked:
|
|
64
|
+
- with surface: the seat must be on that node (or inside it), else it is refused;
|
|
65
|
+
- with a ray only: a seat on another node is refused unless it lies on the hit plane (a coplanar neighbour module, warned);
|
|
66
|
+
- the piece must end within maxMove (default 2) of where it started; a longer planned move is refused before anything changes, and so is (with placeAt "current") a ray hit farther than maxMove along the surface from the piece (hit_far_from_piece: aim at the piece, or pass placeAt "hit").
|
|
67
|
+
A refused or failed seat puts the piece back where it started (restored: true), saves nothing, and names the cause: seated_on, the seat's failure_reason, blockers {overlapping, overlaps, first_contact} and a next_step.
|
|
68
|
+
|
|
69
|
+
Returns {seated_on, final_gap (collider gap from SnapToSurface), back_face_gap (visible back face to the hit plane), moved_by, surface_hit, orientation, back_face_offset, seat {evidence, supportPath, finalGap, gapErrorBound, initiallyOverlapping, ...}, saved, warnings}. A warning visible_back_X_into_surface means the piece's collider sits behind its visible back: add X to standoff.`, attachToSurfaceArgsSchema.shape, runPlacement(attachToSurface));
|
|
70
|
+
server.tool("summer_repeat_along", `Instance copies of one scene along a straight line in one call: wall clamps every 0.45 m, braces every 0.8 m, fence posts, a row of window modules. Give start plus end (with spacing or count) or start plus direction (with count and spacing); positions are in the parent's local space. align (start|center|end) places the leftover length when spacing does not divide the line. Each copy is an InstantiateScene (the engine runs each alone), named <namePrefix>_<n>; the copies' transforms (position, rotationDegrees, scale) are then set in one request and the scene is saved once: N copies cost N + 2 engine requests. At most 64 copies per call.
|
|
71
|
+
|
|
72
|
+
Returns a compact receipt: {count, spacing, first, last, created [node paths], renamed, failures [{index, error}], saved}. Lists are cut to stay under 5 KB and the cut is declared.`, repeatAlongArgsSchema.shape, runPlacement(repeatAlong));
|
|
73
|
+
server.tool("summer_connect_ports", `Move and turn one piece so its port meets another piece's port, facing it: pipe to pipe, duct to duct, gutter section to funnel or outlet. A port is an open-loop id from summer_inspect_asset on that node's scene ("+Y" = the outermost open loop facing +Y in the piece's own axes, "+Y#2" the next one facing +Y; resolved on the live node's meshes in its own frame, so the id is the same in every pose), a Marker3D (or any Node3D) name under the node whose -Z axis points out of the port, or an open-loop index in the same stable order.
|
|
74
|
+
|
|
75
|
+
The subject turns by the shortest rotation that makes its port direction opposite the target's, then by rollDegrees about the joined axis, then moves so the ports coincide (gap along the target port's direction). rollDegrees: axis = roll_axis in the receipt (the target port direction reversed, pointing into the target); right-hand rule, positive = counter-clockwise seen from inside the target looking back at the subject; zero = the shortest turn from the subject's CURRENT orientation, so the same value differs between start poses. Once the ports are joined, a second call turns by exactly rollDegrees (180 flips a bend's free end to the other side).
|
|
76
|
+
|
|
77
|
+
Tilt guard: a join that would tilt the subject's up axis (its local +Y) more than maxTiltDegrees (default 5) is refused before anything changes (failure_reason tilt_exceeds_limit, with tilt_degrees and ports_within_limit: the subject ports that would join within the limit, least tilt first). Turning about the up axis is never tilt. Pass allowTilt true for an intended tilt (a bend laid on its side).
|
|
78
|
+
|
|
79
|
+
One SetProp on transform, saved; a fresh read verifies {distance, angle_degrees, tilt_degrees}. Returns {subject_port {kind, id|name, index, position, direction, radius}, target_port, roll_axis, roll_degrees, rotated_degrees, tilt_degrees, max_tilt_degrees, moved_by, other_ports, verify, warnings}. other_ports: every other port of the subject (its other Marker3D anchors, or its other open loops for an open-loop port) with world position and outward direction AFTER the move, measured by the verify read: read where a bend's free end now points instead of measuring it. Open-loop ports are only as exact as the mesh: check direction_ambiguous warnings and a screenshot.`, connectPortsArgsSchema.shape, runPlacement(connectPorts));
|
|
80
|
+
server.tool("summer_raycast", `Cast one ray from any point in an open scene, before anything is placed there: find the wall, floor or ceiling in front of a point and its normal. (summer_starcast casts from an existing node's bounds; this casts from an arbitrary origin.)
|
|
81
|
+
|
|
82
|
+
evidence auto (default): physics first (collider hit: exact point and normal); if physics hits nothing, the nearest visible-mesh AABB hit, declared with fallback:true and fallback_reason. Physics needs the scene to be the active editor tab (only that scene's bodies are in the editor's physics space); otherwise auto falls back to visual AABBs. When physics hits but a mesh-only object is nearer, nearer_visual_only names it.
|
|
83
|
+
|
|
84
|
+
Returns {hit, evidence, path, point, normal, distance, origin, direction, physics_available, warnings}. Read-only (RunSceneScript probe, undo "none"); never saves.`, raycastArgsSchema.shape, runPlacement(raycast));
|
|
85
|
+
server.tool("summer_measure", `Measure placement between specific nodes from their visible-mesh bounds (evidence visual_aabb). Read-only, never saves. Complements summer_starcast (which reports clearance around ONE node in 26 directions).
|
|
86
|
+
|
|
87
|
+
mode pair (a, b): per axis {gap (> 0 clearance, < 0 overlap depth), relation gap|touching|overlap, a/b intervals, delta_min/max/center (b minus a)} and boxes_overlap. Catches facade gaps and modules that overlap.
|
|
88
|
+
mode plane (nodes, face): whether that face of every node lies on one plane: {coplanar, plane (median), spread, nodes [{path, face, deviation, off_plane: proud|recessed}]}. Catches modules standing proud of a facade line. face '+z' = the face pointing along +z.
|
|
89
|
+
|
|
90
|
+
space "local" measures along the axes of a (pair) or nodes[0] (plane), for rotated facades. tolerance (default 5 mm) decides touching / coplanar.`, measureArgsSchema.shape, runPlacement(measure));
|
|
91
|
+
}
|
|
@@ -119,26 +119,29 @@ ladder, honesty rules, anti-patterns, and recovery steps.`, {}, async () => ({
|
|
|
119
119
|
},
|
|
120
120
|
],
|
|
121
121
|
}));
|
|
122
|
-
server.tool("summer_get_project_context", `Get essential project context before editing.
|
|
123
|
-
-
|
|
124
|
-
-
|
|
125
|
-
-
|
|
126
|
-
- main scene path from project settings
|
|
122
|
+
server.tool("summer_get_project_context", `Get essential project context before editing. The default payload is COMPACT (a few KB):
|
|
123
|
+
- project name and project path, current scene path, main scene path
|
|
124
|
+
- sceneSummary: the open scene's root node and node count (not its tree)
|
|
125
|
+
- health: the engine's scalar status fields plus capability COUNTS
|
|
127
126
|
- projectMemory: the .summer summary — GameSoul/build-plan/memory files and
|
|
128
127
|
\`pin\` (.summer/project.json: which template at which commit started this
|
|
129
128
|
project, toolkit version, created_at)
|
|
130
|
-
- capabilitySkewWarning (only when the engine build and this CLI
|
|
131
|
-
apart — non-fatal; explains upcoming 'unknown op' failures)
|
|
129
|
+
- warnings: capabilitySkewWarning (only when the engine build and this CLI
|
|
130
|
+
have drifted apart — non-fatal; explains upcoming 'unknown op' failures),
|
|
131
|
+
rebindError, summerUpdateNotice
|
|
132
|
+
- omitted: how to ask for each heavy block that was left out
|
|
132
133
|
|
|
133
|
-
Use this first in every fresh chat to avoid guessing scene filenames or editing the wrong scene.
|
|
134
|
+
Use this first in every fresh chat to avoid guessing scene filenames or editing the wrong scene. It also binds the session to the open project.
|
|
134
135
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
136
|
+
Heavy blocks are opt-in with include: 'scene_tree' (the open scene's tree, up
|
|
137
|
+
to 200 nodes; for one subtree prefer summer_world_snapshot path_prefix),
|
|
138
|
+
'capabilities' (the engine's full op lists), 'settings' (project settings in
|
|
139
|
+
project.data.entries, trimmed to the curated default groups: application/,
|
|
140
|
+
display/window/, the project's input/ actions, default gravity,
|
|
141
|
+
rendering/renderer/, the 2D default texture filter; the trim is declared in
|
|
142
|
+
settingsTruncated / totalSettings / settingsPrefixesIncluded /
|
|
143
|
+
settingsPrefixesExcluded). settingsPrefixes (e.g. ["audio/", "layer_names/"])
|
|
144
|
+
or settingsPrefix read other settings groups and imply 'settings'.`, projectContextInputShape, async (args) => withEngine(async (client) =>
|
|
142
145
|
// ONE builder for both faces (core/capabilities/project-context.ts);
|
|
143
146
|
// the MCP surface contributes the boot drift notice it owns and logs
|
|
144
147
|
// a capability skew once per process.
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { withEngine, ToolInputError } from "./with-engine.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { executeSceneMutation } from "../../core/capabilities/engine-ops.js";
|
|
4
|
+
import { instantiateScene, runBatch } from "../../core/capabilities/placement-batch.js";
|
|
5
|
+
import { renderPlacementResult } from "./placement-tools.js";
|
|
6
|
+
import { inspectNodeFields, inspectNodeInputShape } from "../../core/capabilities/inspect-node.js";
|
|
7
|
+
import { inspectResource, inspectResourceInputShape } from "../../core/capabilities/inspect-resource.js";
|
|
8
|
+
import { rawSceneReplaceRefusal, replaceNodeInputShape, replaceNodePersisted, } from "../../core/capabilities/replace-node.js";
|
|
9
|
+
import { connectSignalInputShape, connectSignalPersisted } from "../../core/capabilities/connect-signal.js";
|
|
5
10
|
import { FALLBACK_SINGLE_ONLY_OPS, resolveSingleOnlyOps, } from "../../core/capability-skew.js";
|
|
6
11
|
// Re-exported for tests and for callers that reason about dispatch classes.
|
|
7
12
|
// executeSceneMutation is the ONE copy in core/capabilities/engine-ops.ts,
|
|
@@ -28,6 +33,8 @@ function requireSuccessfulOps(result, context) {
|
|
|
28
33
|
function minimalSceneText(rootName, rootType) {
|
|
29
34
|
return `[gd_scene format=3]\n\n[node name="${rootName}" type="${rootType}"]\n`;
|
|
30
35
|
}
|
|
36
|
+
const finiteNumber = z.number().finite();
|
|
37
|
+
const finiteVector3 = z.tuple([finiteNumber, finiteNumber, finiteNumber]);
|
|
31
38
|
// Node names reject . : @ / " %; the type must be a plain class name.
|
|
32
39
|
// Both are interpolated into quoted .tscn fields, so validate before composing.
|
|
33
40
|
const VALID_ROOT_NAME = /^[A-Za-z_][A-Za-z0-9_\- ]*$/;
|
|
@@ -194,7 +201,9 @@ Do not guess paths. Prefer:
|
|
|
194
201
|
|
|
195
202
|
The scene must already exist in the project. Use summer_import_from_url first if importing from external sources.
|
|
196
203
|
|
|
197
|
-
PASS target_size FOR IMPORTED MODELS. Downloaded/generated .glb assets arrive at arbitrary scale (a "chair" can be 40 units tall). target_size uniformly scales the instanced subtree so its largest world-AABB dimension equals that many units — commit to real-world size: chair 1.0, door 2.0, car 4.5, person 1.7, tree 6-10. The result then reports dimensions + scale_applied; verify placement afterwards (summer_world_snapshot AABBs, summer_screenshot). Older engine builds ignore target_size — when the result lacks scale_applied, this tool appends a note and you must scale the node yourself (summer_set_prop scale) and re-check
|
|
204
|
+
PASS target_size FOR IMPORTED MODELS. Downloaded/generated .glb assets arrive at arbitrary scale (a "chair" can be 40 units tall). target_size uniformly scales the instanced subtree so its largest world-AABB dimension equals that many units — commit to real-world size: chair 1.0, door 2.0, car 4.5, person 1.7, tree 6-10. The result then reports dimensions + scale_applied; verify placement afterwards (summer_world_snapshot AABBs, summer_screenshot). Older engine builds ignore target_size — when the result lacks scale_applied, this tool appends a note and you must scale the node yourself (summer_set_prop scale) and re-check.
|
|
205
|
+
|
|
206
|
+
PLACE IT IN THE SAME CALL: position, rotation_degrees and scale ([x, y, z], parent-local, like summer_set_prop), or transform (a "Transform3D(...)" string). The instance is created, then those properties are set on the exact node path the receipt reports (a name collision rename is followed), then the scene is saved: one call per piece, no window at the origin. The result adds placement {nodePath, applied, fields}. Do not combine transform with the others, or scale/transform with target_size. Measure a kit piece first with summer_inspect_asset.`, {
|
|
198
207
|
scenePath: z.string().describe("Target scene to receive the instance, e.g. 'res://main.tscn'"),
|
|
199
208
|
parent: z.string().describe("Parent node path, e.g. './World'"),
|
|
200
209
|
scene: z.string().describe("Scene/model path, e.g. 'res://player.tscn' or 'res://models/tree.glb'"),
|
|
@@ -204,30 +213,18 @@ PASS target_size FOR IMPORTED MODELS. Downloaded/generated .glb assets arrive at
|
|
|
204
213
|
.positive()
|
|
205
214
|
.optional()
|
|
206
215
|
.describe("Normalize the instance's physical size: uniformly scale it so its largest world-AABB dimension equals this many units (chair 1.0, car 4.5, person 1.7). Strongly recommended for imported .glb/.gltf models."),
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const instanced = envelope.results?.find((entry) => entry.op === "InstantiateScene" && entry.ok === true);
|
|
220
|
-
if (instanced && !("scale_applied" in instanced)) {
|
|
221
|
-
return {
|
|
222
|
-
...envelope,
|
|
223
|
-
target_size_note: `This Summer Engine build IGNORED target_size (no scale_applied in the receipt) — the instance is at the asset's raw scale, NOT normalized to ${target_size}. ` +
|
|
224
|
-
"Scale it yourself (summer_set_prop scale, or ctx code in summer_run_script), verify with summer_world_snapshot/summer_screenshot, or update Summer Engine.",
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
return result;
|
|
229
|
-
}));
|
|
230
|
-
server.tool("summer_connect_signal", `Connect a signal between two nodes. Signals are Godot's event system — they notify when something happens.
|
|
216
|
+
position: finiteVector3.optional().describe("Parent-local position [x, y, z] set right after the instance is created."),
|
|
217
|
+
rotation_degrees: finiteVector3.optional().describe("Parent-local rotation in degrees [x, y, z] (Godot rotation_degrees)."),
|
|
218
|
+
scale: finiteVector3.optional().describe("Local scale [x, y, z]. Not with target_size."),
|
|
219
|
+
transform: z
|
|
220
|
+
.string()
|
|
221
|
+
.optional()
|
|
222
|
+
.describe('Full local transform as "Transform3D(xx, xy, xz, yx, yy, yz, zx, zy, zz, ox, oy, oz)" (Godot row-major basis, then origin). Not with position/rotation_degrees/scale/target_size.'),
|
|
223
|
+
}, async (args) =>
|
|
224
|
+
// E2E: an older engine applies the op but silently drops target_size —
|
|
225
|
+
// instantiateScene appends target_size_note when scale_applied is absent.
|
|
226
|
+
withEngine(async (client) => instantiateScene(client, args)));
|
|
227
|
+
server.tool("summer_connect_signal", `Connect a signal between two nodes so the connection is SAVED in the scene. Signals are Godot's event system — they notify when something happens.
|
|
231
228
|
|
|
232
229
|
Common signals:
|
|
233
230
|
- "body_entered" / "body_exited" — Area3D/Area2D detects physics bodies
|
|
@@ -236,15 +233,9 @@ Common signals:
|
|
|
236
233
|
- "area_entered" — Area detects another area
|
|
237
234
|
- "input_event" — CollisionObject received input
|
|
238
235
|
|
|
239
|
-
The receiver
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
signal: z.string().describe("Signal name, e.g. 'body_entered'"),
|
|
243
|
-
receiver: z.string().describe("Node with the handler script, e.g. './Player'"),
|
|
244
|
-
method: z.string().describe("Method name in the receiver's script, e.g. '_on_hit_area_body_entered'"),
|
|
245
|
-
}, async ({ scenePath, emitter, signal, receiver, method }) => withEngine(async (client) => executeSceneMutation(client, scenePath, [
|
|
246
|
-
{ op: "ConnectSignal", emitter, signal, receiver, method },
|
|
247
|
-
])));
|
|
236
|
+
The receiver should have the method (a script method, or a built-in such as queue_free); a missing one is saved anyway and reported in warnings. scenePath must be a .tscn.
|
|
237
|
+
|
|
238
|
+
PERSISTENCE IS VERIFIED: the engine's ConnectSignal op connects without CONNECT_PERSIST, so its connection never reached the file. This tool connects through a RunSceneScript probe with CONNECT_PERSIST (replacing a non-persistent connection of the same pair), saves, reads the saved .tscn back and returns persisted:true / verified:true only when the [connection] line is there; otherwise an error with failure_reason not_persisted. The probe runs in the active tab: a scene in a background tab is brought forward and the user's tab restored (tab_switched). One Ctrl+Z reverts it.`, connectSignalInputShape, async (args) => withEngine(async (client) => connectSignalPersisted(client, args)));
|
|
248
239
|
server.tool("summer_select_node", "Select a node in the editor's scene tree and show it in the inspector panel. Useful for focusing the editor on a specific node.", {
|
|
249
240
|
nodePath: z.string().describe("Node path to select"),
|
|
250
241
|
scenePath: z.string().optional().describe("Open this scene first, then select the node"),
|
|
@@ -254,35 +245,29 @@ The receiver node must have a script with the specified method.`, {
|
|
|
254
245
|
op.scenePath = scenePath;
|
|
255
246
|
return client.executeOps([op]);
|
|
256
247
|
}));
|
|
257
|
-
server.tool("summer_replace_node",
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
if (type)
|
|
265
|
-
op.type = type;
|
|
266
|
-
if (scene)
|
|
267
|
-
op.scene = scene;
|
|
268
|
-
return executeSceneMutation(client, scenePath, [op]);
|
|
269
|
-
}));
|
|
248
|
+
server.tool("summer_replace_node", `Replace a node with a different scene/model or node type, keeping its parent, sibling index, name, transform and property overrides, and the children the scene added under it. Use it to swap a kit piece for another (a wall host for a door host), a placeholder for a prefab, or a StaticBody3D for a RigidBody3D.
|
|
249
|
+
|
|
250
|
+
Give exactly one of scene or type. The scenePath must be a .tscn.
|
|
251
|
+
|
|
252
|
+
PERSISTENCE IS VERIFIED: the tool saves, reads the saved .tscn back and checks that the node at path now instances the new scene (or has the new type), under the same parent, with every child present. persisted:true is proven from the file; a mismatch is an error with failure_reason not_persisted — never report that as done.
|
|
253
|
+
|
|
254
|
+
How: a scene swap runs as InstantiateScene (temporary name) -> SetProp each property override -> ReparentNode the children -> MoveNode to the old index -> RemoveNode the old node -> rename -> SaveScene, because the engine's own ReplaceNode keeps the OLD scene reference in the saved file. A type change of a plain node uses the engine's ReplaceNode. State that cannot travel (groups, signal connections, scene-local sub_resource values, overrides of nodes inside the old scene) is listed in not_carried_over. Undo takes one Ctrl+Z per step.`, replaceNodeInputShape, async (args) => withEngine(async (client) => replaceNodePersisted(client, args)));
|
|
270
255
|
server.tool("summer_inspect_node", `Get all editable properties of a node with their current values, types, and resource info.
|
|
271
256
|
|
|
272
257
|
Call this before modifying a node to understand its current state. Returns every property the Godot inspector would show. Each prop carries the engine's raw Variant.Type integer as "type" plus its name as "type_name" (e.g. type 5 = TYPE_VECTOR2, 20 = TYPE_COLOR, 24 = TYPE_OBJECT); resource-valued props also carry resource_type / resource_path.
|
|
273
258
|
|
|
274
259
|
Reads the currently OPEN scene — "path" is relative to its root (there is no scenePath argument; open the scene first if needed).
|
|
275
260
|
|
|
276
|
-
Example: inspect a light to see its energy, color, shadow settings before changing them
|
|
277
|
-
|
|
278
|
-
|
|
261
|
+
Example: inspect a light to see its energy, color, shadow settings before changing them.
|
|
262
|
+
|
|
263
|
+
The full read is about 5 KB. To read only what you need, pass fields: property names or globs ('position', 'surface_material_override/*'), plus derived fields: transform (local position / rotation_degrees / scale, and a Transform3D literal for 3D nodes), global_transform (world origin + Transform3D, composed from the world snapshot), scene_file_path (the scene this node instances), aabb (world bounds), warnings. Example: fields:['transform','global_transform','scene_file_path'] is a few hundred bytes. missing_fields and unavailable say what could not be read.`, inspectNodeInputShape, async (args) =>
|
|
279
264
|
// E2E 2026-09-03 F-14: the engine returns Variant.Type as a bare int.
|
|
280
|
-
withEngine(async (client) =>
|
|
281
|
-
server.tool("summer_inspect_resource", `
|
|
265
|
+
withEngine(async (client) => inspectNodeFields(client, args)));
|
|
266
|
+
server.tool("summer_inspect_resource", `Read a resource: a material, mesh, shape, texture, environment, or a scene/model file.
|
|
282
267
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
268
|
+
Two forms, pass exactly one:
|
|
269
|
+
- path: a resource FILE ('res://materials/ground.tres', a mesh 'res://kit/meshes/wall_01.res', 'res://models/player.glb'), loaded read-only in the editor. A Mesh returns its AABB, surface_count and per surface the primitive, vertex and index counts, attributes, triangles and material (class, path or embedded, albedo for standard materials), plus the unique materials and blend shapes. A scene or model (.tscn/.glb/.gltf) returns its node count, the first 40 nodes with type, instanced scene and mesh, and its meshes; summer_inspect_asset measures it (AABB, planes, ports). Every other resource (and a mesh) returns its editor properties that differ from the class default (props), with props_at_default counting the rest.
|
|
270
|
+
- nodePath + property: a resource a node of the ACTIVE scene tab holds, e.g. nodePath 'Floor', property 'mesh'. For example, summer_inspect_node tells you a MeshInstance3D has a "StandardMaterial3D" material_override — this form returns its albedo_color, metallic, roughness, etc.`, inspectResourceInputShape, async (args) => withEngine(async (client) => inspectResource(client, args)));
|
|
286
271
|
server.tool("summer_batch", `Execute multiple operations in a single call. Each op is forwarded to the engine VERBATIM, so this is also how you reach engine ops that have no dedicated tool.
|
|
287
272
|
|
|
288
273
|
UNDO: when nothing in the list forces a split (see the single-op contract below), the whole batch is ONE undo step — the user undoes everything with a single Ctrl+Z. When single-only ops are present the list is split into sequential requests and EACH chunk is its own undo step (one Ctrl+Z per chunk; the receipt shows the chunks). Use this when building something that involves multiple nodes and properties — e.g., creating a player character with collision, camera, and properties.
|
|
@@ -297,6 +282,11 @@ RAW RUNTIME OPS (interactive verification — structured failure_reason passes t
|
|
|
297
282
|
- RunVerification — spawn a hidden, disposable game instance that runs a GDScript probe and dies (never touches the editor): {"op": "RunVerification", "probe_source": "...", "max_seconds": 20}. Returns {ok, results, frames, out_dir}. Probe API: report(name, value) / save_frame(name) / press(action) / key(keycode) / finish(). save_frame REQUIRES a name argument — save_frame() with no args is a script error. Mount scenes deferred: get_tree().root.add_child.call_deferred(instance); await get_tree().process_frame; await settle() — a direct add_child in _ready can hit the parent-busy guard and capture a black frame.
|
|
298
283
|
- SimulateInput — inject an action/key/mouse/axis into the RUNNING game (summer_play first): {"op": "SimulateInput", "type": "action", "action": "jump", "pressed": true}. It MUST be sent alone (single-op batch). failure_reason "not_running" = start the game first; "unsupported" = the running game build predates the handler — fall back to RunVerification or ask the user.
|
|
299
284
|
|
|
285
|
+
A raw ReplaceNode with scene is refused (it saves the old scene reference); use summer_replace_node.
|
|
286
|
+
A raw ConnectSignal is refused (the engine connects without CONNECT_PERSIST, so the file never holds it); use summer_connect_signal.
|
|
287
|
+
|
|
288
|
+
REPARENTNODE KEEPS ITS SUBTREE AND IS VERIFIED: the engine's ReparentNode re-owns only the moved node, so its children and grandchildren were dropped from the saved file. A batch with ReparentNode saves first, reads the saved .tscn, sends each ReparentNode in its own request with an in-place ReparentNode (same parent, same index) per scene-owned descendant to give it back to the scene, then reads the saved file again: persisted:true / verified:true only when every moved node and descendant is at its new path; otherwise an error with failure_reason not_persisted. A move onto a parent that already has a child of that name is refused (failure_reason name_collision). The scenePath must be a .tscn, and Undo cannot share the batch.
|
|
289
|
+
|
|
300
290
|
Do not mix OpenScene with scene mutations in one batch. OpenScene is a UI action;
|
|
301
291
|
send it separately. scenePath selects every mutation target. The tool appends one
|
|
302
292
|
final SaveScene when the batch mutates a scene; if supplied explicitly, SaveScene
|
|
@@ -306,10 +296,21 @@ SaveScene, InstantiateScene, ReplaceNode, SimulateInput, the runtime reads
|
|
|
306
296
|
ops to travel as their own request, so this tool automatically splits your op
|
|
307
297
|
list into sequential requests around them — each split chunk is its own undo
|
|
308
298
|
step (NOT one step for the whole batch), and if a later chunk fails the receipt
|
|
309
|
-
reports exactly which earlier ops already applied
|
|
299
|
+
reports exactly which earlier ops already applied.
|
|
300
|
+
|
|
301
|
+
PLACED INSTANCES: an InstantiateScene op may also carry position, rotation_degrees, scale ([x, y, z] or "Vector3(...)") or transform ("Transform3D(...)"); the tool sets them on the created node. One op per piece. Other ops are forwarded verbatim. Cost: each InstantiateScene is its own engine request (the engine requires it); the transforms of a run of InstantiateScene ops are then sent together (up to 200 per request) before the next other op, so N placed pieces plus the save cost about N + 2 requests and N + 2 undo steps. Every later op (a SetProp, SnapToSurface, the save) sees the pieces already placed; if an InstantiateScene fails, the pieces created before it still get their transforms.
|
|
302
|
+
|
|
303
|
+
RECEIPTS: receipt "summary" returns only counts, failures [{index, op, error}] (index = position in your ops list), created node paths and renames, under 5 KB with any cut declared. Use it for any batch over a few ops; the full receipt of a large batch overflows the tool-output limit.
|
|
304
|
+
|
|
305
|
+
scenePersistence.saved means the SaveScene ran; it does not prove what the file
|
|
306
|
+
holds. verified:true is set only where the tool read the saved file back.`, {
|
|
310
307
|
scenePath: z.string().optional().describe("Required when ops contains scene mutations; exact res:// target scene path"),
|
|
311
308
|
ops: z.array(z.record(z.unknown())).describe("Array of operation objects, each with 'op' plus its parameters"),
|
|
312
|
-
|
|
309
|
+
receipt: z
|
|
310
|
+
.enum(["full", "summary"])
|
|
311
|
+
.optional()
|
|
312
|
+
.describe("full (default): every engine receipt. summary: counts, failures with op index, created node paths only (under 5 KB)."),
|
|
313
|
+
}, async ({ scenePath, ops, receipt }) => withEngine(async (client) => {
|
|
313
314
|
const rawFileMutation = ops.find((op) => {
|
|
314
315
|
const kind = String(op.op ?? "");
|
|
315
316
|
return kind === "WriteFile" || kind === "ReplaceText";
|
|
@@ -318,28 +319,15 @@ reports exactly which earlier ops already applied.`, {
|
|
|
318
319
|
throw new ToolInputError(`summer_batch does not accept raw ${String(rawFileMutation.op)} operations. ` +
|
|
319
320
|
"Use summer_write_file or summer_replace_text so project identity, content guards, and same-file ordering are enforced.");
|
|
320
321
|
}
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
//
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const needsScenePath = containsMutation ||
|
|
333
|
-
ops.some((op) => sceneQueries.has(String(op.op ?? "")));
|
|
334
|
-
if (needsScenePath && !scenePath) {
|
|
335
|
-
throw new ToolInputError("summer_batch requires scenePath when ops targets a scene");
|
|
336
|
-
}
|
|
337
|
-
const options = { groupUndo: true, ...(scenePath ? { scenePath } : {}) };
|
|
338
|
-
if (containsMutation) {
|
|
339
|
-
return executeSceneMutation(client, scenePath, ops, options);
|
|
340
|
-
}
|
|
341
|
-
return executeOpsChunked((chunk) => needsScenePath
|
|
342
|
-
? client.executeIdentityBoundOps(chunk, options)
|
|
343
|
-
: client.executeOps(chunk, options), ops, resolveSingleOnlyOps(client));
|
|
344
|
-
}));
|
|
322
|
+
const replaceRefusal = rawSceneReplaceRefusal(ops);
|
|
323
|
+
if (replaceRefusal)
|
|
324
|
+
throw new ToolInputError(replaceRefusal);
|
|
325
|
+
// Scene mutations get one final SaveScene; read-only spatial queries
|
|
326
|
+
// (TestPlacement3D, NavigationProbe3D, Starcast3D) are identity-bound
|
|
327
|
+
// to the scene but never saved (core/capabilities/placement-batch.ts).
|
|
328
|
+
const result = await runBatch(client, { scenePath, ops: ops, receipt });
|
|
329
|
+
return receipt === "summary" ? { summaryReceipt: result } : result;
|
|
330
|
+
}, receipt === "summary"
|
|
331
|
+
? { onResult: (wrapped) => renderPlacementResult(wrapped.summaryReceipt) }
|
|
332
|
+
: undefined));
|
|
345
333
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { type SeeingResult } from "../../core/capabilities/seeing/seeing.js";
|
|
3
|
+
/**
|
|
4
|
+
* Seeing tools: look at a 3D environment the way a player and an artist do.
|
|
5
|
+
* Every image comes back INLINE as an MCP image block (one grid image for a
|
|
6
|
+
* sheet, never N images), with a compact text caption (labels, poses, scores,
|
|
7
|
+
* mark -> path). Renders use the scene's REAL WorldEnvironment and lights.
|
|
8
|
+
* Implementation and the in-engine kernel: src/core/capabilities/seeing/.
|
|
9
|
+
*/
|
|
10
|
+
type ToolResultContent = {
|
|
11
|
+
type: "text";
|
|
12
|
+
text: string;
|
|
13
|
+
} | {
|
|
14
|
+
type: "image";
|
|
15
|
+
data: string;
|
|
16
|
+
mimeType: string;
|
|
17
|
+
};
|
|
18
|
+
type ToolResult = {
|
|
19
|
+
content: ToolResultContent[];
|
|
20
|
+
isError?: boolean;
|
|
21
|
+
};
|
|
22
|
+
export declare function seeingContent(result: SeeingResult): ToolResult;
|
|
23
|
+
export declare function registerSeeingTools(server: McpServer): void;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { withEngine } from "./with-engine.js";
|
|
2
|
+
import { debugViews, frameNodes, frameShot, shotSheet, zoom, } from "../../core/capabilities/seeing/seeing.js";
|
|
3
|
+
import { debugViewsShape, frameNodesShape, frameShotShape, shotSheetShape, zoomShape, } from "../../core/capabilities/seeing/args.js";
|
|
4
|
+
function wrap(result) {
|
|
5
|
+
return result.ok ? { seeing: result } : { seeing: result, failure_reason: result.failure_reason };
|
|
6
|
+
}
|
|
7
|
+
export function seeingContent(result) {
|
|
8
|
+
if (!result.ok) {
|
|
9
|
+
return {
|
|
10
|
+
isError: true,
|
|
11
|
+
content: [
|
|
12
|
+
{
|
|
13
|
+
type: "text",
|
|
14
|
+
text: JSON.stringify({
|
|
15
|
+
error: result.error,
|
|
16
|
+
failure_reason: result.failure_reason,
|
|
17
|
+
...(result.hint ? { hint: result.hint } : {}),
|
|
18
|
+
...(result.detail ? { detail: result.detail } : {}),
|
|
19
|
+
}, null, 2),
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const content = [];
|
|
25
|
+
if (result.image)
|
|
26
|
+
content.push({ type: "image", data: result.image.base64, mimeType: result.image.mime || "image/jpeg" });
|
|
27
|
+
content.push({ type: "text", text: result.caption });
|
|
28
|
+
return { content };
|
|
29
|
+
}
|
|
30
|
+
function run(op) {
|
|
31
|
+
return async (args) => withEngine(async (client) => wrap(await op(client, args)), {
|
|
32
|
+
onResult: (wrapped) => seeingContent(wrapped.seeing),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const COMMON = `Read-only: the scene file, the open tab and the undo history are never touched (the render is an offscreen copy of the SAVED scene). The image arrives inline; the caption carries poses, labels and numbers. Failures are structured (failure_reason), never a silent fallback.`;
|
|
36
|
+
export function registerSeeingTools(server) {
|
|
37
|
+
server.tool("summer_frame_nodes", `Frame one or more nodes with a camera fitted to their WORLD bounds and render it with the scene's REAL WorldEnvironment, lights, fog and tonemap (unlike summer_screenshot nodePath, which only works with the flat preview environment).
|
|
38
|
+
|
|
39
|
+
Pick the side with direction (front/back/left/right/top/iso) or an explicit from vector; fill sets how much of the frame they span. bookmark_name also saves the fitted pose so every later render (summer_shot_sheet, summer_debug_views, summer_screenshot framing:"bookmark") lines up with it. marks:true adds numbered labels mapped to node paths.
|
|
40
|
+
|
|
41
|
+
An explicit from is checked in-engine before you trust the image: if walls block the nodes, or the camera stands behind or inside a one-sided surface (its back is not drawn, so the image would look THROUGH the wall), the caption opens with a WARNING and the nearest valid from (+ fov) to pass back. With marks:true every labelled node gets an occlusion test (centre + 4 bounds points); hidden ones are noted "(hidden behind <path>)".
|
|
42
|
+
|
|
43
|
+
Returns the image + caption: the pose as Vector3 literals, the bounds, the environment used, the view check, and the mark list. ${COMMON}`, frameNodesShape, run(frameNodes));
|
|
44
|
+
server.tool("summer_shot_sheet", `Render several bookmarks and/or explicit poses into ONE labelled grid image in a single call: same tile size, same view, real lighting. Use it after every change to see all hero views at once instead of N screenshots.
|
|
45
|
+
|
|
46
|
+
compare_previous:true turns each bookmark into a row of [previous | now | difference map] and reports the share of pixels that changed and where. Each bookmark keeps exactly one previous render (its compare baseline) at res://.summer/shots/<bookmark>.jpg (JPEG, <= 1024 px; the folder is capped at 20 MB, oldest first). Only compare_previous:true or update_previous:true replaces it; a plain sheet creates a missing baseline and never overwrites one. view renders every tile as lighting/unshaded/normals/overdraw/wireframe instead of beauty.
|
|
47
|
+
|
|
48
|
+
Returns the grid + caption (tile number -> label and pose, difference stats). ${COMMON}`, shotSheetShape, run(shotSheet));
|
|
49
|
+
server.tool("summer_debug_views", `One pose rendered as a grid of debug views next to the beauty pass: beauty, lighting (light only: direction, pools, dead-dark areas), unshaded (albedo: texture quality, value grouping), normals (world-space, x red y green z blue: seams, flipped or faceted normals), overdraw (stacked transparent layers), wireframe (triangle density, floating or duplicated pieces).
|
|
50
|
+
|
|
51
|
+
Use it on the weakest shot of a sheet to see WHY it reads badly. lighting/unshaded/overdraw/wireframe are the engine's Viewport debug draw modes; normals uses an unshaded override material on a private copy (normal maps and alpha cut-outs are not applied) and works on every renderer. The caption names the method per view.
|
|
52
|
+
|
|
53
|
+
Returns the grid + caption. ${COMMON}`, debugViewsShape, run(debugViews));
|
|
54
|
+
server.tool("summer_zoom", `High-resolution close look at part of a frame: region [x, y, w, h] (fractions of the frame) or mark N from a marks render of the same pose. The camera renders the EXACT sub-frustum of that region at full output resolution, so you see real texture detail, seams, gaps and floating pieces, not upscaled pixels. A region is honoured exactly (no pad by default, never widened to an aspect ratio: the image takes the region's own aspect, with dark bars only for extreme shapes); a mark gets pad 0.15.
|
|
55
|
+
|
|
56
|
+
Use after a sheet or debug view shows something suspicious. view picks beauty or a debug view. Returns the zoomed image + caption (the real zoom factor, the rendered region, widened_because when it is larger than asked, mark -> node path and a warning when that node is hidden). ${COMMON}`, zoomShape, run(zoom));
|
|
57
|
+
server.tool("summer_frame_shot", `Smart framing: find good camera poses for a shot type automatically, measured in-engine against the real geometry.
|
|
58
|
+
|
|
59
|
+
shot: establishing (wide), eye_level (from a spawn node at player eye height), low_angle (hero, near the ground looking up: the camera moves closer and widens its FOV instead of sinking into the ground), detail (close-up), corridor (down a lane or corridor found inside the subject by a free-space scan, preferring the view in from its open end, walls on both sides).
|
|
60
|
+
|
|
61
|
+
How: candidate poses on a ring/hemisphere (or along the corridor line) at the distance where the subject fills the shot's target share of the frame; each is checked with a THICK sphere sweep to points on the subject (thin rays miss corners), a near-lens sphere (camera inside or touching geometry is nudged forward or rejected), a ray grid through the frame, and a small beauty render per pose. Walls/terrain blocking the subject reject a pose, and so does a camera behind or inside a one-sided surface (a sight line or a quarter of the frame meeting a wall from behind: its back is not drawn, so the image would look through it). Foliage, fences, props and pipes in front are allowed (wanted, up to a limit) as framing; transparent materials (alpha, scissor, glass, foliage cards) are see-through cover at partial weight. Scored on rule-of-thirds placement, frame fill, level horizon, sky share, depth behind the subject (no flat wall right behind it), empty or featureless areas, near-wall clearance, near/far value contrast, foreground framing, surfaces seen from behind, the key light's direction (side or front-side beats a flat front-lit face) and the world edge (sky or void below the horizon).
|
|
62
|
+
|
|
63
|
+
Returns the top 3 with score breakdowns and poses — three different views (one per side of the subject while the score allows, at least 25 degrees apart) — saves the best as a bookmark (default <shot>_<subject>), and renders the 3 as one sheet with real lighting (render:"best" or "none" to save context). ${COMMON} Saving the bookmark writes res://.summer/camera_bookmarks.json.`, frameShotShape, run(frameShot));
|
|
64
|
+
}
|