summer-engine 2.0.0 → 2.3.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 (62) hide show
  1. package/{marketplace.json → .claude-plugin/marketplace.json} +3 -3
  2. package/.claude-plugin/plugin.json +37 -4
  3. package/.codex-plugin/plugin.json +38 -5
  4. package/.cursor-plugin/plugin.json +37 -4
  5. package/.factory-plugin/plugin.json +20 -0
  6. package/.opencode/INSTALL.md +9 -3
  7. package/.opencode/plugins/summer.js +0 -2
  8. package/AGENTS.md +51 -0
  9. package/CLAUDE.md +5 -0
  10. package/GEMINI.md +56 -0
  11. package/README.md +211 -61
  12. package/_persona/summer/SKILL.md +113 -0
  13. package/dist/commands/install.js +2 -2
  14. package/dist/commands/setup.js +4 -0
  15. package/dist/commands/skills.js +115 -2
  16. package/dist/lib/agent-config.d.ts +2 -2
  17. package/dist/lib/agent-config.js +171 -5
  18. package/dist/lib/skills-registry.d.ts +290 -26
  19. package/dist/lib/skills-registry.js +503 -1
  20. package/dist/mcp/tools/generate-tools.js +101 -0
  21. package/gemini-extension.json +2 -2
  22. package/package.json +9 -3
  23. package/skills/2d-assets/character-portrait/SKILL.md +196 -0
  24. package/skills/2d-assets/concept-art/SKILL.md +152 -0
  25. package/skills/2d-assets/pixel-art/SKILL.md +180 -0
  26. package/skills/2d-assets/skybox-panorama/SKILL.md +207 -0
  27. package/skills/2d-assets/sprite-sheet/SKILL.md +239 -0
  28. package/skills/2d-assets/tileable-texture/SKILL.md +205 -0
  29. package/skills/2d-assets/ui-graphics/SKILL.md +210 -0
  30. package/skills/3d-assets/character-model/SKILL.md +225 -0
  31. package/skills/3d-assets/environment-kit/SKILL.md +164 -0
  32. package/skills/3d-assets/organic-model/SKILL.md +192 -0
  33. package/skills/3d-assets/prop-model/SKILL.md +133 -0
  34. package/skills/3d-assets/vehicle-model/SKILL.md +202 -0
  35. package/skills/ai-and-npcs/design-npc/SKILL.md +3 -1
  36. package/skills/animation/animation-tree/SKILL.md +260 -0
  37. package/skills/animation/facial-and-lipsync/SKILL.md +282 -0
  38. package/skills/animation/generate-motion/SKILL.md +129 -0
  39. package/skills/animation/procedural-animation/SKILL.md +234 -0
  40. package/skills/animation/retarget/SKILL.md +172 -0
  41. package/skills/asset-pipeline/asset-strategy/SKILL.md +102 -212
  42. package/skills/audio/adaptive-music/SKILL.md +326 -0
  43. package/skills/audio/ambient-bed/SKILL.md +278 -0
  44. package/skills/audio/music-track/SKILL.md +268 -0
  45. package/skills/audio/sound-effect/SKILL.md +237 -0
  46. package/skills/audio/voice-line/SKILL.md +253 -0
  47. package/skills/catalog.yaml +249 -3
  48. package/skills/video/animated-loop/SKILL.md +200 -0
  49. package/skills/video/cinematic-cutscene/SKILL.md +204 -0
  50. package/skills/video/trailer-shot/SKILL.md +203 -0
  51. package/skills/visual-effects/_building-blocks/additive-billboard-particles.md +28 -0
  52. package/skills/visual-effects/_building-blocks/noise-3d-fbm.gdshaderinc +45 -0
  53. package/skills/visual-effects/_building-blocks/trauma-shake-snippet.md +51 -0
  54. package/skills/visual-effects/{vfx → game-feel}/SKILL.md +1 -1
  55. package/skills/visual-effects/recipes/dissolve/SKILL.md +349 -0
  56. package/skills/visual-effects/recipes/fire/SKILL.md +387 -0
  57. package/skills/visual-effects/recipes/hit-spark/SKILL.md +330 -0
  58. package/skills/visual-effects/recipes/lightning/SKILL.md +372 -0
  59. package/skills/visual-effects/recipes/magic-glow/SKILL.md +377 -0
  60. package/skills/visual-effects/recipes/muzzle-flash/SKILL.md +326 -0
  61. package/skills/visual-effects/recipes/smoke/SKILL.md +381 -0
  62. package/skills/visual-effects/recipes/water-ripple/SKILL.md +340 -0
