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.
Files changed (150) hide show
  1. package/.claude-plugin/plugin.json +2 -1
  2. package/.codex-plugin/plugin.json +3 -2
  3. package/.cursor-plugin/plugin.json +2 -1
  4. package/.opencode/INSTALL.md +1 -1
  5. package/.opencode/plugins/summer.js +1 -1
  6. package/AGENTS.md +4 -4
  7. package/CHANGELOG.md +10 -0
  8. package/GEMINI.md +5 -4
  9. package/README.md +58 -19
  10. package/assets/autopilot/README.md +51 -0
  11. package/assets/autopilot/autopilot.gd +222 -0
  12. package/assets/autopilot/probe_base.gd +262 -0
  13. package/assets/autopilot/run.sh +78 -0
  14. package/commands/gameskill.md +11 -2
  15. package/dist/bin/summer.js +8 -0
  16. package/dist/commands/config.d.ts +2 -0
  17. package/dist/commands/config.js +53 -0
  18. package/dist/commands/create.d.ts +1 -0
  19. package/dist/commands/create.js +38 -6
  20. package/dist/commands/list.js +1 -1
  21. package/dist/commands/login.d.ts +19 -0
  22. package/dist/commands/login.js +123 -36
  23. package/dist/commands/logout.js +3 -16
  24. package/dist/commands/logs.d.ts +2 -0
  25. package/dist/commands/logs.js +19 -0
  26. package/dist/commands/mcp.js +7 -2
  27. package/dist/commands/open.js +1 -1
  28. package/dist/commands/publish.d.ts +2 -0
  29. package/dist/commands/publish.js +20 -0
  30. package/dist/commands/releases.d.ts +2 -0
  31. package/dist/commands/releases.js +20 -0
  32. package/dist/commands/run.js +1 -1
  33. package/dist/lib/api-client.d.ts +4 -2
  34. package/dist/lib/api-client.js +28 -4
  35. package/dist/lib/auth.d.ts +40 -12
  36. package/dist/lib/auth.js +173 -37
  37. package/dist/lib/cloud/engine-bridge.d.ts +17 -4
  38. package/dist/lib/cloud/engine-bridge.js +32 -6
  39. package/dist/lib/config.d.ts +20 -0
  40. package/dist/lib/config.js +142 -0
  41. package/dist/lib/creator.d.ts +93 -0
  42. package/dist/lib/creator.js +393 -0
  43. package/dist/lib/engine.d.ts +35 -2
  44. package/dist/lib/engine.js +240 -6
  45. package/dist/lib/public-surface-paths.d.ts +13 -0
  46. package/dist/lib/public-surface-paths.js +42 -0
  47. package/dist/lib/skills-registry.d.ts +8 -0
  48. package/dist/lib/skills-registry.js +9 -0
  49. package/dist/lib/store.d.ts +14 -0
  50. package/dist/lib/store.js +145 -0
  51. package/dist/lib/summer-compatibility.d.ts +6 -0
  52. package/dist/lib/summer-compatibility.js +22 -0
  53. package/dist/mcp/server.d.ts +8 -1
  54. package/dist/mcp/server.js +25 -4
  55. package/dist/mcp/tools/creator-tools.d.ts +2 -0
  56. package/dist/mcp/tools/creator-tools.js +105 -0
  57. package/dist/mcp/tools/generate-tools.js +16 -6
  58. package/dist/mcp/tools/project-tools.js +12 -7
  59. package/dist/mcp/tools/scene-tools.js +6 -6
  60. package/dist/mcp/tools/visual-tools.js +4 -3
  61. package/hooks/pre-commit-doctor.sh +0 -0
  62. package/hooks/run-hook.cmd +0 -0
  63. package/hooks/session-start.sh +5 -5
  64. package/package.json +3 -1
  65. package/references/collaborative-protocol.md +50 -0
  66. package/references/gd-style.md +121 -0
  67. package/references/godot-version.md +51 -0
  68. package/references/mcp-tools-reference.md +199 -0
  69. package/references/public-surface-language.md +30 -0
  70. package/references/summer-folder.md +90 -0
  71. package/references/template-registry.md +73 -0
  72. package/skills/2d-assets/character-portrait/SKILL.md +17 -12
  73. package/skills/2d-assets/concept-art/SKILL.md +10 -9
  74. package/skills/2d-assets/pixel-art/SKILL.md +36 -20
  75. package/skills/2d-assets/skybox-panorama/SKILL.md +32 -37
  76. package/skills/2d-assets/sprite-sheet/SKILL.md +40 -35
  77. package/skills/2d-assets/tileable-texture/SKILL.md +28 -22
  78. package/skills/2d-assets/ui-graphics/SKILL.md +46 -32
  79. package/skills/2d-assets/use-widget-asset/SKILL.md +8 -6
  80. package/skills/3d-assets/character-model/SKILL.md +34 -19
  81. package/skills/3d-assets/environment-kit/SKILL.md +11 -5
  82. package/skills/3d-assets/organic-model/SKILL.md +8 -5
  83. package/skills/3d-assets/prop-model/SKILL.md +9 -3
  84. package/skills/3d-assets/vehicle-model/SKILL.md +32 -19
  85. package/skills/README.md +1 -1
  86. package/skills/ai-and-npcs/design-npc/SKILL.md +31 -13
  87. package/skills/animation/animation-tree/SKILL.md +39 -15
  88. package/skills/animation/facial-and-lipsync/SKILL.md +25 -8
  89. package/skills/animation/generate-motion/SKILL.md +47 -15
  90. package/skills/animation/procedural-animation/SKILL.md +31 -13
  91. package/skills/animation/retarget/SKILL.md +127 -79
  92. package/skills/audio/adaptive-music/SKILL.md +24 -20
  93. package/skills/audio/ambient-bed/SKILL.md +22 -16
  94. package/skills/audio/audio-direction/SKILL.md +15 -10
  95. package/skills/audio/music-track/SKILL.md +18 -12
  96. package/skills/audio/sound-effect/SKILL.md +17 -12
  97. package/skills/audio/voice-line/SKILL.md +42 -42
  98. package/skills/character-controllers/fps-controller/SKILL.md +26 -16
  99. package/skills/debugging/debug/SKILL.md +52 -20
  100. package/skills/deployment/export-and-ship/SKILL.md +105 -38
  101. package/skills/deployment/remote-deploy/SKILL.md +56 -23
  102. package/skills/gameplay-mechanics/auto-fire-targeting/SKILL.md +26 -1
  103. package/skills/gameplay-mechanics/design-mechanic/SKILL.md +23 -15
  104. package/skills/level-design/design-level/SKILL.md +22 -8
  105. package/skills/level-design/scene-to-level/SKILL.md +13 -7
  106. package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +8 -8
  107. package/skills/multiplayer-and-networking/peer-to-peer-multiplayer/SKILL.md +2 -7
  108. package/skills/multiplayer-and-networking/setup-multiplayer/SKILL.md +26 -18
  109. package/skills/performance/tune-performance/SKILL.md +51 -32
  110. package/skills/rendering-and-lighting/3d-lighting/SKILL.md +34 -5
  111. package/skills/rendering-and-lighting/art-direction/SKILL.md +6 -8
  112. package/skills/scene-and-project/brainstorm-game/SKILL.md +3 -5
  113. package/skills/scene-and-project/make-game/SKILL.md +2 -1
  114. package/skills/scene-and-project/play/SKILL.md +10 -4
  115. package/skills/scene-and-project/scene-composition/SKILL.md +21 -8
  116. package/skills/scene-and-project/summer-cloud/SKILL.md +1 -1
  117. package/skills/ui-and-ux/ui-basics/SKILL.md +16 -7
  118. package/skills/video/animated-loop/SKILL.md +63 -39
  119. package/skills/video/cinematic-cutscene/SKILL.md +66 -30
  120. package/skills/video/trailer-shot/SKILL.md +30 -21
  121. package/skills/visual-effects/_building-blocks/additive-billboard-particles.md +25 -16
  122. package/skills/visual-effects/_building-blocks/trauma-shake-snippet.md +5 -3
  123. package/skills/visual-effects/game-feel/SKILL.md +51 -33
  124. package/skills/visual-effects/recipes/dissolve/SKILL.md +51 -9
  125. package/skills/visual-effects/recipes/fire/SKILL.md +136 -49
  126. package/skills/visual-effects/recipes/hit-spark/SKILL.md +45 -8
  127. package/skills/visual-effects/recipes/lightning/SKILL.md +59 -27
  128. package/skills/visual-effects/recipes/magic-glow/SKILL.md +59 -26
  129. package/skills/visual-effects/recipes/muzzle-flash/SKILL.md +68 -25
  130. package/skills/visual-effects/recipes/smoke/SKILL.md +89 -28
  131. package/skills/visual-effects/recipes/water-ripple/SKILL.md +83 -23
  132. package/skills/workflow/brainstorming/SKILL.md +1 -1
  133. package/skills/workflow/brainstorming/scripts/start-server.sh +0 -0
  134. package/skills/workflow/brainstorming/scripts/stop-server.sh +0 -0
  135. package/skills/workflow/debugging-game-feel/SKILL.md +29 -9
  136. package/skills/workflow/diagnosing-perf-regressions/SKILL.md +46 -21
  137. package/skills/workflow/gameskill/SKILL.md +11 -2
  138. package/skills/workflow/headless-scripting/SKILL.md +407 -0
  139. package/skills/workflow/investigating-bugs/SKILL.md +4 -2
  140. package/skills/workflow/investigating-bugs/find-polluter.sh +0 -0
  141. package/skills/workflow/mcpupdate/SKILL.md +3 -3
  142. package/skills/workflow/playtesting-a-feature/SKILL.md +94 -21
  143. package/skills/workflow/skill-create/SKILL.md +7 -7
  144. package/skills/workflow/skill-test/SKILL.md +3 -3
  145. package/skills/workflow/using-summer/SKILL.md +12 -2
  146. package/skills/workflow/verification-before-completion/SKILL.md +2 -0
  147. package/skills/workflow/writing-plans/SKILL.md +7 -7
  148. package/skills/workflow/writing-skills/SKILL.md +7 -5
  149. package/skills/workflow/writing-skills/render-graphs.js +0 -0
  150. package/skills/workflow/writing-skills/testing-skills-with-subagents.md +1 -1
