summer-engine 2.6.6 → 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 (168) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +4 -1
  3. package/.codex-plugin/plugin.json +6 -2
  4. package/.cursor-plugin/plugin.json +5 -1
  5. package/.opencode/INSTALL.md +1 -1
  6. package/.opencode/plugins/summer.js +2 -2
  7. package/AGENTS.md +11 -7
  8. package/CHANGELOG.md +40 -0
  9. package/GEMINI.md +9 -7
  10. package/README.md +63 -21
  11. package/_persona/summer/SKILL.md +1 -0
  12. package/assets/autopilot/README.md +51 -0
  13. package/assets/autopilot/autopilot.gd +222 -0
  14. package/assets/autopilot/probe_base.gd +262 -0
  15. package/assets/autopilot/run.sh +78 -0
  16. package/commands/gameskill.md +11 -2
  17. package/commands/summer.md +13 -0
  18. package/dist/bin/summer.js +12 -0
  19. package/dist/commands/config.d.ts +2 -0
  20. package/dist/commands/config.js +53 -0
  21. package/dist/commands/create.d.ts +1 -0
  22. package/dist/commands/create.js +38 -6
  23. package/dist/commands/debug.d.ts +2 -0
  24. package/dist/commands/debug.js +44 -0
  25. package/dist/commands/doctor.js +112 -8
  26. package/dist/commands/list.js +1 -1
  27. package/dist/commands/login.d.ts +19 -0
  28. package/dist/commands/login.js +123 -36
  29. package/dist/commands/logout.js +3 -16
  30. package/dist/commands/logs.d.ts +2 -0
  31. package/dist/commands/logs.js +19 -0
  32. package/dist/commands/mcp.js +7 -2
  33. package/dist/commands/open.js +1 -1
  34. package/dist/commands/publish.d.ts +2 -0
  35. package/dist/commands/publish.js +20 -0
  36. package/dist/commands/releases.d.ts +2 -0
  37. package/dist/commands/releases.js +20 -0
  38. package/dist/commands/run.js +1 -1
  39. package/dist/commands/skills.js +1 -1
  40. package/dist/lib/api-client.d.ts +15 -4
  41. package/dist/lib/api-client.js +138 -13
  42. package/dist/lib/async-op-lifecycle.d.ts +7 -2
  43. package/dist/lib/async-op-lifecycle.js +32 -9
  44. package/dist/lib/auth.d.ts +40 -12
  45. package/dist/lib/auth.js +173 -37
  46. package/dist/lib/cloud/engine-bridge.d.ts +17 -4
  47. package/dist/lib/cloud/engine-bridge.js +32 -6
  48. package/dist/lib/config.d.ts +20 -0
  49. package/dist/lib/config.js +142 -0
  50. package/dist/lib/creator.d.ts +93 -0
  51. package/dist/lib/creator.js +393 -0
  52. package/dist/lib/debug-report.d.ts +50 -0
  53. package/dist/lib/debug-report.js +308 -0
  54. package/dist/lib/engine.d.ts +35 -2
  55. package/dist/lib/engine.js +240 -6
  56. package/dist/lib/game-task-plan.js +4 -4
  57. package/dist/lib/mcp-boot-notice.d.ts +3 -0
  58. package/dist/lib/mcp-boot-notice.js +7 -0
  59. package/dist/lib/mcp-log.d.ts +3 -0
  60. package/dist/lib/mcp-log.js +72 -0
  61. package/dist/lib/public-surface-paths.d.ts +13 -0
  62. package/dist/lib/public-surface-paths.js +42 -0
  63. package/dist/lib/skills-registry.d.ts +33 -1
  64. package/dist/lib/skills-registry.js +55 -0
  65. package/dist/lib/store.d.ts +14 -0
  66. package/dist/lib/store.js +145 -0
  67. package/dist/lib/summer-compatibility.d.ts +6 -0
  68. package/dist/lib/summer-compatibility.js +22 -0
  69. package/dist/mcp/server.d.ts +10 -3
  70. package/dist/mcp/server.js +104 -11
  71. package/dist/mcp/tools/asset-tools.js +33 -12
  72. package/dist/mcp/tools/cloud-tools.js +15 -15
  73. package/dist/mcp/tools/creator-tools.d.ts +2 -0
  74. package/dist/mcp/tools/creator-tools.js +105 -0
  75. package/dist/mcp/tools/debug-tools.js +44 -1
  76. package/dist/mcp/tools/file-tools.d.ts +2 -0
  77. package/dist/mcp/tools/file-tools.js +154 -0
  78. package/dist/mcp/tools/generate-tools.js +159 -11
  79. package/dist/mcp/tools/project-tools.js +37 -27
  80. package/dist/mcp/tools/scene-tools.js +106 -29
  81. package/dist/mcp/tools/visual-tools.js +4 -3
  82. package/dist/mcp/tools/with-engine.js +4 -4
  83. package/hooks/session-start.sh +5 -5
  84. package/package.json +3 -1
  85. package/references/collaborative-protocol.md +50 -0
  86. package/references/gd-style.md +121 -0
  87. package/references/godot-version.md +51 -0
  88. package/references/mcp-tools-reference.md +199 -0
  89. package/references/public-surface-language.md +30 -0
  90. package/references/summer-folder.md +90 -0
  91. package/references/template-registry.md +73 -0
  92. package/skills/2d-assets/character-portrait/SKILL.md +17 -12
  93. package/skills/2d-assets/concept-art/SKILL.md +10 -9
  94. package/skills/2d-assets/create-asset-sheet/SKILL.md +17 -15
  95. package/skills/2d-assets/pixel-art/SKILL.md +36 -20
  96. package/skills/2d-assets/skybox-panorama/SKILL.md +32 -37
  97. package/skills/2d-assets/sprite-sheet/SKILL.md +40 -35
  98. package/skills/2d-assets/tileable-texture/SKILL.md +28 -22
  99. package/skills/2d-assets/ui-graphics/SKILL.md +46 -32
  100. package/skills/2d-assets/use-widget-asset/SKILL.md +8 -6
  101. package/skills/3d-assets/character-model/SKILL.md +34 -19
  102. package/skills/3d-assets/environment-kit/SKILL.md +11 -5
  103. package/skills/3d-assets/organic-model/SKILL.md +8 -5
  104. package/skills/3d-assets/prop-model/SKILL.md +9 -3
  105. package/skills/3d-assets/vehicle-model/SKILL.md +32 -19
  106. package/skills/README.md +1 -1
  107. package/skills/ai-and-npcs/design-npc/SKILL.md +31 -13
  108. package/skills/animation/animation-tree/SKILL.md +39 -15
  109. package/skills/animation/facial-and-lipsync/SKILL.md +25 -8
  110. package/skills/animation/generate-motion/SKILL.md +47 -15
  111. package/skills/animation/procedural-animation/SKILL.md +31 -13
  112. package/skills/animation/retarget/SKILL.md +127 -79
  113. package/skills/audio/adaptive-music/SKILL.md +24 -20
  114. package/skills/audio/ambient-bed/SKILL.md +22 -16
  115. package/skills/audio/audio-direction/SKILL.md +15 -10
  116. package/skills/audio/music-track/SKILL.md +18 -12
  117. package/skills/audio/sound-effect/SKILL.md +17 -12
  118. package/skills/audio/voice-line/SKILL.md +42 -42
  119. package/skills/character-controllers/fps-controller/SKILL.md +26 -16
  120. package/skills/debugging/debug/SKILL.md +74 -20
  121. package/skills/deployment/export-and-ship/SKILL.md +105 -38
  122. package/skills/deployment/remote-deploy/SKILL.md +140 -0
  123. package/skills/gameplay-mechanics/auto-fire-targeting/SKILL.md +26 -1
  124. package/skills/gameplay-mechanics/design-mechanic/SKILL.md +23 -15
  125. package/skills/level-design/design-level/SKILL.md +22 -8
  126. package/skills/level-design/scene-to-level/SKILL.md +13 -7
  127. package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +8 -8
  128. package/skills/multiplayer-and-networking/peer-to-peer-multiplayer/SKILL.md +2 -7
  129. package/skills/multiplayer-and-networking/setup-multiplayer/SKILL.md +26 -18
  130. package/skills/performance/tune-performance/SKILL.md +51 -32
  131. package/skills/rendering-and-lighting/3d-lighting/SKILL.md +34 -5
  132. package/skills/rendering-and-lighting/art-direction/SKILL.md +6 -8
  133. package/skills/scene-and-project/brainstorm-game/SKILL.md +7 -9
  134. package/skills/scene-and-project/make-game/SKILL.md +2 -1
  135. package/skills/scene-and-project/play/SKILL.md +10 -4
  136. package/skills/scene-and-project/scene-composition/SKILL.md +21 -8
  137. package/skills/scene-and-project/summer-cloud/SKILL.md +92 -0
  138. package/skills/scripting-patterns/gdscript-patterns/SKILL.md +1 -1
  139. package/skills/ui-and-ux/ui-basics/SKILL.md +16 -7
  140. package/skills/video/animated-loop/SKILL.md +63 -39
  141. package/skills/video/cinematic-cutscene/SKILL.md +66 -30
  142. package/skills/video/trailer-shot/SKILL.md +30 -21
  143. package/skills/visual-effects/_building-blocks/additive-billboard-particles.md +25 -16
  144. package/skills/visual-effects/_building-blocks/trauma-shake-snippet.md +5 -3
  145. package/skills/visual-effects/game-feel/SKILL.md +51 -33
  146. package/skills/visual-effects/recipes/dissolve/SKILL.md +51 -9
  147. package/skills/visual-effects/recipes/fire/SKILL.md +136 -49
  148. package/skills/visual-effects/recipes/hit-spark/SKILL.md +45 -8
  149. package/skills/visual-effects/recipes/lightning/SKILL.md +59 -27
  150. package/skills/visual-effects/recipes/magic-glow/SKILL.md +59 -26
  151. package/skills/visual-effects/recipes/muzzle-flash/SKILL.md +68 -25
  152. package/skills/visual-effects/recipes/smoke/SKILL.md +89 -28
  153. package/skills/visual-effects/recipes/water-ripple/SKILL.md +83 -23
  154. package/skills/workflow/brainstorming/SKILL.md +1 -1
  155. package/skills/workflow/debugging-game-feel/SKILL.md +29 -9
  156. package/skills/workflow/diagnosing-perf-regressions/SKILL.md +46 -21
  157. package/skills/workflow/gameskill/SKILL.md +11 -2
  158. package/skills/workflow/headless-scripting/SKILL.md +407 -0
  159. package/skills/workflow/investigating-bugs/SKILL.md +4 -2
  160. package/skills/workflow/mcpupdate/SKILL.md +9 -9
  161. package/skills/workflow/playtesting-a-feature/SKILL.md +94 -21
  162. package/skills/workflow/skill-create/SKILL.md +7 -7
  163. package/skills/workflow/skill-test/SKILL.md +3 -3
  164. package/skills/workflow/using-summer/SKILL.md +32 -3
  165. package/skills/workflow/verification-before-completion/SKILL.md +2 -0
  166. package/skills/workflow/writing-plans/SKILL.md +7 -7
  167. package/skills/workflow/writing-skills/SKILL.md +7 -5
  168. package/skills/workflow/writing-skills/testing-skills-with-subagents.md +1 -1
