summer-engine 1.3.1 → 1.4.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 (49) hide show
  1. package/.claude-plugin/plugin.json +59 -0
  2. package/.codex-plugin/plugin.json +38 -0
  3. package/.cursor-plugin/plugin.json +42 -0
  4. package/.mcp.json +8 -0
  5. package/.opencode/plugins/summer.js +49 -0
  6. package/CHANGELOG.md +67 -0
  7. package/README.md +13 -0
  8. package/dist/lib/skills-registry.d.ts +80 -0
  9. package/dist/lib/skills-registry.js +172 -0
  10. package/gemini-extension.json +21 -0
  11. package/hooks/hooks-cursor.json +21 -0
  12. package/hooks/hooks.json +31 -0
  13. package/hooks/pre-commit-doctor.sh +123 -0
  14. package/hooks/run-hook.cmd +23 -0
  15. package/hooks/session-start.sh +120 -0
  16. package/marketplace.json +24 -0
  17. package/package.json +13 -3
  18. package/skills/_meta/skill-test/SKILL.md +1 -1
  19. package/skills/_shared/summer-folder.md +44 -0
  20. package/skills/_tests/specs/art-direction.md +115 -0
  21. package/skills/_tests/specs/audio-direction.md +123 -0
  22. package/skills/_tests/specs/brainstorm-game.md +99 -0
  23. package/skills/_tests/specs/design-level.md +113 -0
  24. package/skills/_tests/specs/design-mechanic.md +112 -0
  25. package/skills/_tests/specs/design-npc.md +112 -0
  26. package/skills/_tests/specs/export-and-ship.md +114 -0
  27. package/skills/_tests/specs/fps-controller.md +5 -5
  28. package/skills/_tests/specs/peer-to-peer-multiplayer.md +99 -0
  29. package/skills/_tests/specs/setup-multiplayer.md +106 -0
  30. package/skills/_tests/specs/tune-performance.md +104 -0
  31. package/skills/_tests/specs/vfx.md +98 -0
  32. package/skills/ai-and-npcs/design-npc/SKILL.md +343 -0
  33. package/skills/asset-pipeline/asset-strategy/SKILL.md +8 -0
  34. package/skills/audio/audio-direction/SKILL.md +258 -0
  35. package/skills/catalog.yaml +82 -0
  36. package/skills/character-controllers/fps-controller/SKILL.md +27 -0
  37. package/skills/deployment/export-and-ship/SKILL.md +300 -0
  38. package/skills/gameplay-mechanics/design-mechanic/SKILL.md +265 -0
  39. package/skills/level-design/design-level/SKILL.md +266 -0
  40. package/skills/multiplayer-and-networking/peer-to-peer-multiplayer/SKILL.md +378 -0
  41. package/skills/multiplayer-and-networking/setup-multiplayer/SKILL.md +328 -0
  42. package/skills/performance/tune-performance/SKILL.md +219 -0
  43. package/skills/rendering-and-lighting/3d-lighting/SKILL.md +20 -0
  44. package/skills/rendering-and-lighting/art-direction/SKILL.md +284 -0
  45. package/skills/scene-and-project/brainstorm-game/SKILL.md +210 -0
  46. package/skills/scene-and-project/make-game/SKILL.md +10 -0
  47. package/skills/scene-and-project/scene-composition/SKILL.md +8 -0
  48. package/skills/ui-and-ux/ui-basics/SKILL.md +28 -0
  49. package/skills/visual-effects/vfx/SKILL.md +280 -0