@@ -1,28 +1,37 @@
1
1
  # Additive Billboard Particles — Canonical Material Settings
2
2
 
3
- Reusable `BaseMaterial3D` configuration for additive particle billboards (motes, sparks, glow, ash). Used by `magic-glow`, optionally substitutable in `hit-spark` and `fire` if a custom shader is overkill.
3
+ Reusable material configuration for additive particle billboards (motes, sparks, glow, ash). Used by `magic-glow`, optionally substitutable in `hit-spark` and `fire` if a custom shader is overkill.
4
+
5
+ `BaseMaterial3D` is abstract — `BaseMaterial3D.new()` is a parse error ("Native class
6
+ "BaseMaterial3D" cannot be constructed as it is abstract"). Instantiate
7
+ `StandardMaterial3D`; the `BaseMaterial3D.*` enum constants below are still the right
8
+ way to name the values.
4
9
 
5
10
  ## Settings
6
11
 
7
- ```
8
- transparency: BaseMaterial3D.TRANSPARENCY_ALPHA
9
- blend_mode: BaseMaterial3D.BLEND_MODE_ADD
10
- shading_mode: BaseMaterial3D.SHADING_MODE_UNSHADED
11
- billboard_mode: BaseMaterial3D.BILLBOARD_PARTICLES
12
- emission_enabled: true
13
- emission_energy_multiplier: 3.0 // 1.0–5.0 typical; bump for HDR bloom
14
- disable_receive_shadows: true
15
- shadow_to_opacity: false
16
- no_depth_test: false // true only if particles must read on TOP of opaque geometry
17
- albedo_color: Color(R, G, B, 1.0)
18
- emission: Color(R, G, B) // usually same as albedo for single-color motes
12
+ ```gdscript
13
+ var bm := StandardMaterial3D.new()
14
+ bm.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
15
+ bm.blend_mode = BaseMaterial3D.BLEND_MODE_ADD
16
+ bm.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
17
+ bm.billboard_mode = BaseMaterial3D.BILLBOARD_PARTICLES
18
+ bm.emission_enabled = true
19
+ bm.emission_energy_multiplier = 3.0 # 1.0–5.0 typical; bump for HDR bloom
20
+ bm.disable_receive_shadows = true
21
+ bm.shadow_to_opacity = false
22
+ bm.no_depth_test = false # true only if particles must read on TOP of opaque geometry
23
+ bm.billboard_keep_scale = true # BILLBOARD_PARTICLES drops per-particle scale without this
24
+ bm.albedo_color = Color(R, G, B, 1.0)
25
+ bm.emission = Color(R, G, B) # usually same as albedo for single-color motes
19
26
  ```
20
27
 
21
28
  ## When to use this vs a custom shader
22
29
 
23
- - **Use this `BaseMaterial3D` config** when the effect is just a tinted soft-circle mote (magic glow, sparkles, dust). Less code, faster iteration, free Godot-managed billboard math.
24
- - **Use a custom `ShaderMaterial`** when you need per-particle color ramps over `CUSTOM.x` age (fire), shape masks (muzzle flash star burst), or stretched streaks (hit-spark). Those can't be expressed in `BaseMaterial3D`.
30
+ - **Use this `StandardMaterial3D` config** when the effect is just a tinted soft-circle mote (magic glow, sparkles, dust). Less code, faster iteration, free Godot-managed billboard math.
31
+ - **Per-particle color over age does NOT need a custom shader.** `ParticleProcessMaterial` has `color_ramp`, `color_initial_ramp`, `alpha_curve` and `emission_curve` (all verified present in 4.6) — those drive the particle's `COLOR` from age and feed straight into a plain `StandardMaterial3D`. Reach for a ramp before reaching for a shader.
32
+ - **Use a custom `ShaderMaterial`** when you need per-fragment shape masks (muzzle flash star burst), noise-distorted UVs (fire, smoke), or anything that varies *within* the quad. Those are what `StandardMaterial3D` genuinely can't express.
33
+ - If a custom shader does need the particle age, note that the age lives in `INSTANCE_CUSTOM.y` (normalized by `INSTANCE_CUSTOM.w`, the lifetime), that `INSTANCE_CUSTOM.x` is the rotation angle — not the age — and that `INSTANCE_CUSTOM` is **vertex-stage only**. Pass it to `fragment()` through a `varying`.
25
34
 
26
35
  ## Performance note
27
36
 
28
- Additive overdraw is fillrate-bound. Keep `draw_pass_1.size <= 0.2` for motes/sparks; larger quads at full additive intensity destroy mobile fillrate budgets fast.
37
+ Additive overdraw is fillrate-bound. `draw_pass_1` is a `Mesh`, so the size dial is on the mesh: keep `QuadMesh.size` at or under `Vector2(0.2, 0.2)` for motes/sparks. Larger quads at full additive intensity destroy mobile fillrate budgets fast.
@@ -22,8 +22,7 @@ func _process(delta: float) -> void:
22
22
  if camera == null:
23
23
  return
24
24
  if trauma > 0.0:
25
- var s = trauma * trauma // perceived intensity grows quadratically
26
- var t = Time.get_ticks_msec() * 0.01
25
+ var s := trauma * trauma # perceived intensity grows quadratically
27
26
  camera.h_offset = max_offset.x * s * (randf() * 2.0 - 1.0)
28
27
  camera.v_offset = max_offset.y * s * (randf() * 2.0 - 1.0)
29
28
  camera.rotation.x += max_pitch * s * (randf() * 2.0 - 1.0) * 0.1
@@ -37,7 +36,10 @@ func add_trauma(amount: float) -> void:
37
36
  trauma = clamp(trauma + amount, 0.0, 1.0)
38
37
  ```
39
38
 
40
- Register `CameraShake` as autoload in Project Settings.
39
+ Register `CameraShake` as autoload in Project Settings. An autoload is a node at
40
+ `/root/CameraShake`, not an engine singleton — reach it with
41
+ `get_node_or_null("/root/CameraShake")`. `Engine.has_singleton("CameraShake")` is
42
+ always `false` for an autoload (measured), so never gate the call on it.
41
43
 
42
44
  ## Usage
43
45
 
@@ -1,17 +1,22 @@
1
1
  ---
2
2
  name: game-feel
3
- description: Use when the user says the game "feels flat", "lacks impact", "needs juice", "needs punch", or asks for hit feedback, screen shake, camera shake, audio ducking, or general "game feel". Walks the user through the canonical Godot 4.5 game-feel stack — hit-flash + trauma camera shake + audio ducking — wired together so a single hit fires all three. Trigger on "vfx", "juice", "punch", "feels flat", "game feel", "hit flash", "screen shake", "camera shake", "ducking".
3
+ description: Use when a Summer game "feels flat", "lacks impact", "needs juice", or "needs punch", or the user asks for hit feedback, screen shake, camera shake, audio ducking, or general game feel. Walks through Summer Engine's hit-flash, trauma camera shake, and audio-ducking stack, wired so one hit fires all three. Trigger on "vfx", "juice", "punch", "feels flat", "game feel", "hit flash", "screen shake", "camera shake", "ducking".
4
4
  license: MIT
5
5
  compatibility: [Cursor, Claude Code, Windsurf, Codex]
6
6
  category: visual-effects
7
7
  user-invocable: true
8
- allowed-tools: Read Grep Edit Write summer_get_scene_tree summer_inspect_node summer_inspect_resource summer_add_node summer_set_prop summer_set_resource_property summer_connect_signal summer_save_scene summer_get_script_errors summer_play summer_stop
8
+ allowed-tools: Read Grep Edit Write summer_write_file summer_read_file summer_get_scene_tree summer_inspect_node summer_inspect_resource summer_add_node summer_set_prop summer_set_resource_property summer_connect_signal summer_project_setting summer_save_scene summer_get_script_errors summer_get_debugger_errors summer_play summer_stop
9
9
  paths: ["**/*.gd", "**/*.tscn", "**/*.tres"]
10
10
  ---
11
11
 
12
12
  # /vfx — The Trio That Makes Games Feel Punchy
13
13
 
14
- Particles don't make a game feel good. Screen shake alone doesn't either. What makes a hit *land in the body* is three systems firing on the same frame: a sub-frame **hit flash** on the body that got hit, **trauma-based camera shake** with quadratic falloff so big hits saturate, and **audio ducking** that briefly squashes Music/Ambient so the impact SFX punches through. Skip any one and the game drops back to "tech demo". This skill installs the canonical Godot 4.5 stack — three small, self-contained systems wired to a single signal — so one `hit()` call fires all three.
14
+ Particles do not make a game feel good. Screen shake alone does not either.
15
+ What makes a hit land is three systems firing on the same frame: a sub-frame
16
+ **hit flash** on the body that got hit, **trauma-based camera shake** with
17
+ quadratic falloff so big hits saturate, and **audio ducking** that briefly
18
+ squashes Music/Ambient so the impact SFX punches through. This skill installs
19
+ the canonical Summer Engine stack as three small systems wired to one signal.
15
20
 
16
21
  ## Step 1 — Ask what feels flat
17
22
 
@@ -62,13 +67,15 @@ World/
62
67
  HitFlash (Node) # holds the script, no transform
63
68
  ```
64
69
 
65
- **MCP path:**
70
+ **MCP path** (every scene-mutating call takes an explicit `scenePath`):
66
71
 
67
72
  ```
68
- summer_add_node(parent="./World/Enemy", type="Node", name="HitFlash")
73
+ summer_add_node(scenePath="res://levels/world.tscn", parent="./World/Enemy", type="Node", name="HitFlash")
74
+ summer_set_prop(scenePath="res://levels/world.tscn", path="./World/Enemy/HitFlash", key="script", value="res://scripts/vfx/hit_flash.gd")
69
75
  ```
70
76
 
71
- Then write `scripts/vfx/hit_flash.gd`:
77
+ Then write `scripts/vfx/hit_flash.gd` (via `summer_write_file`, which requires exactly
78
+ one of `create_only` or `expected_sha256`):
72
79
 
73
80
  ```gdscript
74
81
  class_name HitFlash
@@ -110,7 +117,7 @@ func flash() -> void:
110
117
 
111
118
  **Tunable knobs:** `duration` 0.06–0.12s — under 0.06 reads as a flicker, over 0.12 looks slow. `emission_energy_multiplier` 1.5–3.0.
112
119
 
113
- > CRITICAL: do NOT inline a `StandardMaterial3D` as a `sub_resource` via `summer_set_prop`. Build it in script (as above) or save it to `materials/flash.tres`. Inline sub_resources break `summer_set_resource_property` silently — see `references/mcp-tools-reference.md` § "Trap".
120
+ > CRITICAL: do NOT inline a `StandardMaterial3D` as a `sub_resource` via `summer_set_prop`. Build it in script (as above) or save it to `materials/flash.tres`. Inline sub_resources break `summer_set_resource_property` silently — see `../../../references/mcp-tools-reference.md` § "Trap".
114
121
 
115
122
  ## Step 5 — Install Section 2 (Trauma Camera Shake)
116
123
 
@@ -121,8 +128,8 @@ func flash() -> void:
121
128
  **Node tree:** the script attaches to the existing `./World/Player/Camera3D`. No new nodes.
122
129
 
123
130
  ```
124
- summer_inspect_node "./World/Player/Camera3D"
125
- # attach scripts/vfx/camera_shake.gd
131
+ summer_inspect_node(path="./World/Player/Camera3D")
132
+ summer_set_prop(scenePath="res://levels/world.tscn", path="./World/Player/Camera3D", key="script", value="res://scripts/vfx/camera_shake.gd")
126
133
  ```
127
134
 
128
135
  `scripts/vfx/camera_shake.gd`:
@@ -138,6 +145,11 @@ extends Camera3D
138
145
  var _trauma: float = 0.0
139
146
  var _shake_offset: Vector3 = Vector3.ZERO
140
147
 
148
+ func _ready() -> void:
149
+ # Node has no `groups` property, so there is nothing to set from the outside.
150
+ # Joining the group here is how any actor finds this camera later.
151
+ add_to_group(&"player_camera")
152
+
141
153
  func add_trauma(amount: float) -> void:
142
154
  _trauma = clamp(_trauma + amount, 0.0, 1.0)
143
155
 
@@ -236,12 +248,9 @@ func duck(amount: float, duration: float = 0.5) -> void:
236
248
  var drop_db := -24.0 * amount * weight
237
249
  var target_db := base_db + drop_db
238
250
 
239
- var seq := create_tween()
240
- seq.tween_property(AudioServer, "bus_volume_db", target_db, attack).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)\
241
- .from(base_db)
242
- # NOTE: AudioServer doesn't expose bus volume as an animatable
243
- # property — use a method tween instead. See actual call below.
244
- # (The simpler, real implementation:)
251
+ # AudioServer exposes bus volume through a method, not an animatable
252
+ # property, so tween_method is the only form that works here.
253
+ # `tween_property(AudioServer, "bus_volume_db", ...)` errors at runtime.
245
254
  _tween.tween_method(_set_bus_db.bind(idx), base_db, target_db, attack)\