@@ -0,0 +1,387 @@
1
+ ---
2
+ name: fire
3
+ description: Use when authoring a fire visual effect — animated flames built with a particle shader, GPUParticles3D, and a noise-based color ramp. Trigger on "torch", "campfire", "bonfire", "flame", "fire effect", "burning", "make this thing on fire", "candle".
4
+ license: MIT
5
+ compatibility: [Cursor, Claude Code, Windsurf, Codex]
6
+ category: visual-effects
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
9
+ paths: ["**/*.tscn", "**/*.gd", "**/*.gdshader", "addons/vfx/**"]
10
+ ---
11
+
12
+ # fire — Animated Particle Flames
13
+
14
+ Fire in games is almost never a video clip and never an AI-generated sprite. It's a pile of additive billboard quads emitted from a cone, each running a noise-distorted UV through a hot color ramp (red → orange → yellow → smoky alpha-out). Think Skyrim torches, Doom Eternal's pyre, BotW's bonfires — all built from this same recipe with different dials. This skill writes the shader, the controller, and the node tree, then wires them into the open scene. Authoring, not generating.
15
+
16
+ ## When to use
17
+
18
+ - "Add fire to this torch."
19
+ - "Make a campfire / bonfire / hearth flame."
20
+ - "Light the brazier."
21
+ - "I need a candle flame on this candlestick."
22
+ - "The dragon needs a flame breath" (use this for the persistent emitter; pair with a one-shot `muzzle-flash` for the burst).
23
+ - The player set something on fire and you need a continuous, physically-anchored flame.
24
+
25
+ ## When NOT to use
26
+
27
+ - The user wants an object to *burn away* (mesh disintegrating with a fire edge) — route to `dissolve`. Fire is a separate effect; pair them.
28
+ - The user wants smoke without flame — go straight to `smoke`. (Most fires want a smoke trail above; spawn this recipe and add `smoke` on top.)
29
+ - The user wants a one-frame muzzle flash from a gun — that's `muzzle-flash`, not fire.
30
+ - The user wants 2D fire on a UI element — use `canvas_item` shader; this recipe is `spatial`/`particles`.
31
+ - The user wants stylized magic flame (blue, purple, ethereal) — use this recipe and load the `magic-fire` variant in the cookbook below.
32
+
33
+ ## Recipe
34
+
35
+ ### 1. Files to create
36
+
37
+ ```
38
+ addons/vfx/fire/fire.gdshader # spatial shader for the visual quad
39
+ addons/vfx/fire/fire_process.gdshader # particle shader for the simulation
40
+ addons/vfx/fire/fire_controller.gd # parameter dials + lifetime
41
+ addons/vfx/fire/fire.tscn # reusable scene (instantiate per torch)
42
+ ```
43
+
44
+ ### 2. Visual shader code
45
+
46
+ `addons/vfx/fire/fire.gdshader`:
47
+
48
+ ```glsl
49
+ shader_type spatial;
50
+ render_mode unshaded, blend_add, depth_draw_never, cull_disabled, shadows_disabled;
51
+
52
+ #include "res://addons/vfx/_building-blocks/noise-3d-fbm.gdshaderinc"
53
+
54
+ uniform vec4 color_hot : source_color = vec4(1.0, 0.95, 0.55, 1.0);
55
+ uniform vec4 color_mid : source_color = vec4(1.0, 0.45, 0.10, 1.0);
56
+ uniform vec4 color_cool : source_color = vec4(0.85, 0.10, 0.02, 1.0);
57
+ uniform vec4 color_smoke : source_color = vec4(0.05, 0.04, 0.04, 0.0);
58
+
59
+ uniform float noise_scale : hint_range(0.5, 8.0) = 3.0;
60
+ uniform float noise_speed : hint_range(0.0, 4.0) = 1.4;
61
+ uniform float distortion : hint_range(0.0, 0.6) = 0.20;
62
+ uniform float emission_energy : hint_range(0.0, 8.0) = 3.5;
63
+ uniform float soft_edge : hint_range(0.01, 0.5) = 0.20;
64
+
65
+ void vertex() {
66
+ // Billboard the quad toward the camera (view-space).
67
+ MODELVIEW_MATRIX = VIEW_MATRIX * mat4(
68
+ INV_VIEW_MATRIX[0],
69
+ INV_VIEW_MATRIX[1],
70
+ INV_VIEW_MATRIX[2],
71
+ MODEL_MATRIX[3]
72
+ );
73
+ }
74
+
75
+ void fragment() {
76
+ // CUSTOM.x is particle age 0..1 (set by the process material).
77
+ float age = CUSTOM.x;
78
+
79
+ // Distort UVs with scrolling 3D noise so the flame writhes.
80
+ vec3 np = vec3(UV * noise_scale, TIME * noise_speed);
81
+ float n = fbm3(np);
82
+ vec2 uv = UV + vec2(n - 0.5) * distortion;
83
+
84
+ // Soft round mask, hottest at the bottom-center, cooler toward the top.
85
+ vec2 c = uv - vec2(0.5, 0.15);
86
+ float r = length(c * vec2(1.0, 0.55));
87
+ float mask = smoothstep(0.55, 0.55 - soft_edge, r);
88
+
89
+ // Color ramp by particle age (0 = hot bright, 1 = smoky alpha-out).
90
+ vec4 col;
91
+ if (age < 0.33) {
92
+ col = mix(color_hot, color_mid, age / 0.33);
93
+ } else if (age < 0.75) {
94
+ col = mix(color_mid, color_cool, (age - 0.33) / 0.42);
95
+ } else {
96
+ col = mix(color_cool, color_smoke, (age - 0.75) / 0.25);
97
+ }
98
+
99
+ // Add the noise as a brightness modulation so flames flicker.
100
+ float flicker = mix(0.6, 1.2, n);
101
+
102
+ ALBEDO = col.rgb * flicker;
103
+ EMISSION = col.rgb * flicker * emission_energy * (1.0 - age);
104
+ ALPHA = col.a * mask * (1.0 - age * age);
105
+ }
106
+ ```
107
+
108
+ ### 3. Particle process shader (optional)
109
+
110
+ For most fires the built-in `ParticleProcessMaterial` is fine. Drop in this custom shader only if you need turbulence beyond the standard params:
111
+
112
+ `addons/vfx/fire/fire_process.gdshader`:
113
+
114
+ ```glsl
115
+ shader_type particles;
116
+
117
+ uniform float rise_speed : hint_range(0.1, 6.0) = 1.8;
118
+ uniform float spread_angle : hint_range(0.0, 1.5) = 0.35;
119
+ uniform float lifetime_var : hint_range(0.0, 1.0) = 0.25;
120
+
121
+ void start() {
122
+ // Random spawn within a small disc.
123
+ float r = sqrt(rand_from_seed(RANDOM_SEED + uint(7))) * 0.18;
124
+ float th = rand_from_seed(RANDOM_SEED + uint(11)) * 6.2831853;
125
+ TRANSFORM[3].xyz = vec3(cos(th) * r, 0.0, sin(th) * r);
126
+
127
+ // Initial velocity: mostly up, slight cone.
128
+ vec3 dir = vec3(
129
+ (rand_from_seed(RANDOM_SEED + uint(13)) - 0.5) * spread_angle,
130
+ 1.0,
131
+ (rand_from_seed(RANDOM_SEED + uint(17)) - 0.5) * spread_angle
132
+ );
133
+ VELOCITY = normalize(dir) * rise_speed;
134
+
135
+ // Stagger lifetimes for variety.
136
+ LIFETIME = LIFETIME * (1.0 + (rand_from_seed(RANDOM_SEED + uint(19)) - 0.5) * 2.0 * lifetime_var);
137
+
138
+ CUSTOM.x = 0.0; // age
139
+ CUSTOM.y = rand_from_seed(RANDOM_SEED + uint(23)); // per-particle seed
140
+ }
141
+
142
+ void process() {
143
+ // Rise + small horizontal sway.
144
+ VELOCITY.y += DELTA * 0.6;
145
+ VELOCITY.x += sin(TIME * 2.0 + CUSTOM.y * 6.28) * DELTA * 0.4;
146
+ VELOCITY.z += cos(TIME * 2.3 + CUSTOM.y * 6.28) * DELTA * 0.4;
147
+
148
+ // Shrink as it ages.
149
+ float age = clamp(1.0 - LIFETIME / TOTAL_LIFETIME, 0.0, 1.0);
150
+ CUSTOM.x = age;
151
+ float scale = mix(1.0, 0.35, age);
152
+ TRANSFORM[0].xyz = vec3(scale, 0.0, 0.0);
153
+ TRANSFORM[1].xyz = vec3(0.0, scale, 0.0);
154
+ TRANSFORM[2].xyz = vec3(0.0, 0.0, scale);
155
+ }
156
+ ```
157
+
158
+ ### 4. GDScript controller
159
+
160
+ `addons/vfx/fire/fire_controller.gd`:
161
+
162
+ ```gdscript
163
+ @tool
164
+ class_name FireController
165
+ extends GPUParticles3D
166
+
167
+ @export_group("Flame size")
168
+ @export_range(0.05, 5.0) var flame_height: float = 1.2 :
169
+ set(v): flame_height = v; _apply()
170
+ @export_range(0.05, 3.0) var flame_radius: float = 0.35 :
171
+ set(v): flame_radius = v; _apply()
172
+
173
+ @export_group("Flame intensity")
174
+ @export_range(8, 1024) var particle_count: int = 96 :
175
+ set(v): particle_count = v; _apply()
176
+ @export_range(0.2, 4.0) var flame_lifetime: float = 1.4 :
177
+ set(v): flame_lifetime = v; _apply()
178
+ @export_range(0.0, 8.0) var emission_energy: float = 3.5 :
179
+ set(v): emission_energy = v; _apply()
180
+
181
+ @export_group("Color")
182
+ @export var color_hot: Color = Color(1.0, 0.95, 0.55)
183
+ @export var color_mid: Color = Color(1.0, 0.45, 0.10)
184
+ @export var color_cool: Color = Color(0.85, 0.10, 0.02)
185
+
186
+ func _ready() -> void:
187
+ one_shot = false
188
+ explosiveness = 0.0
189
+ _apply()
190
+
191
+ func _apply() -> void:
192
+ amount = particle_count
193
+ lifetime = flame_lifetime
194
+
195
+ var pm := process_material as ParticleProcessMaterial
196
+ if pm:
197
+ pm.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_SPHERE
198
+ pm.emission_sphere_radius = flame_radius
199
+ pm.direction = Vector3.UP
200
+ pm.spread = 12.0
201
+ pm.initial_velocity_min = flame_height * 0.9
202
+ pm.initial_velocity_max = flame_height * 1.4
203
+ pm.gravity = Vector3.ZERO
204
+ pm.scale_min = flame_radius * 1.1
205
+ pm.scale_max = flame_radius * 1.6
206
+
207
+ if draw_pass_1:
208
+ var mesh := draw_pass_1 as QuadMesh
209
+ var mat := mesh.surface_get_material(0) as ShaderMaterial
210
+ if mat:
211
+ mat.set_shader_parameter("color_hot", color_hot)
212
+ mat.set_shader_parameter("color_mid", color_mid)
213
+ mat.set_shader_parameter("color_cool", color_cool)
214
+ mat.set_shader_parameter("emission_energy", emission_energy)
215
+
216
+ func extinguish(fade_seconds: float = 0.5) -> void:
217
+ var t := create_tween()
218
+ t.tween_property(self, "amount_ratio", 0.0, fade_seconds)
219
+ t.tween_callback(queue_free)
220
+ ```
221
+
222
+ ### 5. Node tree
223
+
224
+ ```
225
+ GPUParticles3D ("Fire") [script: fire_controller.gd]
226
+ ├── process_material: ParticleProcessMaterial (or ShaderMaterial w/ fire_process.gdshader)
227
+ └── draw_pass_1: QuadMesh (size 1.0 × 1.0)
228
+ └── surface_material_override[0]: ShaderMaterial (shader: fire.gdshader)
229
+ ```
230
+
231
+ Add an OmniLight3D as a sibling (parented to the same anchor as the GPUParticles3D, NOT under it) for proper light cast. Recommended: `light_color = Color(1.0, 0.6, 0.25)`, `light_energy = 1.6`, `omni_range = 6.0`. Pulse it lightly via the controller for free atmosphere.
232
+
233
+ ### 6. Wire it in (MCP calls)
234
+
235
+ ```
236
+ summer_add_node(parent="./World/Torch", type="GPUParticles3D", name="Fire")
237
+ summer_set_prop(path="./World/Torch/Fire", property="script", value="res://addons/vfx/fire/fire_controller.gd")
238
+
239
+ summer_add_node(parent="./World/Torch/Fire", type="ParticleProcessMaterial", name="ProcessMat")
240
+ summer_set_resource_property(nodePath="./World/Torch/Fire", resourceProperty="process_material", value="res://addons/vfx/fire/process_mat.tres")
241
+
242
+ summer_set_resource_property(nodePath="./World/Torch/Fire", resourceProperty="draw_pass_1", value="res://addons/vfx/fire/quad.tres")
243
+ # quad.tres is a QuadMesh with size (1,1) and surface_material_override[0] = fire_material.tres (ShaderMaterial wrapping fire.gdshader)
244
+
245
+ summer_set_prop(path="./World/Torch/Fire", property="flame_height", value=1.2)
246
+ summer_set_prop(path="./World/Torch/Fire", property="flame_radius", value=0.35)
247
+ summer_set_prop(path="./World/Torch/Fire", property="particle_count", value=96)
248
+
249
+ summer_add_node(parent="./World/Torch", type="OmniLight3D", name="FireLight")
250
+ summer_set_prop(path="./World/Torch/FireLight", property="light_color", value="Color(1.0, 0.6, 0.25)")
251
+ summer_set_prop(path="./World/Torch/FireLight", property="light_energy", value=1.6)
252
+ summer_set_prop(path="./World/Torch/FireLight", property="omni_range", value=6.0)
253
+
254
+ summer_save_scene
255
+ ```
256
+
257
+ ### 7. Parameters to tune
258
+
259
+ | Parameter | Range | Effect |
260
+ |---|---|---|
261
+ | `particle_count` (`amount`) | 32–512 | density of the flame, perf cost is linear |
262
+ | `flame_lifetime` | 0.5–3.0 s | how long each particle lives before fade |
263
+ | `flame_height` | 0.2–4.0 | initial upward velocity → height of the column |
264
+ | `flame_radius` | 0.05–2.0 | spawn disc + per-particle scale |
265
+ | `emission_energy` | 0.0–8.0 | how much it blooms in the post-process |
266
+ | `noise_scale` | 0.5–8.0 | small = wide flowing tongues; large = fine flicker |
267
+ | `noise_speed` | 0.0–4.0 | how fast the flame moves |
268
+ | `distortion` | 0.0–0.6 | how warped the silhouette gets |
269
+ | `color_hot/mid/cool` | Color | the ramp; shift toward blue/green/purple for magic |
270
+
271
+ ## Cookbook — named variants
272
+
273
+ ### torch-flame (default)
274
+
275
+ Wall-mounted torch, ~1 m flame.
276
+
277
+ ```
278
+ particle_count = 64
279
+ flame_lifetime = 1.2
280
+ flame_height = 1.2
281
+ flame_radius = 0.20
282
+ emission_energy = 3.5
283
+ color_hot = Color(1.0, 0.95, 0.55)
284
+ color_mid = Color(1.0, 0.45, 0.10)
285
+ color_cool = Color(0.85, 0.10, 0.02)
286
+ OmniLight3D: light_energy = 1.6, omni_range = 6.0
287
+ ```
288
+
289
+ ### bonfire
290
+
291
+ Wide, tall, hungry. Pair with `smoke` directly above.
292
+
293
+ ```
294
+ particle_count = 256
295
+ flame_lifetime = 1.8
296
+ flame_height = 2.4
297
+ flame_radius = 0.80
298
+ emission_energy = 4.5
299
+ OmniLight3D: light_energy = 3.5, omni_range = 12.0, pulse 1.0 Hz
300
+ ```
301
+
302
+ ### candle-flame
303
+
304
+ Tiny, calm, mostly motionless. Lifetime small so it shimmers in place.
305
+
306
+ ```
307
+ particle_count = 24
308
+ flame_lifetime = 0.6
309
+ flame_height = 0.18
310
+ flame_radius = 0.04
311
+ emission_energy = 2.0
312
+ noise_speed = 0.7
313
+ distortion = 0.10
314
+ OmniLight3D: light_energy = 0.6, omni_range = 2.5
315
+ ```
316
+
317
+ ### magic-fire (blue / arcane)
318
+
319
+ Cool flame. Same dynamics, recolor the ramp.
320
+
321
+ ```
322
+ particle_count = 96
323
+ flame_lifetime = 1.6
324
+ flame_height = 1.4
325
+ flame_radius = 0.30
326
+ emission_energy = 5.0
327
+ color_hot = Color(0.85, 0.95, 1.00)
328
+ color_mid = Color(0.20, 0.55, 1.00)
329
+ color_cool = Color(0.05, 0.10, 0.55)
330
+ OmniLight3D: light_color = Color(0.40, 0.65, 1.0), light_energy = 2.4
331
+ ```
332
+
333
+ ## Anti-patterns
334
+
335
+ - **Spatial-only shader on the visual quad.** It still works, but you forgot `blend_add` + `unshaded` and the flame looks like a flat orange triangle in shadow. Always: `render_mode unshaded, blend_add, depth_draw_never;`.
336
+ - **Looping a one-shot emitter.** `one_shot = false` for fire (continuous). One-shot is for `muzzle-flash` and `hit-spark`.
337
+ - **Missing `cull_disabled`.** Particles billboard toward camera; if you cull backfaces, the flame disappears at certain angles.
338
+ - **`depth_draw_opaque` on the visual material.** You'll z-fight with anything inside the flame (the torch handle). Use `depth_draw_never`.
339
+ - **OmniLight3D parented under the GPUParticles3D.** The particles emit in local space; the light moves with them. Sibling, not child.
340
+ - **Procedural noise computed in world space without a per-instance seed.** Every particle pulses identically, looking like a strobe. The included shader uses `CUSTOM.y` as a per-particle seed.
341
+ - **Generating a fire image and using it as the particle texture.** That's the misroute this skill exists to prevent. The shader-driven look is more alive and trivially recolorable.
342
+
343
+ ## Performance notes
344
+
345
+ - Default 96 particles per fire: ~0.05 ms on desktop GPU, scales linearly. Budget ~512 fire particles total in a scene before you should think about LOD.
346
+ - Mobile: drop default to 48, disable the `OmniLight3D` shadow cast, and use a 64×64 noise texture instead of FBM if FPS dips.
347
+ - LOD: in the controller's `_process`, scale `amount_ratio` by `clamp(1.0 - dist_to_camera / 30.0, 0.05, 1.0)`. Past 30 m, drop to a single billboard quad with the same shader (no particles).
348
+ - The OmniLight3D shadow is the expensive part. Disable shadows on torches that aren't doing dramatic work.
349
+ - `blend_add` skips the depth write — good for perf, but means you can't sort fires against each other. Avoid stacking 5 flames at the same Z.
350
+
351
+ ## Edge cases
352
+
353
+ - **Underwater fire.** Don't. If the user insists (a magic underwater shrine), drop `flame_height` to 0.4, `noise_speed` to 0.3, and tint everything blue-green via the color ramp.
354
+ - **Wind affecting the flame.** The simple `process_material` doesn't know about wind. Either drive `pm.gravity = Vector3(wind.x, 0.6, wind.z)` from a script polling a wind vector, or use the `fire_process.gdshader` and pass a wind uniform.
355
+ - **Fire on a moving object (torch held by NPC).** Set `local_coords = true` so particles emit in the torch's local space. Otherwise they trail behind as the NPC walks.
356
+ - **Fire viewed in mirror / reflection probe.** The flame won't appear in baked reflections (it's transparent). Add a fake glow card (MeshInstance3D with an emissive material) for reflections to pick up.
357
+ - **Cinematic close-up of the flame.** Default 96 particles looks sparse at 1080p. Bump to 256 for cinematics, drop back for gameplay.
358
+
359
+ ## Fallback (no MCP)
360
+
361
+ VFX is code, no MCP required. The user can:
362
+
363
+ 1. Create the four files at `addons/vfx/fire/` with the contents above.
364
+ 2. In Godot, add a `GPUParticles3D` node, attach `fire_controller.gd`.
365
+ 3. Set `process_material` to a new `ParticleProcessMaterial` (the script configures it on `_ready`).
366
+ 4. Set `draw_pass_1` to a new `QuadMesh` (size 1×1), with `surface_material_override[0]` set to a `ShaderMaterial` whose shader is `fire.gdshader`.
367
+ 5. Save as `fire.tscn` and instantiate at every torch / brazier.
368
+
369
+ ## Handoff
370
+
371
+ After firing this recipe, suggest neighbors:
372
+
373
+ - `summer:visual-effects/recipes/smoke` — every fire that lasts > 5 s wants smoke rising above. Spawn 0.5 m above the flame top.
374
+ - `summer:visual-effects/recipes/dissolve` — if the user wants the *fuel* (logs, body, paper) to gradually disappear as it burns.
375
+ - `summer:visual-effects/recipes/magic-glow` — for an `OmniLight3D` with proper sin-wave pulsing if the default is too constant.
376
+ - `summer:visual-effects/game-feel` — for a slight camera flicker / thermal heat-haze post-process near very large fires.
377
+ - `summer:audio/sound-effect` — generate `fire crackle, dry wood, soft pops, 2s loop` and wire to an `AudioStreamPlayer3D` on the same anchor.
378
+
379
+ ## See also
380
+
381
+ - `_building-blocks/noise-3d-fbm.gdshaderinc` — the FBM noise this shader includes
382
+ - `_building-blocks/additive-billboard-particles.md` — canonical additive material settings
383
+ - `summer:visual-effects/recipes/smoke` — sister recipe for the column rising above
384
+ - `summer:visual-effects/recipes/dissolve` — for objects burning away
385
+ - `summer:visual-effects/recipes/magic-glow` — for the light source pulsing
386
+ - `summer:visual-effects/game-feel` — non-particle game-feel pairings
387
+ - `summer:gdscript-patterns` — for the controller's idioms (signals, exports, `@tool`)