@@ -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_create_scene summer_add_node summer_set_prop summer_set_resource_property summer_inspect_node summer_inspect_resource summer_save_scene 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,11 +36,16 @@ Smoke is the same particle pattern as fire — billboard quads emitted from a sm
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/smoke/smoke.gdshader
40
41
  addons/vfx/smoke/smoke_controller.gd
41
42
  addons/vfx/smoke/smoke.tscn
42
43
  ```
43
44
 
45
+ `smoke.gdshader` `#include`s the FBM noise file. A missing include is a hard compile
46
+ error, so copy `_building-blocks/noise-3d-fbm.gdshaderinc` from this skill into
47
+ `res://addons/vfx/_building-blocks/` before writing the shader.
48
+
44
49
  ### 2. Shader code
45
50
 
46
51
  `addons/vfx/smoke/smoke.gdshader`:
@@ -59,20 +64,41 @@ uniform float noise_speed : hint_range(0.0, 2.0) = 0.5;
59
64
  uniform float soft_edge : hint_range(0.01, 0.5) = 0.30;
60
65
  uniform float depth_fade : hint_range(0.0, 4.0) = 1.0; // soft against geometry behind
61
66
 
67
+ // INSTANCE_CUSTOM is vertex-stage only, so carry what fragment() needs across.
68
+ varying float age;
69
+ varying float seed;
70
+
62
71
  void vertex() {
63
- MODELVIEW_MATRIX = VIEW_MATRIX * mat4(
64
- INV_VIEW_MATRIX[0],
65
- INV_VIEW_MATRIX[1],
66
- INV_VIEW_MATRIX[2],
67
- MODEL_MATRIX[3]
68
- );
72
+ // Billboard toward the camera, keeping per-particle rotation (INSTANCE_CUSTOM.x)
73
+ // and per-particle scale. The bare `VIEW_MATRIX * mat4(INV_VIEW_MATRIX[0..2],
74
+ // MODEL_MATRIX[3])` form throws both away — this mirrors what Godot's own
75
+ // BILLBOARD_PARTICLES generator emits (scene/resources/material.cpp:1313-1334).
76
+ mat4 mat_world = mat4(
77
+ normalize(INV_VIEW_MATRIX[0]),
78
+ normalize(INV_VIEW_MATRIX[1]),
79
+ normalize(INV_VIEW_MATRIX[2]),
80
+ MODEL_MATRIX[3]);
81
+ mat_world = mat_world * mat4(
82
+ vec4(cos(INSTANCE_CUSTOM.x), -sin(INSTANCE_CUSTOM.x), 0.0, 0.0),
83
+ vec4(sin(INSTANCE_CUSTOM.x), cos(INSTANCE_CUSTOM.x), 0.0, 0.0),
84
+ vec4(0.0, 0.0, 1.0, 0.0),
85
+ vec4(0.0, 0.0, 0.0, 1.0));
86
+ MODELVIEW_MATRIX = VIEW_MATRIX * mat_world * mat4(
87
+ vec4(length(MODEL_MATRIX[0].xyz), 0.0, 0.0, 0.0),
88
+ vec4(0.0, length(MODEL_MATRIX[1].xyz), 0.0, 0.0),
89
+ vec4(0.0, 0.0, length(MODEL_MATRIX[2].xyz), 0.0),
90
+ vec4(0.0, 0.0, 0.0, 1.0));
91
+ MODELVIEW_NORMAL_MATRIX = mat3(MODELVIEW_MATRIX);
92
+
93
+ // CUSTOM.x is the rotation angle, NOT the age. The age lives in CUSTOM.y
94
+ // (it counts up by DELTA/LIFETIME) and CUSTOM.w holds the lifetime scale.
95
+ age = clamp(INSTANCE_CUSTOM.y / max(INSTANCE_CUSTOM.w, 0.0001), 0.0, 1.0);
96
+ seed = fract(sin(float(INSTANCE_ID) * 12.9898) * 43758.5453);
69
97
  }