246
255
  .set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
247
256
  _tween.tween_method(_set_bus_db.bind(idx), target_db, base_db, release)\
@@ -255,9 +264,12 @@ func _set_bus_db(idx: int, db: float) -> void:
255
264
  Register it as an autoload:
256
265
 
257
266
  ```
258
- Project Settings → Autoload → Add scripts/vfx/audio_ducker.gd as "AudioDucker"
267
+ summer_project_setting(...) # autoload/AudioDucker = "*res://scripts/vfx/audio_ducker.gd"
259
268
  ```
260
269
 
270
+ (Equivalently, by hand: Project Settings → Autoload → Add `scripts/vfx/audio_ducker.gd`
271
+ as `AudioDucker`.)
272
+
261
273
  **Calling it:**
262
274
 
263
275
  ```gdscript
@@ -290,25 +302,27 @@ func _on_damaged(amount: float) -> void:
290
302
  # 1. Hit flash on this body.
291
303
  _hit_flash.flash()
292
304
  # 2. Camera shake on the player's camera, scaled by hit weight.
293
- var trauma := clamp(0.2 + amount * 0.05, 0.2, 0.9)
305
+ # clampf(), not clamp(): clamp() returns Variant, and inferring a variable from
306
+ # a Variant is an error under Godot's default warning settings.
307
+ var trauma := clampf(0.2 + amount * 0.05, 0.2, 0.9)
294
308
  if _player_camera:
295
309
  _player_camera.add_trauma(trauma)
296
310
  # 3. Audio duck, scaled by hit weight.
297
- var duck_amount := clamp(0.3 + amount * 0.04, 0.3, 1.0)
311
+ var duck_amount := clampf(0.3 + amount * 0.04, 0.3, 1.0)
298
312
  AudioDucker.duck(duck_amount, 0.45)
299
313
  ```
