summer-engine 2.7.0 → 2.8.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/plugin.json +2 -1
- package/.codex-plugin/plugin.json +3 -2
- package/.cursor-plugin/plugin.json +2 -1
- package/.opencode/INSTALL.md +1 -1
- package/.opencode/plugins/summer.js +1 -1
- package/AGENTS.md +4 -4
- package/CHANGELOG.md +10 -0
- package/GEMINI.md +5 -4
- package/README.md +58 -19
- package/assets/autopilot/README.md +51 -0
- package/assets/autopilot/autopilot.gd +222 -0
- package/assets/autopilot/probe_base.gd +262 -0
- package/assets/autopilot/run.sh +78 -0
- package/commands/gameskill.md +11 -2
- package/dist/bin/summer.js +8 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +53 -0
- package/dist/commands/create.d.ts +1 -0
- package/dist/commands/create.js +38 -6
- package/dist/commands/list.js +1 -1
- package/dist/commands/login.d.ts +19 -0
- package/dist/commands/login.js +123 -36
- package/dist/commands/logout.js +3 -16
- package/dist/commands/logs.d.ts +2 -0
- package/dist/commands/logs.js +19 -0
- package/dist/commands/mcp.js +7 -2
- package/dist/commands/open.js +1 -1
- package/dist/commands/publish.d.ts +2 -0
- package/dist/commands/publish.js +20 -0
- package/dist/commands/releases.d.ts +2 -0
- package/dist/commands/releases.js +20 -0
- package/dist/commands/run.js +1 -1
- package/dist/lib/api-client.d.ts +4 -2
- package/dist/lib/api-client.js +28 -4
- package/dist/lib/auth.d.ts +40 -12
- package/dist/lib/auth.js +173 -37
- package/dist/lib/cloud/engine-bridge.d.ts +17 -4
- package/dist/lib/cloud/engine-bridge.js +32 -6
- package/dist/lib/config.d.ts +20 -0
- package/dist/lib/config.js +142 -0
- package/dist/lib/creator.d.ts +93 -0
- package/dist/lib/creator.js +393 -0
- package/dist/lib/engine.d.ts +35 -2
- package/dist/lib/engine.js +240 -6
- package/dist/lib/public-surface-paths.d.ts +13 -0
- package/dist/lib/public-surface-paths.js +42 -0
- package/dist/lib/skills-registry.d.ts +8 -0
- package/dist/lib/skills-registry.js +9 -0
- package/dist/lib/store.d.ts +14 -0
- package/dist/lib/store.js +145 -0
- package/dist/lib/summer-compatibility.d.ts +6 -0
- package/dist/lib/summer-compatibility.js +22 -0
- package/dist/mcp/server.d.ts +8 -1
- package/dist/mcp/server.js +25 -4
- package/dist/mcp/tools/creator-tools.d.ts +2 -0
- package/dist/mcp/tools/creator-tools.js +105 -0
- package/dist/mcp/tools/generate-tools.js +16 -6
- package/dist/mcp/tools/project-tools.js +12 -7
- package/dist/mcp/tools/scene-tools.js +6 -6
- package/dist/mcp/tools/visual-tools.js +4 -3
- package/hooks/pre-commit-doctor.sh +0 -0
- package/hooks/run-hook.cmd +0 -0
- package/hooks/session-start.sh +5 -5
- package/package.json +3 -1
- package/references/collaborative-protocol.md +50 -0
- package/references/gd-style.md +121 -0
- package/references/godot-version.md +51 -0
- package/references/mcp-tools-reference.md +199 -0
- package/references/public-surface-language.md +30 -0
- package/references/summer-folder.md +90 -0
- package/references/template-registry.md +73 -0
- package/skills/2d-assets/character-portrait/SKILL.md +17 -12
- package/skills/2d-assets/concept-art/SKILL.md +10 -9
- package/skills/2d-assets/pixel-art/SKILL.md +36 -20
- package/skills/2d-assets/skybox-panorama/SKILL.md +32 -37
- package/skills/2d-assets/sprite-sheet/SKILL.md +40 -35
- package/skills/2d-assets/tileable-texture/SKILL.md +28 -22
- package/skills/2d-assets/ui-graphics/SKILL.md +46 -32
- package/skills/2d-assets/use-widget-asset/SKILL.md +8 -6
- package/skills/3d-assets/character-model/SKILL.md +34 -19
- package/skills/3d-assets/environment-kit/SKILL.md +11 -5
- package/skills/3d-assets/organic-model/SKILL.md +8 -5
- package/skills/3d-assets/prop-model/SKILL.md +9 -3
- package/skills/3d-assets/vehicle-model/SKILL.md +32 -19
- package/skills/README.md +1 -1
- package/skills/ai-and-npcs/design-npc/SKILL.md +31 -13
- package/skills/animation/animation-tree/SKILL.md +39 -15
- package/skills/animation/facial-and-lipsync/SKILL.md +25 -8
- package/skills/animation/generate-motion/SKILL.md +47 -15
- package/skills/animation/procedural-animation/SKILL.md +31 -13
- package/skills/animation/retarget/SKILL.md +127 -79
- package/skills/audio/adaptive-music/SKILL.md +24 -20
- package/skills/audio/ambient-bed/SKILL.md +22 -16
- package/skills/audio/audio-direction/SKILL.md +15 -10
- package/skills/audio/music-track/SKILL.md +18 -12
- package/skills/audio/sound-effect/SKILL.md +17 -12
- package/skills/audio/voice-line/SKILL.md +42 -42
- package/skills/character-controllers/fps-controller/SKILL.md +26 -16
- package/skills/debugging/debug/SKILL.md +52 -20
- package/skills/deployment/export-and-ship/SKILL.md +105 -38
- package/skills/deployment/remote-deploy/SKILL.md +56 -23
- package/skills/gameplay-mechanics/auto-fire-targeting/SKILL.md +26 -1
- package/skills/gameplay-mechanics/design-mechanic/SKILL.md +23 -15
- package/skills/level-design/design-level/SKILL.md +22 -8
- package/skills/level-design/scene-to-level/SKILL.md +13 -7
- package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +8 -8
- package/skills/multiplayer-and-networking/peer-to-peer-multiplayer/SKILL.md +2 -7
- package/skills/multiplayer-and-networking/setup-multiplayer/SKILL.md +26 -18
- package/skills/performance/tune-performance/SKILL.md +51 -32
- package/skills/rendering-and-lighting/3d-lighting/SKILL.md +34 -5
- package/skills/rendering-and-lighting/art-direction/SKILL.md +6 -8
- package/skills/scene-and-project/brainstorm-game/SKILL.md +3 -5
- package/skills/scene-and-project/make-game/SKILL.md +2 -1
- package/skills/scene-and-project/play/SKILL.md +10 -4
- package/skills/scene-and-project/scene-composition/SKILL.md +21 -8
- package/skills/scene-and-project/summer-cloud/SKILL.md +1 -1
- package/skills/ui-and-ux/ui-basics/SKILL.md +16 -7
- package/skills/video/animated-loop/SKILL.md +63 -39
- package/skills/video/cinematic-cutscene/SKILL.md +66 -30
- package/skills/video/trailer-shot/SKILL.md +30 -21
- package/skills/visual-effects/_building-blocks/additive-billboard-particles.md +25 -16
- package/skills/visual-effects/_building-blocks/trauma-shake-snippet.md +5 -3
- package/skills/visual-effects/game-feel/SKILL.md +51 -33
- package/skills/visual-effects/recipes/dissolve/SKILL.md +51 -9
- package/skills/visual-effects/recipes/fire/SKILL.md +136 -49
- package/skills/visual-effects/recipes/hit-spark/SKILL.md +45 -8
- package/skills/visual-effects/recipes/lightning/SKILL.md +59 -27
- package/skills/visual-effects/recipes/magic-glow/SKILL.md +59 -26
- package/skills/visual-effects/recipes/muzzle-flash/SKILL.md +68 -25
- package/skills/visual-effects/recipes/smoke/SKILL.md +89 -28
- package/skills/visual-effects/recipes/water-ripple/SKILL.md +83 -23
- package/skills/workflow/brainstorming/SKILL.md +1 -1
- package/skills/workflow/brainstorming/scripts/start-server.sh +0 -0
- package/skills/workflow/brainstorming/scripts/stop-server.sh +0 -0
- package/skills/workflow/debugging-game-feel/SKILL.md +29 -9
- package/skills/workflow/diagnosing-perf-regressions/SKILL.md +46 -21
- package/skills/workflow/gameskill/SKILL.md +11 -2
- package/skills/workflow/headless-scripting/SKILL.md +407 -0
- package/skills/workflow/investigating-bugs/SKILL.md +4 -2
- package/skills/workflow/investigating-bugs/find-polluter.sh +0 -0
- package/skills/workflow/mcpupdate/SKILL.md +3 -3
- package/skills/workflow/playtesting-a-feature/SKILL.md +94 -21
- package/skills/workflow/skill-create/SKILL.md +7 -7
- package/skills/workflow/skill-test/SKILL.md +3 -3
- package/skills/workflow/using-summer/SKILL.md +12 -2
- package/skills/workflow/verification-before-completion/SKILL.md +2 -0
- package/skills/workflow/writing-plans/SKILL.md +7 -7
- package/skills/workflow/writing-skills/SKILL.md +7 -5
- package/skills/workflow/writing-skills/render-graphs.js +0 -0
- package/skills/workflow/writing-skills/testing-skills-with-subagents.md +1 -1
|
@@ -49,7 +49,7 @@ The bible defines an SFX *class* (UI, Pickup positive, Damage, Footstep, Attack,
|
|
|
49
49
|
Generation is metered. Reuse beats regenerate.
|
|
50
50
|
|
|
51
51
|
```
|
|
52
|
-
summer_search_assets(query="<class> <subject>",
|
|
52
|
+
summer_search_assets(query="<class> <subject>", assetType="audio", source="all")
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
If something fits, ask:
|
|
@@ -93,18 +93,21 @@ Show the prompt and the call before running:
|
|
|
93
93
|
|
|
94
94
|
> Prompt: `metal sword swing whoosh, dry, sharp, 250ms`. Duration 0.5s (model floor). Model: ElevenLabs SFX. Cost: ~1 credit. Generate?
|
|
95
95
|
|
|
96
|
+
The `sound_effects` capability reads the description from **`text`**, not `prompt`. Passing `prompt` here is a 400 `text_required` — `prompt` is only for the `music` capability.
|
|
97
|
+
|
|
96
98
|
```
|
|
97
99
|
summer_generate_audio(
|
|
98
100
|
capability: "sound_effects",
|
|
99
|
-
|
|
101
|
+
text: "metal sword swing whoosh, dry, sharp, 250ms",
|
|
100
102
|
durationSeconds: 0.5
|
|
101
103
|
)
|
|
102
|
-
// Result: { asset: { fileUrl, ... } }
|
|
103
104
|
// Then: summer_import_from_url(url: "<fileUrl>", path: "res://audio/sfx/sword_swing_01.wav")
|
|
104
105
|
```
|
|
105
106
|
|
|
106
107
|
`durationSeconds` floor is 0.5, ceiling is 22. If the user wants a 100ms click, generate 0.5s and the player only plays the front; or trim in the engine.
|
|
107
108
|
|
|
109
|
+
SFX comes back as **WAV**, not MP3 — Summer requests raw PCM from the provider, trims the model's lead-in silence, applies short anti-click fades, and emits a 16-bit mono WAV. A `.wav` target path is therefore correct, and the usual "ElevenLabs one-shots start with dead air" problem is already handled upstream.
|
|
110
|
+
|
|
108
111
|
### 5. Decision: one-shot vs loop
|
|
109
112
|
|
|
110
113
|
| Use case | Setup |
|
|
@@ -126,13 +129,15 @@ Choose the right player node:
|
|
|
126
129
|
|
|
127
130
|
Add it under the emitting node:
|
|
128
131
|
|
|
132
|
+
Every scene-mutating tool takes an explicit `scenePath`, node paths are relative to the scene root (`./`), and the property argument is named `key`, not `property`:
|
|
133
|
+
|
|
129
134
|
```
|
|
130
|
-
summer_add_node(
|
|
131
|
-
summer_set_prop(path="
|
|
132
|
-
summer_set_prop(path="
|
|
133
|
-
summer_set_prop(path="
|
|
134
|
-
summer_set_prop(path="
|
|
135
|
-
summer_set_prop(path="
|
|
135
|
+
summer_add_node(scenePath="res://main.tscn", parent="./Player", type="AudioStreamPlayer3D", name="SwingSFX")
|
|
136
|
+
summer_set_prop(scenePath="res://main.tscn", path="./Player/SwingSFX", key="stream", value="res://audio/sfx/sword_swing_01.wav")
|
|
137
|
+
summer_set_prop(scenePath="res://main.tscn", path="./Player/SwingSFX", key="bus", value="SFX")
|
|
138
|
+
summer_set_prop(scenePath="res://main.tscn", path="./Player/SwingSFX", key="volume_db", value=0.0)
|
|
139
|
+
summer_set_prop(scenePath="res://main.tscn", path="./Player/SwingSFX", key="max_distance", value=25.0)
|
|
140
|
+
summer_set_prop(scenePath="res://main.tscn", path="./Player/SwingSFX", key="attenuation_model", value=0)
|
|
136
141
|
```
|
|
137
142
|
|
|
138
143
|
Bus name must match the bible (`SFX`, `UI`, `Ambient`, etc.). If the bus doesn't exist, run `/audio-direction` step 8.
|
|
@@ -150,7 +155,7 @@ func _ready() -> void:
|
|
|
150
155
|
play()
|
|
151
156
|
```
|
|
152
157
|
|
|
153
|
-
Save it as a `.tscn`, then `summer_instantiate_scene` at the emitter's
|
|
158
|
+
Save it as a `.tscn`, then `summer_instantiate_scene(scenePath, parent, scene, name)` to place it. Note this only wires the node into a scene at author time — spawning one per event at the emitter's runtime position is a job for GDScript (`preload(...).instantiate()` + `add_child`), not for an MCP call. The node frees itself when the stream finishes — no leaks.
|
|
154
159
|
|
|
155
160
|
### 8. Volume calibration
|
|
156
161
|
|
|
@@ -233,5 +238,5 @@ After the SFX is wired:
|
|
|
233
238
|
- `audio/ambient-bed` — long looping textures
|
|
234
239
|
- `audio/music-track` — music
|
|
235
240
|
- `audio/voice-line` — TTS
|
|
236
|
-
-
|
|
237
|
-
-
|
|
241
|
+
- `../../../references/mcp-tools-reference.md`
|
|
242
|
+
- `../../../references/godot-version.md` — Summer compatibility for audio nodes
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: voice-line
|
|
3
|
-
description: Use when generating TTS voice lines — NPC barks, narrator, dialogue.
|
|
3
|
+
description: Use when generating TTS voice lines — NPC barks, narrator, dialogue. Covers where voice ids come from, a character-to-voice decision tree, stability/style/similarity guidance, and the multi-line dialogue case via text_to_dialogue. Trigger on "make a voice line", "narrator", "NPC says", "voice for the merchant", "TTS line", "dialogue".
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: [Cursor, Claude Code, Windsurf, Codex]
|
|
6
6
|
category: audio
|
|
7
7
|
user-invocable: true
|
|
8
|
-
allowed-tools: Read Grep Glob Write Edit
|
|
8
|
+
allowed-tools: Read Grep Glob Write Edit summer_generate_audio summer_search_assets summer_import_from_url summer_add_node summer_set_prop summer_inspect_node
|
|
9
9
|
paths: [".summer/**", "audio/voice/**", "scripts/**", "**/*.tscn"]
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -13,7 +13,7 @@ paths: [".summer/**", "audio/voice/**", "scripts/**", "**/*.tscn"]
|
|
|
13
13
|
|
|
14
14
|
## Overview
|
|
15
15
|
|
|
16
|
-
The hardest part of TTS isn't the line — it's picking the right voice. ElevenLabs ships hundreds; pick wrong and a tough warlord NPC sounds like a podcast host.
|
|
16
|
+
The hardest part of TTS isn't the line — it's picking the right voice. ElevenLabs ships hundreds; pick wrong and a tough warlord NPC sounds like a podcast host. **There is no MCP tool that lists voices** — the only voice-related input Summer exposes is the `voiceId` string on `summer_generate_audio`. So the voice pick has to come from the user (or from the project's cast bible), and this skill's job is to get that pick made deliberately and then locked.
|
|
17
17
|
|
|
18
18
|
Then it generates one line (or a multi-turn dialogue) with the right stability / style / similarity / speed for the delivery — flat for narration, expressive for a bark, fast for an excited shout.
|
|
19
19
|
|
|
@@ -44,24 +44,21 @@ If a character bible exists, the voice should match the character's age / gender
|
|
|
44
44
|
|
|
45
45
|
> Tell me the character: gender, rough age, accent or regional flavor, and energy (calm / measured / excited / gruff). Or just give me a reference — "sounds like the captain in Mass Effect" works.
|
|
46
46
|
|
|
47
|
-
### 2.
|
|
47
|
+
### 2. Get the voice id
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
summer_list_models(family="audio-voice")
|
|
51
|
-
```
|
|
49
|
+
Check the cast bible first (step 1). If the character already has a locked id, use it and skip to step 5.
|
|
52
50
|
|
|
53
|
-
|
|
51
|
+
If not, you need a `voiceId` from the user. There is no catalog tool and no preview-URL endpoint on this surface — do not invent one, and do not claim you auditioned anything. Ask:
|
|
54
52
|
|
|
55
|
-
|
|
53
|
+
> I need an ElevenLabs voice id for `<character>`. Two ways to get one:
|
|
54
|
+
> - Browse and audition at `elevenlabs.io/app/voice-library` (or your Voices page) and paste me the voice id.
|
|
55
|
+
> - Or say "use the default" and I'll generate with Summer's default voice so you can hear the line, then we swap the id later.
|
|
56
|
+
>
|
|
57
|
+
> Describe the target if it helps me sanity-check the pick: gender, rough age, accent, and energy (calm / measured / excited / gruff).
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
> 1. **Brogan** — male, 50s, rough Scottish, gravelly. Sample: `<previewUrl>`
|
|
59
|
-
> 2. **Hodge** — male, 60s, neutral, weathered baritone. Sample: `<previewUrl>`
|
|
60
|
-
> 3. **Marrick** — male, 40s, rough English, mid-energy. Sample: `<previewUrl>`
|
|
61
|
-
> 4. **Old Drum** — male, 70s, deep, slow speech. Sample: `<previewUrl>`
|
|
62
|
-
> Audition and pick one. Or ask for four more.
|
|
59
|
+
Omitting `voiceId` entirely is valid — `summer_generate_audio` falls back to a single default voice (`JBFqnCBsd6RMkjVDRZzb`). That is fine for a throwaway read-through, never for a shipped cast.
|
|
63
60
|
|
|
64
|
-
|
|
61
|
+
Voice is the highest-stakes audio decision in the project. Do not pick an id for the user and do not guess ids — a wrong id is a hard provider error, not a different-sounding voice.
|
|
65
62
|
|
|
66
63
|
### 3. Character-to-voice decision tree (when the user wants a recommendation)
|
|
67
64
|
|
|
@@ -117,18 +114,18 @@ If legacy `.summer/voice-cast.md` already exists, read it first and either keep
|
|
|
117
114
|
|
|
118
115
|
### 5. Tune stability / similarity / style / speed for delivery
|
|
119
116
|
|
|
120
|
-
ElevenLabs
|
|
117
|
+
These are **not** top-level tool arguments. They go inside `options.voiceSettings`, in camelCase, exactly as the ElevenLabs SDK's `VoiceSettings` type spells them — `stability`, `similarityBoost`, `style`, `speed`, `useSpeakerBoost`. `options` is spread straight into the SDK call, so a flat `similarity_boost` at the top of `options` is silently dropped.
|
|
121
118
|
|
|
122
119
|
| Param | Range | Effect |
|
|
123
120
|
|---|---|---|
|
|
124
121
|
| `stability` | 0.0–1.0 | Higher = monotone, consistent. Lower = expressive, variable. |
|
|
125
|
-
| `
|
|
122
|
+
| `similarityBoost` | 0.0–1.0 | Higher = closer to the original sample. Lower = more freedom (can drift). |
|
|
126
123
|
| `style` | 0.0–1.0 | Higher = more theatrical / exaggerated. 0 = neutral. |
|
|
127
124
|
| `speed` | 0.7–1.2 | Speech speed multiplier. |
|
|
128
125
|
|
|
129
126
|
Defaults that work as a starting point:
|
|
130
127
|
|
|
131
|
-
| Delivery | stability |
|
|
128
|
+
| Delivery | stability | similarityBoost | style | speed |
|
|
132
129
|
|---|---|---|---|---|
|
|
133
130
|
| Calm narration | 0.75 | 0.75 | 0.0 | 1.0 |
|
|
134
131
|
| Cinematic narration | 0.5 | 0.85 | 0.4 | 0.95 |
|
|
@@ -166,50 +163,52 @@ Example lines:
|
|
|
166
163
|
### 7. Confirm and call
|
|
167
164
|
|
|
168
165
|
```
|
|
169
|
-
|
|
166
|
+
summer_generate_audio(
|
|
167
|
+
capability="text_to_speech",
|
|
170
168
|
text="Hold the line. They're coming through the south gate. Ready arrows.",
|
|
171
169
|
voiceId="21m00Tcm4TlvDq8ikWAM",
|
|
172
170
|
modelId="eleven_multilingual_v2",
|
|
173
|
-
stability
|
|
174
|
-
similarity_boost=0.75,
|
|
175
|
-
style=0.2,
|
|
176
|
-
speed=1.0,
|
|
177
|
-
outputPath="audio/voice/hodge_hold_the_line.mp3"
|
|
171
|
+
options={ voiceSettings: { stability: 0.6, similarityBoost: 0.75, style: 0.2, speed: 1.0 } }
|
|
178
172
|
)
|
|
179
173
|
```
|
|
180
174
|
|
|
181
175
|
`modelId` defaults to `eleven_multilingual_v2`. For very short / latency-sensitive in-game barks, `eleven_turbo_v2_5` is faster; for highest fidelity, `eleven_multilingual_v2` is the default.
|
|
182
176
|
|
|
177
|
+
There is no `outputPath` — the tool returns a hosted asset (MP3, `audio/mpeg`). Land it in the project explicitly:
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
summer_import_from_url(url="<fileUrl>", path="res://audio/voice/hodge_hold_the_line.mp3")
|
|
181
|
+
```
|
|
182
|
+
|
|
183
183
|
### 8. Multi-line dialogue (two or more speakers)
|
|
184
184
|
|
|
185
185
|
Use `summer_generate_audio` with `capability: "text_to_dialogue"`:
|
|
186
186
|
|
|
187
|
+
There is no `speakers` roster and no `script` array. The whole conversation is one flat `inputs` array of `{ text, voiceId }` objects, in turn order — the voice id is repeated on every line:
|
|
188
|
+
|
|
187
189
|
```
|
|
188
190
|
summer_generate_audio(
|
|
189
191
|
capability="text_to_dialogue",
|
|
190
|
-
|
|
191
|
-
{
|
|
192
|
-
{
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
{ speaker: "Hodge", text: "They're at the gate." },
|
|
196
|
-
{ speaker: "Brogan", text: "Aye. Let 'em come." },
|
|
197
|
-
{ speaker: "Hodge", text: "Steady. On my mark." }
|
|
198
|
-
],
|
|
199
|
-
outputPath="audio/voice/dialogue_gate_warning.mp3"
|
|
192
|
+
inputs=[
|
|
193
|
+
{ text: "They're at the gate.", voiceId: "21m00Tcm4TlvDq8ikWAM" },
|
|
194
|
+
{ text: "Aye. Let 'em come.", voiceId: "<brogan-id>" },
|
|
195
|
+
{ text: "Steady. On my mark.", voiceId: "21m00Tcm4TlvDq8ikWAM" }
|
|
196
|
+
]
|
|
200
197
|
)
|
|
201
198
|
```
|
|
202
199
|
|
|
203
|
-
|
|
200
|
+
`inputs` is required for this capability; omitting it is a 400 `inputs_required`. `modelId` defaults to `eleven_v3` here, not `eleven_multilingual_v2`. The result is a single MP3 with both voices and dialogue-aware pacing the model handles internally — import it with `summer_import_from_url` like any other clip. For very long scripts, split into chunks of ~6 turns and concatenate.
|
|
204
201
|
|
|
205
202
|
### 9. Wire the line as `AudioStreamPlayer` on the Voice bus
|
|
206
203
|
|
|
204
|
+
Every scene-mutating tool takes an explicit `scenePath`, node paths are relative to the scene root (`./`), and the property argument is named `key`, not `property`:
|
|
205
|
+
|
|
207
206
|
```
|
|
208
|
-
summer_add_node(
|
|
209
|
-
summer_set_prop(path="
|
|
210
|
-
summer_set_prop(path="
|
|
211
|
-
summer_set_prop(path="
|
|
212
|
-
summer_set_prop(path="
|
|
207
|
+
summer_add_node(scenePath="res://main.tscn", parent="./NPC", type="AudioStreamPlayer", name="VoiceLine")
|
|
208
|
+
summer_set_prop(scenePath="res://main.tscn", path="./NPC/VoiceLine", key="stream", value="res://audio/voice/hodge_hold_the_line.mp3")
|
|
209
|
+
summer_set_prop(scenePath="res://main.tscn", path="./NPC/VoiceLine", key="bus", value="Voice")
|
|
210
|
+
summer_set_prop(scenePath="res://main.tscn", path="./NPC/VoiceLine", key="volume_db", value=0.0)
|
|
211
|
+
summer_set_prop(scenePath="res://main.tscn", path="./NPC/VoiceLine", key="autoplay", value=false)
|
|
213
212
|
```
|
|
214
213
|
|
|
215
214
|
For positional NPCs, `AudioStreamPlayer3D` with `max_distance=15.0` so distant NPCs aren't audible.
|
|
@@ -239,7 +238,8 @@ Narrator intimate: warmer mid pitch, lower stability for breath
|
|
|
239
238
|
|
|
240
239
|
## Anti-patterns
|
|
241
240
|
|
|
242
|
-
- **
|
|
241
|
+
- **Inventing a voice catalog.** There is no `summer_list_models`, no voice-listing tool, and no preview URLs on this surface. Ask the user for the id.
|
|
242
|
+
- **Guessing a voice id.** A wrong id is a provider error, not a different voice.
|
|
243
243
|
- **Reusing a voice id across two characters.** Players notice instantly. Lock the cast.
|
|
244
244
|
- **Changing a locked voice without approval.** A cast voice is project memory, not a throwaway generation option.
|
|
245
245
|
- **Stage directions in the text.** `(angrily) Get out!` is read literally as "angrily get out". Use stability/style.
|
|
@@ -35,27 +35,40 @@ Always call `summer_get_scene_tree` before mutating. If `World/Player`, `World/P
|
|
|
35
35
|
### 2. Add the Player root
|
|
36
36
|
|
|
37
37
|
```
|
|
38
|
-
summer_add_node(parent="./World", type="CharacterBody3D", name="Player")
|
|
39
|
-
summer_set_prop(path="./World/Player", key="position", value="Vector3(0, 1, 0)")
|
|
38
|
+
summer_add_node(scenePath="res://main.tscn", parent="./World", type="CharacterBody3D", name="Player")
|
|
39
|
+
summer_set_prop(scenePath="res://main.tscn", path="./World/Player", key="position", value="Vector3(0, 1, 0)")
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
### 3. Add the collision shape
|
|
42
|
+
### 3. Add the collision shape and size it
|
|
43
43
|
|
|
44
44
|
```
|
|
45
|
-
summer_add_node(parent="./World/Player", type="CollisionShape3D", name="Collision")
|
|
46
|
-
summer_set_prop(path="./World/Player/Collision", key="shape", value="CapsuleShape3D")
|
|
47
|
-
summer_set_resource_property(nodePath="./World/Player/Collision", resourceProperty="shape", subProperty="radius", value="0.4")
|
|
48
|
-
summer_set_resource_property(nodePath="./World/Player/Collision", resourceProperty="shape", subProperty="height", value="1.8")
|
|
45
|
+
summer_add_node(scenePath="res://main.tscn", parent="./World/Player", type="CollisionShape3D", name="Collision")
|
|
46
|
+
summer_set_prop(scenePath="res://main.tscn", path="./World/Player/Collision", key="shape", value="CapsuleShape3D")
|
|
47
|
+
summer_set_resource_property(scenePath="res://main.tscn", nodePath="./World/Player/Collision", resourceProperty="shape", subProperty="radius", value="0.4")
|
|
48
|
+
summer_set_resource_property(scenePath="res://main.tscn", nodePath="./World/Player/Collision", resourceProperty="shape", subProperty="height", value="1.8")
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
Two things the older version of this skill got wrong:
|
|
52
|
+
|
|
53
|
+
- **`scenePath` is required** on `summer_add_node`, `summer_set_prop`,
|
|
54
|
+
`summer_set_resource_property` and `summer_save_scene`. Omit it and the call
|
|
55
|
+
never reaches the engine. (`summer_input_map_bind` is the exception — it edits
|
|
56
|
+
`project.godot`, not a scene, and takes only `name` + `events`.)
|
|
57
|
+
- **This inline-resource sequence is the supported pattern, not a trap.**
|
|
58
|
+
`summer_set_prop` with a class-name string instantiates a scene-embedded
|
|
59
|
+
(`[sub_resource]`) resource, and `summer_set_resource_property` then reads that
|
|
60
|
+
property off the node and writes the sub-property straight onto it. Embedded
|
|
61
|
+
and external `.tres` resources take the same path. The op has no silent-drop
|
|
62
|
+
branch: the only failures are explicit — `node not found`, `property is not a
|
|
63
|
+
resource`, or `resource is null` when the property was never assigned. Assign
|
|
64
|
+
the resource first, then set its sub-properties.
|
|
52
65
|
|
|
53
66
|
### 4. Add the Head pivot + Camera
|
|
54
67
|
|
|
55
68
|
```
|
|
56
|
-
summer_add_node(parent="./World/Player", type="Node3D", name="Head")
|
|
57
|
-
summer_set_prop(path="./World/Player/Head", key="position", value="Vector3(0, 1.6, 0)")
|
|
58
|
-
summer_add_node(parent="./World/Player/Head", type="Camera3D", name="Camera")
|
|
69
|
+
summer_add_node(scenePath="res://main.tscn", parent="./World/Player", type="Node3D", name="Head")
|
|
70
|
+
summer_set_prop(scenePath="res://main.tscn", path="./World/Player/Head", key="position", value="Vector3(0, 1.6, 0)")
|
|
71
|
+
summer_add_node(scenePath="res://main.tscn", parent="./World/Player/Head", type="Camera3D", name="Camera")
|
|
59
72
|
```
|
|
60
73
|
|
|
61
74
|
Eye height of 1.6 sits the camera right at the top of a 1.8-tall capsule (capsule center 0.9, plus 0.7 to the eye). Tweak per art style.
|
|
@@ -73,7 +86,7 @@ summer_input_map_bind(name="sprint", events=[{type:"key", key:"Shift"}])
|
|
|
73
86
|
|
|
74
87
|
### 6. Attach the controller script
|
|
75
88
|
|
|
76
|
-
Ask before writing: "May I create `scripts/player_controller.gd` and attach it to `./World/Player`?" Then `Write` the file with the skeleton in the next section, attach via inspector, and finish with `summer_save_scene` + `summer_get_script_errors
|
|
89
|
+
Ask before writing: "May I create `scripts/player_controller.gd` and attach it to `./World/Player`?" Then `Write` the file with the skeleton in the next section, attach via inspector, and finish with `summer_save_scene(scenePath="res://main.tscn")` + `summer_get_script_errors(path="res://scripts/player_controller.gd")` (that tool checks one file and requires `path`).
|
|
77
90
|
|
|
78
91
|
## Physics-Process Flow (the order matters)
|
|
79
92
|
|
|
@@ -328,14 +341,11 @@ Then patch `project.godot` with the InputMap actions (`input/move_forward = { ..
|
|
|
328
341
|
|
|
329
342
|
## Collaborative Protocol
|
|
330
343
|
|
|
331
|
-
This skill writes scene nodes, an InputMap, and a `.gd` script. Always ask before applying. Group writes per phase: "May I add the Player + Collision + Head + Camera, bind WASD/jump/sprint, and attach `player_controller.gd`?"
|
|
344
|
+
This skill writes scene nodes, an InputMap, and a `.gd` script. Always ask before applying. Group writes per phase: "May I add the Player + Collision + Head + Camera, bind WASD/jump/sprint, and attach `player_controller.gd`?"
|
|
332
345
|
|
|
333
346
|
If the user asks for a "third-person FPS", flag the contradiction: FPS = first-person. Either clarify, or hand off to the `tps-controller` skill when it ships.
|
|
334
347
|
|
|
335
348
|
## See Also
|
|
336
349
|
|
|
337
|
-
- `references/gd-style.md` — typed GDScript conventions used in the skeleton.
|
|
338
|
-
- `references/mcp-tools-reference.md` — `summer_set_resource_property` rules + the inline-sub-resource silent-fail trap.
|
|
339
|
-
- `references/collaborative-protocol.md` — when to ask before writing.
|
|
340
350
|
- `scripting-patterns/state-machine-patterns/SKILL.md` — once movement grows past this skeleton (slide, grapple, hover), refactor into a state machine instead of stuffing more branches into `_physics_process`.
|
|
341
351
|
- `physics/character-body-tuning/SKILL.md` (when shipped) — slope handling, step-up, floor-snap.
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: debug
|
|
3
|
-
description: Use when the user reports a bug, crash, error, or unexpected behavior in a
|
|
3
|
+
description: Use when the user reports a bug, crash, error, or unexpected behavior in a Summer project, before making code or scene changes. Runs a disciplined script-errors, console, debugger, hypothesis, fix, and verify loop. Trigger on "debug", "crash", "error", "broken", "not working", "freezes", "wrong".
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: [Cursor, Claude Code, Codex, Windsurf, Gemini, OpenCode]
|
|
6
6
|
category: debugging
|
|
7
7
|
user-invocable: true
|
|
8
|
-
allowed-tools: Read Edit Grep summer_create_debug_report summer_get_diagnostics summer_get_script_errors summer_get_console summer_clear_console summer_get_debugger_errors summer_play summer_stop summer_is_running summer_inspect_node summer_inspect_resource summer_get_scene_tree
|
|
8
|
+
allowed-tools: Read Edit Grep summer_create_debug_report summer_get_diagnostics summer_get_script_errors summer_get_console summer_clear_console summer_get_debugger_errors summer_get_debugger_warnings summer_play summer_stop summer_is_running summer_inspect_node summer_inspect_resource summer_get_scene_tree summer_screenshot summer_batch
|
|
9
9
|
paths: ["**/*.gd", "**/*.cs", "**/*.tscn", "**/*.tres", "project.godot"]
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# /debug — Triage and Fix a Bug End-to-End
|
|
13
13
|
|
|
14
|
-
The disciplined debugging loop for
|
|
14
|
+
The disciplined debugging loop for Summer projects. Read the error before
|
|
15
|
+
guessing. Form a hypothesis before editing. Verify the fix before declaring
|
|
16
|
+
victory. Never grep the codebase before reading the actual error.
|
|
15
17
|
|
|
16
18
|
**Core principle:** The debugger already knows what's wrong. Your job is to listen to it before doing anything else.
|
|
17
19
|
|
|
@@ -76,29 +78,37 @@ In strict order. Stop at the first one that returns useful signal.
|
|
|
76
78
|
| 2 | `summer_get_diagnostics` | Aggregate console + debugger error counts. One call, broad picture. |
|
|
77
79
|
| 3 | `summer_get_console` | Read the editor Output panel — print statements, warnings, errors that didn't crash. |
|
|
78
80
|
| 4 | `summer_get_debugger_errors` | Runtime errors caught by the debugger. Use AFTER `summer_play` for runtime-only bugs. |
|
|
81
|
+
| 5 | `summer_get_debugger_warnings` | Warning bodies (file/line/function/callstack). Use when diagnostics reports a non-zero `debugger.warnings`. |
|
|
79
82
|
|
|
80
83
|
**If `summer_get_script_errors` is clean and the user says it crashes only when running:** it's a runtime bug. Go to step 2b.
|
|
81
84
|
|
|
82
85
|
### 2b. Runtime-only bugs
|
|
83
86
|
|
|
87
|
+
**Reproduce it yourself first.** Write a `RunVerification` probe that performs the repro steps (`press` / `key`), reports the state you expect to be wrong, and saves a frame. See "Drive the game yourself" below for the exact call. The returned `results.errors_seen` and `reports` are the diagnostic. This costs one call, touches nothing the user owns, and gives you a repro you can re-run after the fix.
|
|
88
|
+
|
|
89
|
+
Fall back to the editor session only when the repro genuinely needs a human (feel, hardware, "is this what you meant?"):
|
|
90
|
+
|
|
84
91
|
```
|
|
85
92
|
summer_clear_console
|
|
86
93
|
summer_play
|
|
87
94
|
─▶ ASK USER: "Reproduce the bug now."
|
|
88
95
|
(wait for confirmation)
|
|
89
96
|
summer_get_debugger_errors
|
|
97
|
+
summer_get_debugger_warnings (if diagnostics showed warnings)
|
|
90
98
|
summer_stop
|
|
91
99
|
```
|
|
92
100
|
|
|
93
|
-
|
|
101
|
+
If you go this route, do not skip the "reproduce now" prompt. Auto-running and grabbing whatever's in the buffer leads you to chase ghosts from previous sessions.
|
|
94
102
|
|
|
95
103
|
### 2c. MCP unavailable (engine not running, or no Summer install)
|
|
96
104
|
|
|
97
105
|
If `summer_get_script_errors` returns "Summer Engine is not running" or the tool isn't available:
|
|
98
106
|
|
|
99
|
-
1.
|
|
100
|
-
2.
|
|
101
|
-
|
|
107
|
+
1. Tell the user `summer run` starts it. One command restores every tool in this loop — try that before degrading to an interview.
|
|
108
|
+
2. If they cannot or will not, ask the user to copy-paste the Output panel and
|
|
109
|
+
the Debugger panel from Summer Engine.
|
|
110
|
+
3. Reason over the pasted text exactly as you would over MCP output.
|
|
111
|
+
4. Continue with the rest of the loop unchanged.
|
|
102
112
|
|
|
103
113
|
Do NOT loop on MCP retry. Do NOT pretend the engine will come back. Use the fallback the moment it fails once.
|
|
104
114
|
|
|
@@ -133,7 +143,7 @@ Surface the proposed fix in plain language and ask permission. Two patterns:
|
|
|
133
143
|
|
|
134
144
|
- For GDScript edits: `Read` the 20–40 lines around the error, `Edit` the exact change. Don't read the whole file. Don't reformat. Don't rename other things.
|
|
135
145
|
- For scene edits: use the appropriate `summer_*` tool (`summer_add_node`, `summer_set_prop`, `summer_replace_node`). Group multi-step changes in `summer_batch` for one undo step.
|
|
136
|
-
- **Trap to avoid:** never call `summer_set_resource_property` against an inline `sub_resource` — the value is silently dropped. If the property is on a nested resource, first call `summer_set_prop` with the resource class name to instantiate a standalone resource, then drill in. See
|
|
146
|
+
- **Trap to avoid:** never call `summer_set_resource_property` against an inline `sub_resource` — the value is silently dropped. If the property is on a nested resource, first call `summer_set_prop` with the resource class name to instantiate a standalone resource, then drill in. See `../../../references/mcp-tools-reference.md`.
|
|
137
147
|
|
|
138
148
|
### 6. Verify — re-run the diagnostic that found it
|
|
139
149
|
|
|
@@ -158,7 +168,7 @@ If the diagnostic is still red after the fix, you formed the wrong hypothesis. G
|
|
|
158
168
|
| Auto-fix linter warnings unrelated to the bug | Same reason. |
|
|
159
169
|
| Declare victory after editing | Re-run the diagnostic. Always. |
|
|
160
170
|
|
|
161
|
-
## Quick reference — common
|
|
171
|
+
## Quick reference — common Summer Engine bug families
|
|
162
172
|
|
|
163
173
|
| Symptom | First tool | Common cause |
|
|
164
174
|
|---|---|---|
|
|
@@ -180,24 +190,45 @@ Tell the user one short sentence: "Fixed `<file>:<line>` — `<change>`. Diagnos
|
|
|
180
190
|
|
|
181
191
|
## What the MCP debug tools CAN and CAN'T see (read this before claiming "clean")
|
|
182
192
|
|
|
183
|
-
The
|
|
193
|
+
The static and boot-time tools (`summer_get_script_errors`, `summer_get_console`, `summer_get_diagnostics`) **can't substitute for play-testing** — but you have a play-testing route, so a false-clean report is a choice, not a limitation. Know which tool answers which question.
|
|
184
194
|
|
|
185
195
|
### What the MCP CAN see
|
|
186
196
|
|
|
187
197
|
- **Script parse errors** (`summer_get_script_errors`) — full text + file:line. Reliable.
|
|
188
198
|
- **Editor console output** (`summer_get_console`) — `print` statements, editor-side warnings, std startup messages. Full text.
|
|
189
199
|
- **Runtime debugger error count + full text** (`summer_get_debugger_errors`) — returns `errors_data` with `error`, `error_descr`, `callstack`, `file`, `function`, `line`. Reliable for errors.
|
|
190
|
-
- **
|
|
200
|
+
- **Runtime debugger warning text** (`summer_get_debugger_warnings`) — same structured shape as the errors tool, filtered to severity `warning`. Use it whenever `summer_get_diagnostics` shows a non-zero `debugger.warnings`; do not report a warning count you never read.
|
|
201
|
+
- **Diagnostics summary** (`summer_get_diagnostics`) — counts of console errors, debugger errors, debugger warnings, script errors. Tells you where to drill. **Counts only** — it carries no FPS, frame time, draw calls, or physics-body numbers.
|
|
191
202
|
- **Scene tree + node properties** (`summer_get_scene_tree`, `summer_inspect_node`) — only the **edited** scene, not the running game's live tree.
|
|
203
|
+
- **Rendered pixels** (`summer_screenshot`) — `target:"viewport"` for the editor's current view, `target:"scene"` for an offscreen render of a scene file, `target:"game"` for a frame of the running game (that one needs the Summer desktop app bridge and fails cleanly over a plain local connection). You look at the actual image, not a description of it.
|
|
204
|
+
- **Gameplay behaviour, driven by you** (`RunVerification` via `summer_batch`) — see the next section. Input, live tree, and frames from a real running instance.
|
|
192
205
|
- **Whether the game is running** (`summer_is_running`) — and on which scene.
|
|
193
206
|
|
|
194
|
-
###
|
|
207
|
+
### Drive the game yourself — `RunVerification`
|
|
208
|
+
|
|
209
|
+
For any bug that only fires from gameplay (movement, weapon firing, level transitions, button clicks), do **not** hand the repro to the user first. Spawn a hidden, disposable game instance that runs a GDScript probe and dies. It never touches the user's editor session.
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
summer_batch ops:[{
|
|
213
|
+
"op": "RunVerification",
|
|
214
|
+
"probe_source": "extends SummerProbeBase\nfunc _ready() -> void:\n\tawait super._ready()\n\tawait get_tree().process_frame\n\tvar p := get_tree().root.find_child(\"Player\", true, false)\n\treport(\"y0\", p.global_position.y)\n\tawait press(\"jump\", 120)\n\tawait get_tree().create_timer(0.4).timeout\n\treport(\"y1\", p.global_position.y)\n\tsave_frame(\"after\")\n\tdump_tree()\n\tfinish()",
|
|
215
|
+
"max_seconds": 20
|
|
216
|
+
}]
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Probe API: `report(key, value)`, `save_frame(name)`, `dump_tree(max_depth)`, `press(action, hold_ms)`, `key(keycode, hold_ms)`, `finish()`. Returns `{ok, results, frames, out_dir}`; `results.errors_seen` carries error-level engine/script messages from that run, so a probe is also a clean way to catch a runtime error without polluting the editor's debugger buffer.
|
|
220
|
+
|
|
221
|
+
This covers what static diagnostics cannot: pressed input, live scene tree (`dump_tree` reads the *running* tree, unlike `summer_get_scene_tree`), state sampled over several frames, and real rendered frames.
|
|
222
|
+
|
|
223
|
+
`summer_batch` forwards unknown ops verbatim, which is how you reach `RunVerification` with no dedicated tool. Note that the sibling `SimulateInput` op is **not** reachable this way — non-bridge callers get `{ok:false, failure_reason:"unsupported_transport"}`; it only works from the in-editor chat bridge. Drive input with `press()` / `key()` inside a probe instead.
|
|
224
|
+
|
|
225
|
+
### What still genuinely needs the user
|
|
226
|
+
|
|
227
|
+
- **Whether it feels right.** Frame counts and reports cannot tell you the jump feels floaty. See `summer:debugging-game-feel`.
|
|
228
|
+
- **Hardware-specific and non-deterministic behaviour.** A bug that only fires on their GPU, their controller, or one run in twenty.
|
|
229
|
+
- **A judgement call on intent.** "Is this the behaviour you wanted?" is a question, not a measurement.
|
|
195
230
|
|
|
196
|
-
|
|
197
|
-
- **Live runtime scene tree.** `summer_get_scene_tree` returns the editor's edited scene, not what's currently instantiated in the running game. When debugging runtime state, you can't introspect the live nodes.
|
|
198
|
-
- **Keyboard / mouse / controller input simulation.** No tool sends key presses or mouse motion. So bugs that only fire from gameplay (player movement, weapon firing, level transitions, button clicks) require the user to play.
|
|
199
|
-
- **Screenshot / visual diff.** No tool captures the rendered viewport. Visual regressions ("the floor looks wrong", "the camera clips through terrain") require user eyes.
|
|
200
|
-
- **Frame-by-frame physics state.** No tool steps the simulation or watches for transient errors over time.
|
|
231
|
+
Everything else — press the button, read the state, look at the frame — is yours to do.
|
|
201
232
|
|
|
202
233
|
### The "static-only is not testing" rule
|
|
203
234
|
|
|
@@ -207,11 +238,12 @@ When you've made code changes and want to declare them "verified":
|
|
|
207
238
|
|
|
208
239
|
1. **Required**: `summer_get_script_errors` clean on every modified file.
|
|
209
240
|
2. **Required**: `summer_play` boot returns 0 errors (use the scene most likely to exercise the change).
|
|
210
|
-
3. **
|
|
211
|
-
4.
|
|
241
|
+
3. **Required**: exercise the specific scenario the change touches with a `RunVerification` probe — press the input, assert the state, save a frame. Quote the reports you got back.
|
|
242
|
+
4. Ask the user to play only for what a probe cannot judge: feel, hardware-specific behaviour, or whether the result is what they wanted.
|
|
243
|
+
5. **Never claim "verified" from static analysis alone.** Say "compiles and boots clean — needs play-test to confirm <specific behavior>."
|
|
212
244
|
|
|
213
245
|
### The cost of skipping it
|
|
214
246
|
|
|
215
|
-
A typical cautionary scenario: many parallel agents write thousands of lines of game code in one batch with verification static-only. The build is "diagnostic-clean" yet the running game has telegraph meshes that render the wrong axis, parse-valid scripts that crash on first autoload because of a guard pattern that doesn't compile, projectile tracers sized so they read as straight lines instead of projectiles, transform-leaked colliders from a copied scene, and `@onready` paths that throw on every boot when the script is loaded into a sibling scene that lacks one of the referenced children. None of these surface in `summer_get_diagnostics` until the
|
|
247
|
+
A typical cautionary scenario: many parallel agents write thousands of lines of game code in one batch with verification static-only. The build is "diagnostic-clean" yet the running game has telegraph meshes that render the wrong axis, parse-valid scripts that crash on first autoload because of a guard pattern that doesn't compile, projectile tracers sized so they read as straight lines instead of projectiles, transform-leaked colliders from a copied scene, and `@onready` paths that throw on every boot when the script is loaded into a sibling scene that lacks one of the referenced children. None of these surface in `summer_get_diagnostics` until something actually plays the game — which is exactly what a `RunVerification` probe does, and why "I had no way to test it" is not an available excuse.
|
|
216
248
|
|
|
217
249
|
Static analysis is necessary but never sufficient.
|