@@ -0,0 +1,280 @@
1
+ ---
2
+ name: vfx
3
+ description: Use when the user says the game "feels flat", "needs juice", "lacks impact", or asks for particles, hit feedback, screen shake, camera juice, muzzle flashes, explosions, or pickup effects. Walks the user through a guided 30-minute polish pass that adds visible feedback to combat, movement, pickups, damage, and casting. Trigger on "vfx", "juice", "feels flat", "polish", "particles", "screen shake", "hit flash", "feedback".
4
+ license: MIT
5
+ compatibility: [Cursor, Claude Code, Windsurf, Codex]
6
+ category: visual-effects
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
9
+ paths: ["**/*.gd", "**/*.tscn", "**/*.tres"]
10
+ ---
11
+
12
+ # /vfx — Visible Polish in 30 Minutes
13
+
14
+ A game with bad VFX feels like a tech demo. A game with good VFX feels alive even before the mechanics are deep. This skill is a guided polish pass: you ask the user *what feels flat*, then deliver one or two tight recipes that add immediately-visible feedback. No vague "add particles". Specific node, specific values, specific signal hookup, verified result.
15
+
16
+ ## Steps
17
+
18
+ ### 1. Ask which thing feels flat
19
+
20
+ Open with one focused question. Wait for the answer.
21
+
22
+ > What feels flat? Pick one to start: combat hits / movement / pickups / damage taken / explosions / spell casts / something else.
23
+
24
+ If the user says "all of it", anchor on **combat hits** — it gives the loudest perceived improvement per minute spent.
25
+
26
+ ### 2. Read the current scene state
27
+
28
+ Before adding anything, see what's there.
29
+
30
+ **Preferred (Summer MCP):**
31
+
32
+ ```
33
+ summer_get_scene_tree
34
+ summer_inspect_node "./World/Player" # or whichever node owns the action
35
+ ```
36
+
37
+ You're looking for: where the trigger lives (e.g. `body_entered` signal on a Hurtbox), whether a Camera3D exists for shake, whether the target has a MeshInstance3D for a flash material override.
38
+
39
+ **Fallback (no MCP):** ask the user to paste the relevant `.tscn` snippet or describe the parent of the actor.
40
+
41
+ ### 3. Pick the recipe and propose it explicitly
42
+
43
+ Each recipe is a tight bundle of (node + property + signal + script). Don't mix recipes — add one, see it land, stack later.
44
+
45
+ > I want to add a hit flash + 0.04s camera shake bound to the `damaged` signal on `./World/Enemy`. About 6 lines of GDScript and one Camera3D shake node. May I proceed?
46
+
47
+ ### 4. Apply the recipe
48
+
49
+ See **Recipes** below. Each is end-to-end: node tree, property values, signal hookup, GDScript stub.
50
+
51
+ ### 5. Verify
52
+
53
+ ```
54
+ summer_save_scene
55
+ summer_get_script_errors
56
+ summer_play
57
+ # user reproduces the trigger
58
+ summer_stop
59
+ ```
60
+
61
+ If it doesn't read on screen, the values are wrong (flash too dim, shake too short, particle lifetime too long). Tune one knob at a time.
62
+
63
+ ## Recipes
64
+
65
+ ### Recipe A — Hit flash (mesh albedo flicker)
66
+
67
+ **Goal:** when an enemy takes damage, the mesh flashes white for 80 ms. Single biggest-bang-for-buck VFX. Read instantly even at 60 fps.
68
+
69
+ **Approach:** swap to a flash material via `material_override`, then back. Use Tween for the timing.
70
+
71
+ **MCP path:**
72
+
73
+ ```
74
+ summer_add_node(parent="./World/Enemy", type="Node", name="HitFlash")
75
+ # attach script (see below) — script holds reference to MeshInstance3D and a flash StandardMaterial3D
76
+ ```
77
+
78
+ Script (`scripts/hit_flash.gd`):
79
+
80
+ ```gdscript
81
+ class_name HitFlash
82
+ extends Node
83
+
84
+ @export var mesh: MeshInstance3D
85
+ @export var flash_color: Color = Color(1.0, 1.0, 1.0)
86
+ @export var duration: float = 0.08
87
+
88
+ var _flash_mat: StandardMaterial3D
89
+ var _orig_mat: Material
90
+
91
+ func _ready() -> void:
92
+ if mesh == null:
93
+ push_warning("HitFlash: mesh export not assigned")
94
+ return
95
+ _orig_mat = mesh.get_surface_override_material(0)
96
+ _flash_mat = StandardMaterial3D.new()
97
+ _flash_mat.albedo_color = flash_color
98
+ _flash_mat.emission_enabled = true
99
+ _flash_mat.emission = flash_color
100
+ _flash_mat.emission_energy_multiplier = 2.0
101
+
102
+ func flash() -> void:
103
+ if mesh == null:
104
+ return
105
+ mesh.set_surface_override_material(0, _flash_mat)
106
+ var tween := create_tween()
107
+ tween.tween_interval(duration)
108
+ tween.tween_callback(func(): mesh.set_surface_override_material(0, _orig_mat))
109
+ ```
110
+
111
+ Wire the trigger:
112
+
113
+ ```
114
+ summer_connect_signal(from="./World/Enemy", signal="damaged", to="./World/Enemy/HitFlash", method="flash")
115
+ ```
116
+
117
+ **Tunable knobs:** `duration` (0.06–0.12 sec — under 0.06 reads as a flicker, over 0.12 looks slow), `emission_energy_multiplier` (1.5–3.0).
118
+
119
+ > CRITICAL: do NOT inline the StandardMaterial3D as a sub_resource via `summer_set_prop`. Build it in script (as above) or save it to `materials/flash.tres` and load. Inline sub_resources break `summer_set_resource_property` silently — see `_shared/mcp-tools-reference.md` § "Trap".
120
+
121
+ ### Recipe B — Screen shake (Camera3D wobble)
122
+
123
+ **Goal:** punchy 40–80 ms camera shake on impact. Ties hits to the player's body.
124
+
125
+ **Approach:** attach a shake script to the existing Camera3D. Don't spawn a new camera; reuse the player's.
126
+
127
+ ```
128
+ summer_inspect_node "./World/Player/Camera3D" # confirm the path
129
+ ```
130
+
131
+ Script (`scripts/camera_shake.gd`):
132
+
133
+ ```gdscript
134
+ class_name CameraShake
135
+ extends Camera3D
136
+
137
+ @export var trauma_decay: float = 5.0
138
+ @export var max_offset: Vector2 = Vector2(0.15, 0.15)
139
+ @export var max_roll: float = 0.05
140
+
141
+ var _trauma: float = 0.0
142
+ var _base_position: Vector3
143
+
144
+ func _ready() -> void:
145
+ _base_position = position
146
+
147
+ func add_trauma(amount: float) -> void:
148
+ _trauma = clamp(_trauma + amount, 0.0, 1.0)
149
+
150
+ func _process(delta: float) -> void:
151
+ if _trauma <= 0.0:
152
+ position = _base_position
153
+ rotation.z = 0.0
154
+ return
155
+ var shake := _trauma * _trauma # quadratic — feels punchy, not noisy
156
+ position = _base_position + Vector3(
157
+ randf_range(-max_offset.x, max_offset.x) * shake,
158
+ randf_range(-max_offset.y, max_offset.y) * shake,
159
+ 0.0
160
+ )
161
+ rotation.z = randf_range(-max_roll, max_roll) * shake
162
+ _trauma = max(_trauma - trauma_decay * delta, 0.0)
163
+ ```
164
+
165
+ Trigger from the player or any damage handler:
166
+
167
+ ```gdscript
168
+ $Camera3D.add_trauma(0.5) # 0.3 light hit, 0.5 normal, 0.8 heavy
169
+ ```
170
+
171
+ **Tunable knobs:** `max_offset` (0.05 subtle, 0.2 punchy, 0.4 cinematic), `trauma_decay` (3 long, 5 standard, 8 snappy). Quadratic falloff is the trick — linear feels twitchy.
172
+
173
+ ### Recipe C — Pickup sparkle (GPUParticles3D one-shot)
174
+
175
+ **Goal:** when a pickup is collected, emit a quick burst of particles where it disappeared.
176
+
177
+ **Approach:** GPUParticles3D set to `one_shot = true`, `emitting = true` triggered from script. Particle material **must** be a standalone `.tres` (inline sub_resource trap).
178
+
179
+ ```
180
+ summer_add_node(parent="./World/Pickup", type="GPUParticles3D", name="Sparkle")
181
+ summer_set_prop(path="./World/Pickup/Sparkle", key="one_shot", value="true")
182
+ summer_set_prop(path="./World/Pickup/Sparkle", key="amount", value="24")
183
+ summer_set_prop(path="./World/Pickup/Sparkle", key="lifetime", value="0.5")
184
+ summer_set_prop(path="./World/Pickup/Sparkle", key="explosiveness", value="0.95")
185
+ ```
186
+
187
+ For the `process_material`, save a `ParticleProcessMaterial.tres` first (see `asset-pipeline/asset-strategy/SKILL.md` for the pattern), then:
188
+
189
+ ```
190
+ summer_set_prop(path="./World/Pickup/Sparkle", key="process_material", value="res://materials/sparkle_particles.tres")
191
+ ```
192
+
193
+ In the pickup's collection handler:
194
+
195
+ ```gdscript
196
+ func _on_picked_up() -> void:
197
+ $Sparkle.restart()
198
+ $Sparkle.emitting = true
199
+ $Mesh.visible = false
200
+ await get_tree().create_timer(0.6).timeout
201
+ queue_free()
202
+ ```
203
+
204
+ `explosiveness = 0.95` is the difference between a continuous trickle and a satisfying *poof*. `0.0` = constant emission, `1.0` = all particles spawn at frame zero.
205
+
206
+ ### Recipe D — Muzzle flash (light + sprite + sound)
207
+
208
+ **Goal:** when a weapon fires, a brief OmniLight3D flash + Sprite3D billboard + audio cue. Sells the shot more than the projectile does.
209
+
210
+ ```
211
+ summer_add_node(parent="./World/Player/Weapon", type="OmniLight3D", name="MuzzleLight")
212
+ summer_set_prop(path="./World/Player/Weapon/MuzzleLight", key="light_color", value="Color(1.0, 0.85, 0.4)")
213
+ summer_set_prop(path="./World/Player/Weapon/MuzzleLight", key="light_energy", value="0.0")
214
+ summer_set_prop(path="./World/Player/Weapon/MuzzleLight", key="omni_range", value="2.5")
215
+ ```
216
+
217
+ Trigger from the fire function:
218
+
219
+ ```gdscript
220
+ func fire() -> void:
221
+ var light: OmniLight3D = $MuzzleLight
222
+ light.light_energy = 4.0
223
+ var tween := create_tween()
224
+ tween.tween_property(light, "light_energy", 0.0, 0.06)
225
+ $MuzzleSound.play()
226
+ $Camera3D.add_trauma(0.3) # if camera shake recipe applied
227
+ ```
228
+
229
+ Do NOT keep the light always-on at low energy — that costs a real-time light slot. Tween from 4.0 to 0.0 over 60 ms.
230
+
231
+ ### Recipe E — Explosion (5-element stack)
232
+
233
+ **Goal:** one Big Boom, all senses. Stack five small effects on the same trigger.
234
+
235
+ 1. **Light flash:** OmniLight3D, `light_energy` 0 → 8 → 0 over 200 ms
236
+ 2. **Particles:** GPUParticles3D one-shot, `amount` 64, `lifetime` 0.8, `explosiveness` 1.0
237
+ 3. **Camera shake:** `add_trauma(0.8)`
238
+ 4. **Audio:** AudioStreamPlayer3D, layered explosion
239
+ 5. **Hit-stop:** `Engine.time_scale = 0.05` for 60 ms, then 1.0
240
+
241
+ Hit-stop is the secret weapon. 60 ms of 0.05 time scale = the explosion lands. Use sparingly; once per "big" event. Reset with:
242
+
243
+ ```gdscript
244
+ Engine.time_scale = 0.05
245
+ await get_tree().create_timer(0.06).timeout
246
+ Engine.time_scale = 1.0
247
+ ```
248
+
249
+ ## Anti-patterns
250
+
251
+ | Don't | Do | Why |
252
+ |---|---|---|
253
+ | Inline sub_resource for ParticleProcessMaterial via `summer_set_prop` | Save standalone `.tres`, reference by path | `summer_set_resource_property` silently fails on inline sub_resources |
254
+ | Hit flash for 0.3 sec | Hit flash for 0.06–0.12 sec | Anything over ~120 ms reads as a "stay" not a flash |
255
+ | Camera shake with linear falloff | Quadratic falloff (`_trauma * _trauma`) | Linear feels jittery; quadratic feels weighty |
256
+ | `OmniLight3D` always on at low energy | Tween from off → on → off in 60–80 ms | Real-time lights have hard count limits per frame |
257
+ | GPUParticles3D `one_shot = false` for pickups | `one_shot = true`, `restart()` to fire | Otherwise particles trickle continuously and look wrong |
258
+ | Tween every property at once | Tween one or two; let the others snap | Over-animated polish reads as noise |
259
+ | `Engine.time_scale` < 0.1 for > 100 ms | `time_scale = 0.05` for 40–80 ms | Longer hit-stop feels broken, not satisfying |
260
+ | Particles with `lifetime > 1.0` for hits | `lifetime = 0.3–0.6` | Hit feedback should resolve before the next swing |
261
+
262
+ ## Collaborative protocol
263
+
264
+ This skill writes scripts and adds nodes. Always ask before each recipe is applied. Group related writes into one ask: "I'm about to add HitFlash + CameraShake + connect 2 signals. OK?" See `_shared/collaborative-protocol.md`.
265
+
266
+ ## Want a working starter?
267
+
268
+ No template — this is a workflow that bolts onto any existing scene. For a project that already has these recipes pre-wired, see `template-3d-fps` (hit flash + muzzle flash) and `template-3d-platformer` (pickup sparkle).
269
+
270
+ ## See also
271
+
272
+ - `_shared/mcp-tools-reference.md` — full MCP tool list, especially the inline-sub_resource trap
273
+ - `_shared/godot-version.md` — Godot 4.5 API notes
274
+ - `_shared/collaborative-protocol.md` — "May I write" pattern
275
+ - `_shared/gd-style.md` — typed GDScript conventions
276
+ - `post-processing/screen-shake/SKILL.md` — deeper camera-shake techniques (Perlin noise, trauma stacks)
277
+ - `visual-effects/gpuparticles-3d-basics/SKILL.md` — particle setup details
278
+ - `visual-effects/hit-impact-flashes/SKILL.md` — flash material variations
279
+ - `visual-effects/muzzle-flashes/SKILL.md` — muzzle flash deep dive
280
+ - `audio/sfx-pipeline/SKILL.md` — pairing audio with VFX