300
314
 
301
- Add the camera to a group so any actor can find it:
302
-
303
- ```
304
- summer_add_node(...) # ./World/Player/Camera3D already exists
305
- summer_set_prop(path="./World/Player/Camera3D", key="groups", value='["player_camera"]')
306
- ```
315
+ The camera joins `player_camera` from its own `_ready()` (Step 5). `Node` has no
316
+ `groups` property, so `summer_set_prop(key="groups", ...)` cannot do this — and
317
+ `summer_set_prop` only accepts a string, number, or boolean anyway, never an array.
318
+ The two ways that do work are the `add_to_group()` call in `_ready()` or a `groups =
319
+ ["player_camera"]` line written into the `.tscn` node header.
307
320
 
308
- Or wire the signal explicitly via MCP if you prefer:
321
+ Or wire the signal explicitly via MCP if you prefer (`emitter`/`receiver`, not
322
+ `from`/`to`, and `scenePath` is required):
309
323
 
310
324
  ```
311
- summer_connect_signal(from="./World/Enemy", signal="damaged", to="./World/Enemy/HitFlash", method="flash")
325
+ summer_connect_signal(scenePath="res://levels/world.tscn", emitter="./World/Enemy", signal="damaged", receiver="./World/Enemy/HitFlash", method="flash")
312
326
  ```