70
98
 
71
99
  void fragment() {
72
- float age = CUSTOM.x;
73
-
74
100
  // Distort UVs with slow noise.
75
- vec3 np = vec3(UV * noise_scale, TIME * noise_speed + CUSTOM.y * 10.0);
101
+ vec3 np = vec3(UV * noise_scale, TIME * noise_speed + seed * 10.0);
76
102
  float n = fbm3(np);
77
103
 
78
104
  // Soft round mask, modulated by noise so the silhouette is wispy not circular.
@@ -162,6 +188,10 @@ func _apply() -> void:
162
188
 
163
189
  func _make_scale_curve(end_mult: float) -> CurveTexture:
164
190
  var c := Curve.new()
191
+ # Curve clamps every sample to max_value, which defaults to 1.0. Without this
192
+ # line every end multiplier above 1.0 is silently flattened back to 1.0 and the
193
+ # whole cookbook's growth numbers do nothing.
194
+ c.max_value = maxf(1.0, end_mult)
165
195
  c.add_point(Vector2(0.0, 0.4))
166
196
  c.add_point(Vector2(0.3, 1.0))
167
197
  c.add_point(Vector2(1.0, end_mult))
@@ -188,30 +218,52 @@ func stop_smoke(fade_seconds: float = 1.5) -> void:
188
218
  ```
189
219
  GPUParticles3D ("Smoke") [script: smoke_controller.gd]
190
220
  ├── process_material: ParticleProcessMaterial
191
- └── draw_pass_1: QuadMesh (size 1.0 × 1.0)
192
- └── surface_material_override[0]: ShaderMaterial (shader: smoke.gdshader)
221
+ └── draw_pass_1: QuadMesh (size Vector2(1, 1))
222
+ └── material: ShaderMaterial (shader: smoke.gdshader)
193
223
  ```
194
224
 
225
+ `process_material` and `draw_pass_1` are resource properties on the one node, not
226
+ child nodes, and `ParticleProcessMaterial` is a `Material` — you cannot
227
+ `summer_add_node` it.
228
+
195
229
  ### 5. Wire it in (MCP calls)
196
230
 
231
+ Every scene-mutating call takes an explicit `scenePath`. `summer_set_prop` uses `key`
232
+ (not `property`) and only accepts a string, number, or boolean.
233
+
197
234
  For chimney smoke or fire pairing, parent the Smoke just above the heat source:
198
235
 
199
236
  ```
200
- summer_add_node(parent="./World/Cottage/Chimney", type="GPUParticles3D", name="Smoke")
201
- summer_set_prop(path="./World/Cottage/Chimney/Smoke", property="script", value="res://addons/vfx/smoke/smoke_controller.gd")
202
- summer_set_prop(path="./World/Cottage/Chimney/Smoke", property="position", value="Vector3(0, 0.3, 0)")
203
- summer_set_prop(path="./World/Cottage/Chimney/Smoke", property="rise_height", value=4.0)
204
- summer_set_prop(path="./World/Cottage/Chimney/Smoke", property="particle_count", value=48)
205
- summer_set_prop(path="./World/Cottage/Chimney/Smoke", property="wind_direction", value="Vector3(0.4, 0, 0.1)")
206
- summer_save_scene
237
+ summer_write_file(path="res://addons/vfx/_building-blocks/noise-3d-fbm.gdshaderinc", content="<from this skill>", create_only=true)
238
+ summer_write_file(path="res://addons/vfx/smoke/smoke.gdshader", content="<section 2>", create_only=true)
239
+ summer_write_file(path="res://addons/vfx/smoke/smoke_controller.gd", content="<section 3>", create_only=true)
240
+
241
+ summer_add_node(scenePath="res://main.tscn", parent="./World/Cottage/Chimney", type="GPUParticles3D", name="Smoke")
242
+ summer_set_prop(scenePath="res://main.tscn", path="./World/Cottage/Chimney/Smoke", key="script", value="res://addons/vfx/smoke/smoke_controller.gd")
243
+ summer_set_prop(scenePath="res://main.tscn", path="./World/Cottage/Chimney/Smoke", key="position", value="Vector3(0, 0.3, 0)")
244
+ summer_set_prop(scenePath="res://main.tscn", path="./World/Cottage/Chimney/Smoke", key="rise_height", value=4.0)
245
+ summer_set_prop(scenePath="res://main.tscn", path="./World/Cottage/Chimney/Smoke", key="particle_count", value=48)
246
+ summer_set_prop(scenePath="res://main.tscn", path="./World/Cottage/Chimney/Smoke", key="wind_direction", value="Vector3(0.4, 0, 0.1)")
247
+ summer_save_scene(scenePath="res://main.tscn")
207
248
  ```
208
249
 
209
250
  For pairing with a fire:
210
251
 
211
252
  ```
212
- summer_add_node(parent="./World/Campfire", type="GPUParticles3D", name="Smoke")
213
- summer_set_prop(path="./World/Campfire/Smoke", property="position", value="Vector3(0, 1.5, 0)") # above flame top
214
- summer_set_prop(path="./World/Campfire/Smoke", property="rise_height", value=3.0)
253
+ summer_add_node(scenePath="res://main.tscn", parent="./World/Campfire", type="GPUParticles3D", name="Smoke")
254
+ summer_set_prop(scenePath="res://main.tscn", path="./World/Campfire/Smoke", key="position", value="Vector3(0, 1.5, 0)") # above flame top
255
+ summer_set_prop(scenePath="res://main.tscn", path="./World/Campfire/Smoke", key="rise_height", value=3.0)
256
+ ```
257
+
258
+ ### 5b. Verify
259
+
260
+ The shader and the controller are compiled code. Check them before tuning anything.
261
+
262
+ ```
263
+ summer_get_script_errors # smoke_controller.gd parsed?
264
+ summer_play
265
+ summer_get_debugger_errors # shader compile errors surface here at runtime
266
+ summer_stop
215
267
  ```
216
268
 
217
269
  For a rocket trail (smoke follows the rocket):
@@ -323,13 +375,15 @@ color_old = Color(0.65, 0.68, 0.75)
323
375
  - **Forgetting `cull_disabled`.** Billboarded quads disappear at certain camera angles otherwise.
324
376
  - **`puff_lifetime < 1.0`.** Smoke needs time to spread out and dilute. Short-lifetime smoke looks like spray.
325
377
  - **No scale curve.** Smoke that doesn't grow looks like a row of identical balls. The controller's `_make_scale_curve` ramps from 0.4× → 1.0× → end_mult.
378
+ - **A scale curve built without raising `Curve.max_value`.** `Curve` defaults to `max_value = 1.0` and silently clamps, so every `end_scale_multiplier` above 1.0 is inert and the smoke never grows. Set `c.max_value` before adding the points.
379
+ - **Reading `CUSTOM` in `fragment()`.** In a `spatial` shader `CUSTOM` does not exist at all ("SHADER ERROR: Unknown identifier in expression: 'CUSTOM'"). The particle built-in is `INSTANCE_CUSTOM`, it is vertex-stage only, and `INSTANCE_CUSTOM.x` is the rotation angle — the age is in `.y`.
326
380
  - **`emission_energy > 0` on the shader's quad material.** Smoke isn't bright. Keep `unshaded` and let `ALBEDO` carry the look. (The shader as written has no EMISSION line — correct.)
327
381
  - **Smoke local-space when paired with a moving emitter (rocket).** Set `local_coords = false` so puffs stay where they were emitted.
328
- - **Skipping the angular velocity.** Static-rotation smoke looks weird. The controller adds ±25 deg/s for organic tumbling.
382
+ - **Skipping the angular velocity.** Static-rotation smoke looks weird. The controller adds ±25 deg/s for organic tumbling — but that only reaches the screen if the vertex billboard applies the `INSTANCE_CUSTOM.x` rotation, which is why section 2 uses the full block rather than the bare `VIEW_MATRIX * mat4(INV_VIEW_MATRIX[0..2], MODEL_MATRIX[3])` form.
329
383
 
330
384
  ## Performance notes
331
385
 
332
- - Default 48 particles per smoke source: ~0.03 ms on desktop.
386
+ - Default 48 particles per smoke source is sub-millisecond on desktop. That is an order-of-magnitude expectation, not a measurement — profile your own scene.
333
387
  - Heavy `density` at high count (128 particles, density 0.85) gets fillrate-bound — overdraw on a screen full of smoke. Budget ~256 smoke particles total visible at once.
334
388
  - Mobile: drop default to 24, drop `puff_lifetime` to 2.5, drop `density` to 0.35.
335
389
  - LOD: scale `amount_ratio` by `clamp(1.5 - dist_to_camera / 40.0, 0.10, 1.0)` for distant chimneys.
@@ -356,13 +410,20 @@ ALPHA *= clamp(d * depth_fade, 0.0, 1.0);
356
410
 
357
411
  ## Fallback (no MCP)
358
412
 
359
- VFX is code, no MCP required:
413
+ Section 5 is fully automatable — `summer_write_file` writes the shader and the
414
+ controller, `summer_create_scene` + `summer_add_node` + `summer_set_prop` +
415
+ `summer_save_scene` build and save `smoke.tscn`. Do not hand these steps to the user
416
+ when the MCP tools are available.
417
+
418
+ Without the MCP connection there is no engine to drive, so the user does it
419
+ manually in Summer Engine:
360
420
 
361
- 1. Create `addons/vfx/smoke/` with the three files.
362
- 2. In Godot, add a `GPUParticles3D`, attach `smoke_controller.gd`.
421
+ 1. Create `addons/vfx/_building-blocks/` and `addons/vfx/smoke/` with the files above.
422
+ 2. Add a `GPUParticles3D`, attach `smoke_controller.gd`.
363
423
  3. Set `process_material` to a new `ParticleProcessMaterial` (script configures it).
364
- 4. Set `draw_pass_1` to a new `QuadMesh` with `surface_material_override[0]` = `ShaderMaterial(smoke.gdshader)`.
424
+ 4. Set `draw_pass_1` to a new `QuadMesh` with its `material` = `ShaderMaterial(smoke.gdshader)`.
365
425
  5. Save as `smoke.tscn`; instantiate at every smoke source.
426
+ 6. Check the Errors dock — a missing `.gdshaderinc` fails the whole shader.
366
427
 
367
428
  ## Handoff
368
429
 
@@ -5,13 +5,13 @@ 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_create_scene summer_add_node summer_set_prop summer_set_resource_property summer_inspect_node summer_inspect_resource summer_save_scene summer_get_script_errors summer_get_debugger_errors summer_play summer_stop
9
9
  paths: ["**/*.tscn", "**/*.gd", "**/*.gdshader", "addons/vfx/**"]
10
10
  ---
11
11
 
12
12
  # water-ripple — Impact Ripples on a Water Plane
13
13
 
14
- Concentric expanding rings on a water surface, normal-distorted so the ripple actually refracts the reflection. The shader keeps a small ring buffer of (origin, time, amplitude) tuples and renders all of them simultaneously over a base water plane. GDScript exposes `add_ripple(world_pos, amplitude)` so any system (rain, footsteps, projectile impacts, fish) can spawn one with a single call. Tested in BotW-style stylized water and in flat-puddle FPS scenes.
14
+ Concentric expanding rings on a water surface, normal-distorted so the ripple actually refracts the reflection. The shader keeps a small ring buffer of (origin, time, amplitude) tuples and renders all of them simultaneously over a base water plane. GDScript exposes `add_ripple(world_pos, amplitude)` so any system (rain, footsteps, projectile impacts, fish) can spawn one with a single call.
15
15
 
16
16
  ## When to use
17
17
 
@@ -60,6 +60,12 @@ uniform sampler2D normal_map : hint_normal;
60
60
  // Per-ripple data: (origin.xz, start_time, amplitude * (1 if active else 0))
61
61
  uniform vec4 ripples[MAX_RIPPLES];
62
62
 
63
+ // Ripple ages run off the controller's own clock, pushed here every frame. They
64
+ // cannot run off TIME: shader TIME wraps at
65
+ // rendering/limits/time/time_rollover_secs (3600 by default, verified), which
66
+ // would send every live ripple to a negative age once an hour of uptime.
67
+ uniform float now = 0.0;
68
+
63
69
  uniform float ripple_speed : hint_range(0.5, 8.0) = 2.5; // m/s expansion
64
70
  uniform float ripple_wavelength : hint_range(0.05, 1.0) = 0.20; // distance between crests
65
71
  uniform float ripple_lifetime : hint_range(0.5, 6.0) = 2.5; // seconds visible
@@ -72,7 +78,7 @@ void vertex() {
72
78
  for (int i = 0; i < MAX_RIPPLES; i++) {
73
79
  vec4 r = ripples[i];
74
80
  if (r.w <= 0.0) continue;
75
- float age = TIME - r.z;
81
+ float age = now - r.z;
76
82
  if (age < 0.0 || age > ripple_lifetime) continue;
77
83
  float dist = length(wpos.xz - r.xy);
78
84
  float front = age * ripple_speed;
@@ -95,7 +101,7 @@ void fragment() {
95
101
  for (int i = 0; i < MAX_RIPPLES; i++) {
96
102
  vec4 r = ripples[i];
97
103
  if (r.w <= 0.0) continue;
98
- float age = TIME - r.z;
104
+ float age = now - r.z;
99
105
  if (age < 0.0 || age > ripple_lifetime) continue;
100
106
  vec2 d = wpos.xz - r.xy;
101
107
  float dist = length(d);
@@ -108,7 +114,11 @@ void fragment() {
108
114
  ripple_glow += ring * fade * r.w * 0.5;
109
115
  }
110
116
 
111
- NORMAL_MAP = normalize(base_n + vec3(ripple_n.xy, 0.0));
117
+ // NORMAL_MAP is decoded by the engine (scene_forward_clustered.glsl:1427 —
118
+ // `normal_map.xy = normal_map.xy * 2.0 - 1.0`), so hand it the RAW [0,1] texel
119
+ // encoding. Assigning an already-decoded +/-1 vector makes a flat surface read
120
+ // as permanent maximum tilt.
121
+ NORMAL_MAP = normalize(base_n + vec3(ripple_n.xy, 0.0)) * 0.5 + 0.5;
112
122
  NORMAL_MAP_DEPTH = 0.6;
113
123
 
114
124
  // Fresnel-tinted color.
@@ -142,6 +152,7 @@ const MAX_RIPPLES := 16
142
152
  var _ripples: Array[Vector4] = []
143
153
  var _next_slot: int = 0
144
154
  var _mat: ShaderMaterial
155
+ var _time: float = 0.0
145
156
 
146
157
  func _ready() -> void:
147
158
  _ripples.resize(MAX_RIPPLES)
@@ -168,21 +179,33 @@ func _apply() -> void:
168
179
  ## Spawn a ripple at a world position.
169
180
  ## amplitude: 0..1 (raindrop ~0.2, footstep ~0.5, body splash ~1.0)
170
181
  func add_ripple(world_pos: Vector3, amplitude: float = 0.5) -> void:
171
- var local := global_transform.affine_inverse() * world_pos
172
- var t := float(Time.get_ticks_msec()) / 1000.0 - _shader_time_origin()
173
- _ripples[_next_slot] = Vector4(world_pos.x, world_pos.z, t, clamp(amplitude, 0.0, 1.0))
182
+ # Stamp with our own clock, not Time.get_ticks_msec(): the shader compares
183
+ # against the same `now` uniform, so the two only line up if one source drives both.
184
+ _ripples[_next_slot] = Vector4(world_pos.x, world_pos.z, _time, clamp(amplitude, 0.0, 1.0))
174
185
  _next_slot = (_next_slot + 1) % MAX_RIPPLES
175
186
  _push()
176
187
 
188
+ ## True when `world_pos` is over this surface (XZ against the mesh's local AABB).
189
+ ## Use this rather than a containment method on the caller — there isn't one.
190
+ func contains_point(world_pos: Vector3) -> bool:
191
+ if mesh == null:
192
+ return false
193
+ var local := to_local(world_pos)
194
+ var box := mesh.get_aabb()
195
+ return local.x >= box.position.x and local.x <= box.end.x \
196
+ and local.z >= box.position.z and local.z <= box.end.z
197
+
177
198
  func _push() -> void:
178
199
  if _mat == null: return
179
200
  _mat.set_shader_parameter("ripples", _ripples)
180
201
 
181
- func _shader_time_origin() -> float:
182
- # Shader's TIME starts at scene load; align our timestamps to it.
183
- return 0.0
184
-
185
- func _process(_delta: float) -> void:
202
+ func _process(delta: float) -> void:
203
+ # Drive ripple age from this clock and push it every frame. Using the shader's
204
+ # TIME instead would silently kill every ripple after
205
+ # rendering/limits/time/time_rollover_secs (3600 s default) of uptime.
206
+ _time += delta
207
+ if _mat:
208
+ _mat.set_shader_parameter("now", _time)
186
209
  # Re-push so editor updates pick up changes; cheap.
187
210
  if Engine.is_editor_hint():
188
211
  _push()
@@ -198,20 +221,48 @@ MeshInstance3D ("Water") [script: water_ripple_surface.gd]
198
221
 
199
222
  ### 5. Wire it in (MCP calls)
200
223
 
224
+ Every scene-mutating call takes an explicit `scenePath`. `summer_set_prop` uses `key`
225
+ (not `property`); `summer_set_resource_property` always needs a `subProperty` and is
226
+ for reaching *into* a resource, not for assigning one.
227
+
228
+ ```
229
+ summer_write_file(path="res://addons/vfx/water-ripple/water_ripple.gdshader", content="<section 2>", create_only=true)
230
+ summer_write_file(path="res://addons/vfx/water-ripple/water_ripple_surface.gd", content="<section 3>", create_only=true)
231
+
232
+ summer_add_node(scenePath="res://main.tscn", parent="./World", type="MeshInstance3D", name="Puddle")
233
+ summer_set_prop(scenePath="res://main.tscn", path="./World/Puddle", key="script", value="res://addons/vfx/water-ripple/water_ripple_surface.gd")
234
+ summer_set_prop(scenePath="res://main.tscn", path="./World/Puddle", key="position", value="Vector3(0, 0.01, 0)")
235
+
236
+ # Whole resources are assigned by path with summer_set_prop.
237
+ summer_set_prop(scenePath="res://main.tscn", path="./World/Puddle", key="mesh", value="res://addons/vfx/water-ripple/plane.tres")
238
+ # `normal_texture` is an exported script property on the controller, not a
239
+ # MeshInstance3D property — MeshInstance3D has no `normal_texture`.
240
+ summer_set_prop(scenePath="res://main.tscn", path="./World/Puddle", key="normal_texture", value="res://addons/vfx/water-ripple/water_normals.png")
241
+
242
+ # subProperty is required — this is how you reach into the PlaneMesh itself.
243
+ summer_set_resource_property(scenePath="res://main.tscn", nodePath="./World/Puddle", resourceProperty="mesh", subProperty="subdivide_width", value=32)
244
+ summer_set_resource_property(scenePath="res://main.tscn", nodePath="./World/Puddle", resourceProperty="mesh", subProperty="subdivide_depth", value=32)
245
+
246
+ summer_save_scene(scenePath="res://main.tscn")
201
247
  ```
202
- summer_add_node(parent="./World", type="MeshInstance3D", name="Puddle")
203
- summer_set_prop(path="./World/Puddle", property="script", value="res://addons/vfx/water-ripple/water_ripple_surface.gd")
204
- summer_set_prop(path="./World/Puddle", property="position", value="Vector3(0, 0.01, 0)")
205
- summer_set_resource_property(nodePath="./World/Puddle", resourceProperty="mesh", value="res://addons/vfx/water-ripple/plane.tres")
206
- summer_set_resource_property(nodePath="./World/Puddle", resourceProperty="normal_texture", value="res://addons/vfx/water-ripple/water_normals.png")
207
- summer_save_scene
248
+
249
+ ### 5b. Verify
250
+
251
+ The shader and the controller are compiled code, and the controller `preload`s the
252
+ shader — a shader that failed to write takes the script down at parse time.
253
+
254
+ ```
255
+ summer_get_script_errors # water_ripple_surface.gd parsed?
256
+ summer_play
257
+ summer_get_debugger_errors # shader compile errors surface here at runtime
258
+ summer_stop
208
259
  ```
209
260
 
210
261
  Then trigger ripples from any system. Footstep:
211
262
 
212
263
  ```gdscript
213
264
  # In your character controller's footstep callback:
214
- if puddle and puddle.is_in_water(global_position):
265
+ if puddle and puddle.contains_point(global_position):
215
266
  puddle.add_ripple(global_position, 0.5)
216
267
  ```
217
268
 
@@ -295,13 +346,15 @@ ripple_wavelength= 0.40
295
346
  - **Spawning more than 16 ripples per surface.** The shader has a fixed array. Older ripples get overwritten — fine for rain (they were fading anyway), bad if you spawn 50 in one frame. Either raise `MAX_RIPPLES` (and the GLSL `MAX_RIPPLES`) together, or throttle spawns.
296
347
  - **Ripple position in local instead of world space.** The shader expects world XZ. Always convert in the GDScript before pushing.
297
348
  - **No normal map assigned.** The surface looks like flat-shaded plastic. Always assign a tileable water normals texture (any blue noise normals will do).
349
+ - **Writing a decoded ±1 vector into `NORMAL_MAP`.** The engine decodes it again (`normal_map.xy = normal_map.xy * 2.0 - 1.0`), so a flat surface reads as permanent maximum tilt. Re-encode with `* 0.5 + 0.5` before assigning, as section 2 does.
350
+ - **Timing ripples against shader `TIME`.** `TIME` wraps at `rendering/limits/time/time_rollover_secs` (3600 s default) and is not `Time.get_ticks_msec() / 1000`. Push your own accumulated clock as a uniform and compare against that; otherwise every ripple silently vanishes after an hour of uptime.
298
351
  - **PlaneMesh too low-res.** The vertex displacement needs ~2–4 verts per meter or the silhouette wobble looks blocky. Use `subdivide_width = 32, subdivide_depth = 32` minimum on a 10×10 plane.
299
352
  - **`depth_draw_disabled` on the water material.** You'll see the bottom of the puddle through the water at every angle. Use `depth_draw_opaque`.
300
353
  - **Triggering ripples in `_process` without spacing.** Spamming 60/sec floods the buffer. Throttle to ~5 ripples/sec for rain unless tuned.
301
354
 
302
355
  ## Performance notes
303
356
 
304
- - Cost is O(MAX_RIPPLES × pixels_drawn × 2). With 16 ripples on a 5×5 m puddle visible at 1080p: ~0.15 ms. Cheap.
357
+ - Cost is O(MAX_RIPPLES × pixels_drawn) in the fragment loop plus O(MAX_RIPPLES × verts) in the vertex loop. 16 ripples on a 5×5 m puddle at 1080p is cheap — an order-of-magnitude expectation, not a measurement. Profile if you need a number.
305
358
  - The vertex displacement loop runs per vertex; raising plane subdivision past 64 starts to hurt. 32×32 is plenty.
306
359
  - For lakes (large planes), don't apply this shader to the entire lake — split the lake into chunks and only apply ripples to the chunk near impacts. Or use a `Decal` projection instead (see edge cases).
307
360
  - Mobile: drop `MAX_RIPPLES` to 8, disable vertex displacement (set `ripple_height = 0` and skip the vertex loop), and rely on normal-only ripples.
@@ -316,12 +369,19 @@ ripple_wavelength= 0.40
316
369
 
317
370
  ## Fallback (no MCP)
318
371
 
319
- VFX is code, no MCP required:
372
+ Section 5 is fully automatable — `summer_write_file` writes the shader and the
373
+ controller, and `summer_add_node` + `summer_set_prop` +
374
+ `summer_set_resource_property` + `summer_save_scene` build the plane including its
375
+ subdivision. Do not hand these steps to the user when the MCP tools are available.
376
+
377
+ Without the MCP connection there is no engine to drive, so the user does it
378
+ manually in Summer Engine:
320
379
 
321
380
  1. Create `addons/vfx/water-ripple/` and write the three files above.
322
- 2. In Godot, add a `MeshInstance3D` with a high-subdivision `PlaneMesh`.
381
+ 2. Add a `MeshInstance3D` with a high-subdivision `PlaneMesh` (32×32 minimum).
323
382
  3. Attach `water_ripple_surface.gd` and assign a tileable water normals texture.
324
383
  4. From your trigger code, call `$Puddle.add_ripple(impact_pos, amplitude)`.
384
+ 5. Check the Errors dock — the controller `preload`s the shader, so a broken shader path fails the script too.
325
385
 
326
386
  ## Handoff
327
387
 
@@ -161,4 +161,4 @@ A browser-based companion for showing mockups, diagrams, and visual options duri
161
161
  A question about a UI topic is not automatically a visual question. "What does personality mean in this context?" is a conceptual question — use the terminal. "Which wizard layout works better?" is a visual question — use the browser.
162
162
 
163
163
  If they agree to the companion, read the detailed guide before proceeding:
164
- `skills/brainstorming/visual-companion.md`
164
+ `skills/workflow/brainstorming/visual-companion.md`
@@ -11,7 +11,7 @@ description: Use when a gameplay feature works correctly but feels wrong — flo
11
11
 
12
12
  **Core principle:** Game-feel bugs are tuning bugs, not logic bugs. Treat them with discipline — isolate one variable, compare to a reference, measure what you can, feel what you can't.
13
13
 
14
- This skill is for the diagnostic phase. The polish-and-juice layer (screen shake, hit-stop, particles, sound design) is `summer:visual-effects/game-feel` — go there once you've identified WHICH variable is wrong.
14
+ This skill is for the diagnostic phase. The polish-and-juice layer (screen shake, hit-stop, particles, sound design) is `summer:game-feel` — go there once you've identified WHICH variable is wrong.
15
15
 
16
16
  ## When To Use
17
17
 
@@ -80,16 +80,36 @@ Two compare modes:
80
80
 
81
81
  **Feel:** subjective. The user (or you, if you have eyes on the screen) reports "warmer" or "colder." This is fine. Most game-feel work is felt, not measured.
82
82
 
83
- **Measure:** objective. When the feel is ambiguous, drop down to frame counts.
83
+ **Measure:** objective. When the feel is ambiguous, drop down to frame counts — and take them yourself with a `RunVerification` probe rather than asking the user to eyeball anything. The probe presses the input, samples per frame, and hands back an array.
84
84
 
85
85
  | Question | Measurement |
86
86
  |---|---|
87
- | "Does the jump peak too fast?" | `print` the y-velocity in `_physics_process`, count frames from launch to peak |
88
- | "Is the camera too laggy?" | `print` `camera.global_position - target.global_position` per frame |
89
- | "Is the input dead?" | Log input event frame and resulting state-machine transition frame; subtract |
90
- | "Is the hit-stop too short?" | Log frame at hit + frame at unfreeze |
87
+ | "Does the jump peak too fast?" | Probe: `press("jump")`, sample `player.velocity.y` every `process_frame`, count frames to the sign flip |
88
+ | "Is the camera too laggy?" | Probe: sample `camera.global_position - target.global_position` per frame while driving movement |
89
+ | "Is the input dead?" | Probe: report the frame input was injected and the frame the state machine changed; subtract |
90
+ | "Is the hit-stop too short?" | Probe: report frame at hit and frame at unfreeze |
91
+
92
+ Sketch:
93
+
94
+ ```gdscript
95
+ extends SummerProbeBase
96
+ func _ready() -> void:
97
+ await super._ready()
98
+ await get_tree().process_frame
99
+ var p := get_tree().root.find_child("Player", true, false)
100
+ var trace: Array = []
101
+ await press("jump", 100)
102
+ for i in 40:
103
+ await get_tree().process_frame
104
+ trace.append(p.velocity.y)
105
+ report("vel_y_per_frame", trace)
106
+ save_frame("apex")
107
+ finish()
108
+ ```
109
+
110
+ Send it with `summer_batch ops:[{"op":"RunVerification","probe_source":"<the probe>","max_seconds":20}]`.
91
111
 
92
- The point of measurement is to ground the conversation in numbers when "feels off" stops being useful. You do not need to measure most tweaks — only the ambiguous ones.
112
+ The point of measurement is to ground the conversation in numbers when "feels off" stops being useful. You do not need to measure most tweaks — only the ambiguous ones. **The verdict on whether it now feels right stays with the user** — that part is genuinely subjective, and it is the only part of this loop you hand over.
93
113
 
94
114
  ### 5. Decide
95
115
 
@@ -144,7 +164,7 @@ These are diagnostic tools, not lecture material. When the user says "feels weig
144
164
  | "The user said floaty so I'm jacking gravity" | "Floaty" can also mean weak air control, slow terminal velocity, or too-long apex hang. Confirm before tweaking. |
145
165
  | Skipping the play step ("I'll just bump the number and trust the feel") | Game feel is felt, not predicted. Play every tweak. |
146
166
  | Adding "while I'm here" tweaks to unrelated features | Game feel debugging is a focused activity. Stay on the one feature. |
147
- | Going to `summer:visual-effects/game-feel` before identifying the broken variable | Juice on top of a broken tuning makes the bug louder. Fix tuning first. |
167
+ | Going to `summer:game-feel` before identifying the broken variable | Juice on top of a broken tuning makes the bug louder. Fix tuning first. |
148
168
  | Spending >30 minutes on one variable without progress | The anchor is wrong, or the variable is wrong. Step back, re-anchor. |
149
169
 
150
170
  ## Rationalization Prevention
@@ -161,7 +181,7 @@ These are diagnostic tools, not lecture material. When the user says "feels weig
161
181
 
162
182
  After diagnosis pins down which variable(s) are wrong, the tuning is done — but the feature may still want polish. Hand off to:
163
183
 
164
- - `summer:visual-effects/game-feel` — for the screen-shake, hit-stop, particle, sound layer once tuning is locked.
184
+ - `summer:game-feel` — for the screen-shake, hit-stop, particle, sound layer once tuning is locked.
165
185
  - `summer:debug` — if mid-debug you discover an actual error (signal not firing, node freed) hiding under the feel issue.
166
186
 
167
187
  ## The Bottom Line
@@ -42,21 +42,40 @@ You cannot fix a regression you have not measured. Eyeballing "feels laggy" is n
42
42
 
43
43
  ### 1. Measure now
44
44
 
45
+ Measure in a `RunVerification` probe, not by eye. It spawns a hidden, disposable game instance with a real renderer, so `Performance.get_monitor(...)` returns the game's actual numbers and the user's editor session is untouched.
46
+
47
+ ```
48
+ summer_batch ops:[{"op":"RunVerification","probe_source":"<probe>","max_seconds":20}]
45
49
  ```
46
- summer_play (on the scene that's slow)
47
- ── wait 5-10 seconds of normal play ──
48
- summer_get_diagnostics
49
- summer_get_console
50
- summer_stop
50
+
51
+ ```gdscript
52
+ extends SummerProbeBase
53
+ func _ready() -> void:
54
+ await super._ready()
55
+ await get_tree().process_frame
56
+ var samples: Array = []
57
+ for i in 30: # ~5 s at 60 fps, sampled over time
58
+ await get_tree().create_timer(0.16).timeout
59
+ samples.append(Performance.get_monitor(Performance.TIME_FPS))
60
+ report("fps_samples", samples) # spike pattern lives here
61
+ report("draw_calls", Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME))
62
+ report("bodies_3d", Performance.get_monitor(Performance.PHYSICS_3D_ACTIVE_OBJECTS))
63
+ report("objects", Performance.get_monitor(Performance.RENDER_TOTAL_OBJECTS_IN_FRAME))
64
+ dump_tree()
65
+ finish()
51
66
  ```
52
67
 
53
68
  What to capture:
54
69
 
55
70
  | Metric | Source | Why |
56
71
  |---|---|---|
57
- | Avg FPS / frame time | `summer_get_diagnostics` or in-game perf monitor | Headline number |
58
- | Spike pattern | `summer_get_console` if user has frame-time prints, else in-game monitor | Steady 30 vs intermittent stutters → different causes |
59
- | Scene complexity | `summer_get_scene_tree` (root counts) | Establishes baseline for instance/body count cliffs |
72
+ | Avg FPS / frame time | `Performance.TIME_FPS` / `TIME_PROCESS` sampled in the probe | Headline number |
73
+ | Spike pattern | the per-sample FPS array above | Steady 30 vs intermittent stutters → different causes |
74
+ | Scene complexity | probe `dump_tree()` for the live tree; `summer_get_scene_tree` for the edited scene | Establishes baseline for instance/body count cliffs |
75
+
76
+ `summer_get_diagnostics` does **not** report performance. It returns counts of console errors, debugger errors, debugger warnings and script errors. Use it to confirm the slow run isn't also throwing, never as a source of FPS or draw-call numbers.
77
+
78
+ Discard every sample from the first second — `TIME_FPS` is a per-second average and reads exactly `1.0` until it has a second of history. A leading run of `1.0` in the array is warm-up, not a stall.
60
79
 
61
80
  Write the bad-state number down. "Scene `Level3` runs at 31fps avg, drops to 12fps when 5+ enemies are on screen."
62
81
 
@@ -96,7 +115,7 @@ Read the list. Group commits into suspects:
96
115
 
97
116
  If a single commit is the obvious suspect (e.g. "Added 200 enemies to spawner"), test it first. Otherwise actually `git bisect` — check out the midpoint, measure FPS, repeat. Don't theorize through it; the bisect is fast and definitive.
98
117
 
99
- ### 4. Match to one of the four Godot perf cliffs
118
+ ### 4. Match to one of the four Summer Engine performance cliffs
100
119
 
101
120
  Godot 4.x regressions cluster heavily into four categories. Knowing them shortens diagnosis to minutes.
102
121
 
@@ -127,13 +146,7 @@ If no → that wasn't it. Restore. Try the next suspect.
127
146
 
128
147
  The fix is not done until the same measurement that found the regression returns the good number. Same scene, same play duration, same warm-up.
129
148
 
130
- ```
131
- summer_clear_console
132
- summer_play
133
- ── 5-10 seconds of play ──
134
- summer_get_diagnostics
135
- summer_stop
136
- ```
149
+ Re-run the identical probe from step 1 — same scene, same sample count, same drive sequence. A number taken a different way is not a comparison.
137
150
 
138
151
  State the result with the number: "Reverted the per-frame `get_tree().get_nodes_in_group()` call in `enemy_manager.gd`. Level3 back to 58fps avg." Not "should be faster now."
139
152
 
@@ -156,7 +169,9 @@ State the result with the number: "Reverted the per-frame `get_tree().get_nodes_
156
169
  | Skipping the known-good measurement | Without a baseline, you don't know when you're done. |
157
170
  | Reading the slow scene's code top-to-bottom looking for inefficiency | The regression is in what changed, not in what was already there. |
158
171
  | Adding caching / pooling / LOD before identifying the cause | These are tuning moves. They can hide the regression instead of fixing it. |
159
- | Trusting editor FPS as ground truth | Editor runs `@tool` scripts, debug overlays, and a different render pipeline. Measure in a built or `--game` run when possible. |
172
+ | Trusting editor FPS as ground truth | Editor runs `@tool` scripts, debug overlays, and a different render pipeline. Measure in a `RunVerification` probe or a built binary. |
173
+ | Profiling with `--headless` | No renderer: draw calls read `0.0` and the FPS figure is a bare loop rate. The verify instance is windowed-offscreen for exactly this reason. |
174
+ | Quoting FPS from `summer_get_diagnostics` | It returns error/warning counts only. That number did not come from where you said it did. |
160
175
  | "Maybe it's just a Tuesday thing" | Performance does not regress randomly. There is a cause. |
161
176
 
162
177
  ## Rationalization Prevention
@@ -171,11 +186,21 @@ State the result with the number: "Reverted the per-frame `get_tree().get_nodes_
171
186
 
172
187
  ## When The Engine Isn't Running
173
188
 
174
- If `summer_play` is unavailable:
189
+ If the MCP tools report the engine isn't running:
190
+
191
+ 1. **Get it running.** `summer run` starts it and restores the probe route. That is one command, and it is almost always the right answer — do not degrade to interviewing the user because a tool returned "not running" once.
192
+ 2. **If you have shell access, you can run a probe without the editor at all.** The same verify instance is a plain flag on the Summer binary:
193
+
194
+ ```bash
195
+ "<summer-binary>" --path <project-dir> \
196
+ --summer-verify <abs/path/probe.gd> \
197
+ --summer-verify-out <abs/out-dir> \
198
+ --summer-verify-max 20
199
+ ```
175
200
 
176
- 1. Ask the user for an in-game perf overlay screenshot (FPS, frame time, draw call count).
177
- 2. Walk the bisect logically with them: "Run on commit X, tell me the FPS. Run on commit Y, tell me the FPS."
178
- 3. Do not claim a fix is verified from code reading alone.
201
+ Then read `<out-dir>/results.json` (`reports`, `frames`, `errors_seen`, `duration_ms`). Locate the binary rather than guessing a path — on macOS it is the executable inside the installed `Summer.app` bundle. The probe file must extend the probe base; when you invoke the binary directly, write the base alongside it and `extends` it by path.
202
+ 3. Only if neither is possible: ask the user for an in-game perf overlay reading (FPS, frame time, draw call count) and walk the bisect with them — "run on commit X, tell me the FPS; run on commit Y, tell me the FPS."
203
+ 4. Do not claim a fix is verified from code reading alone.
179
204
 
180
205
  ## The Bottom Line
181
206
 
@@ -17,9 +17,18 @@ This is the meta-skill: the loop that turns ad-hoc fixes into reusable expertise
17
17
 
18
18
  Three repos, one ecosystem:
19
19
 
20
- 1. **SummerEngine repo** — the desktop engine (Godot fork in C++) plus the canonical CLI. Engine source: `editor/`, `core/`, and the Summer-specific module at `modules/1summer_engine/`. CLI source: `tools/summer-cli/`. The CLI ships as `npm i -g summer-engine`. It does install/login/create/run for the engine, plus `summer skills install --agent claude-code` which writes a curated subset of skills into `~/.claude/skills/` for any agent to auto-discover.
20
+ 1. **SummerEngine repo** — the Summer Engine desktop source plus the canonical
21
+ CLI. The C++ engine is maintained against the upstream Godot Engine codebase;
22
+ the Summer-owned module lives at `modules/1summer_engine/`. CLI source lives
23
+ at `tools/summer-cli/` and is normally invoked with
24
+ `npx -y summer-engine@latest`. It installs, signs in, creates, and runs Summer
25
+ projects and can install the current Summer skill bundle for supported
26
+ agents.
21
27
  2. **PublicSummerEngine repo** — the web app at summerengine.com. Has its own AI skill stores at `src/lib/ai/skills/bundled/` and `public/knowledge/summer/skills/` for the in-browser chat agent. Also has its own consumers of the engine API in `src/lib/bridge/direct-executor.ts` and `src/lib/ai/tools/run-and-verify.ts` which call the same engine endpoints the CLI does.
22
- 3. **The active game project** — usually `~/development/<game-name>` (Godot 4.6) or whatever game you were just working on. Real working code in this repo is the gold standard for examples.
28
+ 3. **The active Summer game project** — usually
29
+ `~/development/<game-name>` or whatever game you were just working on. The
30
+ live compatibility line comes from Summer Engine, not this prompt. Real
31
+ working code in this repo is the gold standard for examples.
23
32
 
24
33
  The skill system is the value flywheel. Every game shipped teaches lessons. Lessons become skills. Next user starts smarter. Your job here is to close that loop for what you just did.
25
34