313
327
 
314
328
  ## Step 8 — Scaling intensity per hit weight
@@ -328,10 +342,11 @@ Because trauma is squared inside the camera, the *felt* difference between 0.35
328
342
  ## Step 9 — Verify
329
343
 
330
344
  ```
331
- summer_save_scene
332
- summer_get_script_errors
345
+ summer_save_scene(scenePath="res://levels/world.tscn")
346
+ summer_get_script_errors # all three scripts parsed?
333
347
  summer_play
334
348
  # user reproduces a hit
349
+ summer_get_debugger_errors # runtime errors from the tweens / bus lookups
335
350
  summer_stop
336
351
  ```
337
352
 
@@ -355,10 +370,12 @@ Tune one knob at a time:
355
370
  | Equal pitch/yaw shake | `yaw_factor ≤ 0.5` | Eyes are sensitive to vertical motion; equal axes feel mechanical |
356
371
  | `Engine.time_scale` for every hit | Reserve hit-stop for big events only (boss hits, deaths) | Time-scale dips on every hit feel laggy, not impactful |
357
372
  | Inline `StandardMaterial3D` sub_resource via `summer_set_prop` | Build in script or save standalone `.tres` | Inline sub_resources silently break `summer_set_resource_property` |
373
+ | `summer_set_prop(key="groups", value='["player_camera"]')` | `add_to_group(&"player_camera")` in `_ready()`, or a `groups = [...]` line in the `.tscn` | `Node` has no `groups` property, and `summer_set_prop` takes only string/number/boolean — never an array |
374
+ | `BaseMaterial3D.new()` | `StandardMaterial3D.new()` | `BaseMaterial3D` is abstract; constructing it is a parse error that kills the whole script |
358
375
 
359
376
  ## Collaborative protocol
360
377
 
361
- This skill writes 3 GDScript files and adds 1 node + 1 autoload entry. Always ask before each section is applied. Group related writes into one ask: "I'm about to add HitFlash + CameraShake + AudioDucker autoload, wire one signal. OK?" See `references/collaborative-protocol.md`.
378
+ This skill writes 3 GDScript files and adds 1 node + 1 autoload entry. Always ask before each section is applied. Group related writes into one ask: "I'm about to add HitFlash + CameraShake + AudioDucker autoload, wire one signal. OK?" See `../../../references/collaborative-protocol.md`.
362
379
 
363
380
  ## When NOT to use this skill
364
381
 
@@ -369,10 +386,11 @@ This skill writes 3 GDScript files and adds 1 node + 1 autoload entry. Always as
369
386
 
370
387
  ## See also
371
388
 
372
- - `references/mcp-tools-reference.md` — full MCP tool list, especially the inline-sub_resource trap
373
- - `references/godot-version.md` — Godot 4.5 API notes
374
- - `references/collaborative-protocol.md` — "May I write" pattern
375
- - `references/gd-style.md` — typed GDScript conventions
389
+ - `../../../references/mcp-tools-reference.md` — full MCP tool list, especially the inline-sub_resource trap
390
+ - `../../../references/godot-version.md` — Summer compatibility and
391
+ version-sensitive API notes
392
+ - `../../../references/collaborative-protocol.md` — "May I write" pattern
393
+ - `../../../references/gd-style.md` — typed GDScript conventions
376
394
  - `audio/audio-direction/SKILL.md` — bus layout setup (prerequisite for ducking)
377
395
  - `post-processing/screen-shake/SKILL.md` — deeper trauma variants (Perlin noise, stacked sources)
378
396
  - `visual-effects/gpuparticles-3d-basics/SKILL.md` — particles to layer on top of the trio
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: [Cursor, Claude Code, Windsurf, Codex]
6
6
  category: visual-effects
7
7
  user-invocable: true
8
- allowed-tools: Read Write Edit summer_add_node summer_set_prop summer_set_resource_property summer_inspect_node summer_save_scene
8
+ allowed-tools: Read Write Edit summer_write_file summer_read_file summer_inspect_node summer_inspect_resource summer_get_script_errors summer_get_debugger_errors summer_play summer_stop
9
9
  paths: ["**/*.tscn", "**/*.gd", "**/*.gdshader", "addons/vfx/**"]
10
10
  ---
11
11
 
@@ -36,12 +36,17 @@ The dissolve shader samples 3D noise per fragment and clips pixels whose noise v
36
36
  ### 1. Files to create
37
37
 
38
38
  ```
39
+ addons/vfx/_building-blocks/noise-3d-fbm.gdshaderinc # copy from this skill FIRST
39
40
  addons/vfx/dissolve/dissolve.gdshader
40
41
  addons/vfx/dissolve/dissolve_controller.gd
41
42
  ```
42
43
 
43
44
  No `.tscn` needed — this overrides materials on existing meshes.
44
45
 
46
+ `dissolve.gdshader` `#include`s the FBM noise file. A missing include is a hard
47
+ compile error, so copy `_building-blocks/noise-3d-fbm.gdshaderinc` from this skill
48
+ into `res://addons/vfx/_building-blocks/` before writing the shader.
49
+
45
50
  ### 2. Shader code
46
51
 
47
52
  `addons/vfx/dissolve/dissolve.gdshader`:
@@ -68,8 +73,16 @@ uniform vec3 noise_offset = vec3(0.0);
68
73
  uniform bool use_object_space = true;
69
74
 
70
75
  void fragment() {
71
- // Sample noise in object space so the dissolve pattern stays attached to the mesh.
72
- vec3 npos = (use_object_space ? (inverse(MODEL_MATRIX) * vec4(VERTEX, 1.0)).xyz : VERTEX) * noise_scale + noise_offset;
76
+ // In fragment(), VERTEX is in VIEW space — not world, not object. Undo the view
77
+ // transform first, then the model transform, or neither branch samples where it
78
+ // claims to (`inverse(MODEL_MATRIX) * vec4(VERTEX, 1.0)` alone is object-space
79
+ // noise driven by camera-relative coordinates, which slides with the camera).
80
+ vec3 wpos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
81
+ vec3 opos = (inverse(MODEL_MATRIX) * vec4(wpos, 1.0)).xyz;
82
+
83
+ // Object space keeps the dissolve pattern attached to the mesh; world space
84
+ // keeps it fixed in the level and lets the mesh move through it.
85
+ vec3 npos = (use_object_space ? opos : wpos) * noise_scale + noise_offset;
73
86
  float n = fbm3(npos);
74
87
 
75
88
  // Discard everything below the threshold.
@@ -204,7 +217,15 @@ No new nodes added. The recipe overrides the `material_override` on every `MeshI
204
217
 
205
218
  ### 5. Wire it in (MCP calls)
206
219
 
207
- This recipe is mostly script-driven, no scene mutation needed. Just create the files (Write tool) and call from gameplay code.
220
+ This recipe is script-driven — no scene mutation. Write the two files with
221
+ `summer_write_file` (one of `create_only` or `expected_sha256` is required), plus the
222
+ `.gdshaderinc`, then call from gameplay code.
223
+
224
+ ```
225
+ summer_write_file(path="res://addons/vfx/_building-blocks/noise-3d-fbm.gdshaderinc", content="<from this skill>", create_only=true)
226
+ summer_write_file(path="res://addons/vfx/dissolve/dissolve.gdshader", content="<section 2>", create_only=true)
227
+ summer_write_file(path="res://addons/vfx/dissolve/dissolve_controller.gd", content="<section 3>", create_only=true)
228
+ ```
208
229
 
209
230
  ```gdscript
210
231
  # When the enemy dies:
@@ -225,6 +246,20 @@ DissolveController.dissolve_object(target, 1.2, Color(1, 0.55, 0.1), 6.0, false)
225
246
  DissolveController.materialize_object(target, 0.8)
226
247
  ```
227
248
 
249
+ ### 5b. Verify
250
+
251
+ Both files are compiled code. Check them before claiming the dissolve works.
252
+
253
+ ```
254
+ summer_get_script_errors # dissolve_controller.gd parsed?
255
+ summer_play
256
+ summer_get_debugger_errors # shader compile errors surface here at runtime
257
+ summer_stop
258
+ ```
259
+
260
+ A missing `noise-3d-fbm.gdshaderinc` fails the whole shader, and the mesh then
261
+ renders with the fallback material rather than dissolving.
262
+
228
263
  ### 6. Parameters to tune
229
264
 
230
265
  | Parameter | Range | Effect |
@@ -234,7 +269,7 @@ DissolveController.materialize_object(target, 0.8)
234
269
  | `edge_color` | Color | rim glow tint; orange = fire, blue = magic, green = poison, white = holy |
235
270
  | `edge_emission` | 0.0–16.0 | rim bloom strength; needs Bloom in WorldEnvironment |
236
271
  | `noise_scale` | 0.5–12.0 | small = big patches dissolving; large = fine pixel-grain dust |
237
- | `use_object_space` | bool | true = pattern attached to mesh; false = pattern stays in world (cool for moving objects) |
272
+ | `use_object_space` | bool | true = pattern attached to mesh; false = pattern stays in world (cool for moving objects). Both branches derive their position from `INV_VIEW_MATRIX * vec4(VERTEX, 1.0)`, because `VERTEX` in `fragment()` is view space |
238
273
 
239
274
  ## Cookbook — named variants
240
275
 
@@ -304,13 +339,15 @@ noise_scale = 4.0
304
339
  - **Dissolving a particle system (e.g., torch flame).** The shader is `spatial`, not `particles`. Stop the particles, don't try to dissolve them.
305
340
  - **Forgetting to seed `noise_offset` per instance.** Two enemies dissolving at the same frame use identical patterns → they look like clones. The included controller randomizes per-instance.
306
341
  - **Using world-space noise on a moving target.** The dissolve pattern slides over the surface as the mesh moves. Use `use_object_space = true` (default).
342
+ - **Treating `VERTEX` in `fragment()` as world space.** It is view space. Sampling noise from it directly makes the pattern slide as the *camera* moves, and `inverse(MODEL_MATRIX) * vec4(VERTEX, 1.0)` is not object space either. Go through `INV_VIEW_MATRIX` first, as section 2 does.
307
343
  - **Skipping the texture inheritance.** The mesh dissolves but goes white because `base_color` defaults to white and no texture is set. The controller pulls from the original `BaseMaterial3D`; if your mesh uses a custom shader, manually wire the textures.
308
344
  - **Dissolving a `Skeleton3D` directly.** It's not a mesh — recurse into `MeshInstance3D` children. The controller's `_collect_meshes` does this.
309
345
 
310
346
  ## Performance notes
311
347
 
312
- - Per fragment: one FBM noise call + one branch. ~0.05 ms per dissolving object at 1080p covering 10% of the screen. Cheap.
313
- - Multiple meshes on a character (head, body, weapon, cape): each gets its own override. ~5 materials × 0.05 ms = 0.25 ms during the dissolve. Still fine.
348
+ - Per fragment: one FBM noise call (4 octaves, 8 hashes each) + one branch + two 4×4 inversions. Sub-millisecond per dissolving object at 1080p covering 10% of the screen — an order-of-magnitude expectation, not a measurement. Profile if you need a number.
349
+ - Multiple meshes on a character (head, body, weapon, cape): each gets its own override, and the cost scales with the number of materials. Still fine for a handful.
350
+ - `inverse(MODEL_MATRIX)` runs per fragment. If you are dissolving a large screen-filling mesh and profiling says this hurts, hoist the object-space position into a `varying` computed in `vertex()`.
314
351
  - `discard` defeats early-Z. For a screen full of dissolving enemies, consider `cull_back` (already enabled) and avoid stacking 20+ dissolves at once.
315
352
  - Mobile: drop `noise_scale` to a value the FBM can compute in 1–2 octaves, or replace the `fbm3` include with a single-octave noise.
316
353
 
@@ -324,11 +361,16 @@ noise_scale = 4.0
324
361
 
325
362
  ## Fallback (no MCP)
326
363
 
327
- VFX is code, no MCP required:
364
+ Section 5 is fully automatable — `summer_write_file` writes all three files and
365
+ `summer_get_script_errors` proves they parsed. Do not hand these steps to the user
366
+ when the MCP tools are available.
367
+
368
+ Without the MCP connection there is no engine to drive, so the user does it themselves:
328
369
 
329
- 1. Create `addons/vfx/dissolve/` and write the two files above.
370
+ 1. Create `addons/vfx/_building-blocks/` and `addons/vfx/dissolve/` and write the three files above.
330
371
  2. From any gameplay script, call `DissolveController.dissolve_object(target_node)`.
331
372
  3. The controller handles material override, texture inheritance, tween, and freeing.
373
+ 4. Check the Errors dock — a missing `.gdshaderinc` fails the whole shader.
332
374
 
333
375
  ## Handoff
334
376