summer-engine 1.5.0 → 2.2.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 (101) hide show
  1. package/{marketplace.json → .claude-plugin/marketplace.json} +3 -3
  2. package/.claude-plugin/plugin.json +52 -11
  3. package/.codex-plugin/plugin.json +69 -8
  4. package/.cursor-plugin/plugin.json +53 -12
  5. package/.factory-plugin/plugin.json +20 -0
  6. package/.opencode/INSTALL.md +75 -0
  7. package/.opencode/plugins/summer.js +9 -6
  8. package/AGENTS.md +51 -0
  9. package/CHANGELOG.md +31 -1
  10. package/CLAUDE.md +5 -0
  11. package/GEMINI.md +56 -0
  12. package/README.md +409 -80
  13. package/_persona/summer/SKILL.md +113 -0
  14. package/dist/bin/summer.js +0 -0
  15. package/dist/commands/assets.d.ts +2 -0
  16. package/dist/commands/assets.js +189 -0
  17. package/dist/commands/create.js +55 -17
  18. package/dist/commands/debug.d.ts +2 -0
  19. package/dist/commands/debug.js +127 -0
  20. package/dist/commands/install.js +2 -2
  21. package/dist/commands/list.js +24 -5
  22. package/dist/commands/project.d.ts +2 -0
  23. package/dist/commands/project.js +170 -0
  24. package/dist/commands/scene.d.ts +2 -0
  25. package/dist/commands/scene.js +290 -0
  26. package/dist/lib/banner.js +2 -2
  27. package/dist/lib/log-filters.d.ts +53 -0
  28. package/dist/lib/log-filters.js +130 -0
  29. package/dist/lib/plugin-manifests.test.d.ts +1 -0
  30. package/dist/lib/plugin-manifests.test.js +134 -0
  31. package/dist/lib/remote-templates.d.ts +25 -0
  32. package/dist/lib/remote-templates.js +86 -0
  33. package/dist/lib/skills-registry.d.ts +283 -3
  34. package/dist/lib/skills-registry.js +522 -2
  35. package/dist/lib/telemetry.d.ts +13 -0
  36. package/dist/lib/telemetry.js +63 -0
  37. package/dist/lib/with-engine.d.ts +14 -0
  38. package/dist/lib/with-engine.js +109 -0
  39. package/dist/mcp/tools/asset-tools.js +12 -13
  40. package/dist/mcp/tools/debug-tools.js +32 -9
  41. package/dist/mcp/tools/generate-tools.js +101 -0
  42. package/dist/mcp/tools/with-engine.js +4 -0
  43. package/gemini-extension.json +2 -2
  44. package/hooks/pre-commit-doctor.sh +0 -0
  45. package/hooks/run-hook.cmd +0 -0
  46. package/hooks/session-start.sh +0 -0
  47. package/package.json +11 -5
  48. package/skills/2d-assets/character-portrait/SKILL.md +196 -0
  49. package/skills/2d-assets/concept-art/SKILL.md +152 -0
  50. package/skills/2d-assets/pixel-art/SKILL.md +180 -0
  51. package/skills/2d-assets/skybox-panorama/SKILL.md +207 -0
  52. package/skills/2d-assets/sprite-sheet/SKILL.md +239 -0
  53. package/skills/2d-assets/tileable-texture/SKILL.md +205 -0
  54. package/skills/2d-assets/ui-graphics/SKILL.md +210 -0
  55. package/skills/3d-assets/character-model/SKILL.md +225 -0
  56. package/skills/3d-assets/environment-kit/SKILL.md +164 -0
  57. package/skills/3d-assets/organic-model/SKILL.md +192 -0
  58. package/skills/3d-assets/prop-model/SKILL.md +133 -0
  59. package/skills/3d-assets/vehicle-model/SKILL.md +202 -0
  60. package/skills/README.md +1 -1
  61. package/skills/_meta/skill-create/SKILL.md +1 -1
  62. package/skills/_meta/skill-improve/SKILL.md +1 -1
  63. package/skills/_meta/skill-test/SKILL.md +1 -1
  64. package/skills/_meta/using-summer/SKILL.md +130 -0
  65. package/skills/ai-and-npcs/design-npc/SKILL.md +3 -1
  66. package/skills/animation/animation-tree/SKILL.md +260 -0
  67. package/skills/animation/facial-and-lipsync/SKILL.md +282 -0
  68. package/skills/animation/generate-motion/SKILL.md +129 -0
  69. package/skills/animation/procedural-animation/SKILL.md +234 -0
  70. package/skills/animation/retarget/SKILL.md +172 -0
  71. package/skills/asset-pipeline/asset-strategy/SKILL.md +102 -212
  72. package/skills/audio/adaptive-music/SKILL.md +326 -0
  73. package/skills/audio/ambient-bed/SKILL.md +278 -0
  74. package/skills/audio/music-track/SKILL.md +268 -0
  75. package/skills/audio/sound-effect/SKILL.md +237 -0
  76. package/skills/audio/voice-line/SKILL.md +253 -0
  77. package/skills/catalog.yaml +266 -5
  78. package/skills/character-controllers/fps-controller/SKILL.md +1 -1
  79. package/skills/debugging/debug/SKILL.md +102 -82
  80. package/skills/rendering-and-lighting/3d-lighting/SKILL.md +1 -1
  81. package/skills/scene-and-project/browse-templates/SKILL.md +105 -0
  82. package/skills/scene-and-project/make-game/SKILL.md +159 -346
  83. package/skills/scene-and-project/new-project/SKILL.md +106 -0
  84. package/skills/scene-and-project/scene-composition/SKILL.md +1 -1
  85. package/skills/scripting-patterns/gdscript-patterns/SKILL.md +1 -1
  86. package/skills/ui-and-ux/ui-basics/SKILL.md +1 -1
  87. package/skills/video/animated-loop/SKILL.md +200 -0
  88. package/skills/video/cinematic-cutscene/SKILL.md +204 -0
  89. package/skills/video/trailer-shot/SKILL.md +203 -0
  90. package/skills/visual-effects/_building-blocks/additive-billboard-particles.md +28 -0
  91. package/skills/visual-effects/_building-blocks/noise-3d-fbm.gdshaderinc +45 -0
  92. package/skills/visual-effects/_building-blocks/trauma-shake-snippet.md +51 -0
  93. package/skills/visual-effects/{vfx → game-feel}/SKILL.md +1 -1
  94. package/skills/visual-effects/recipes/dissolve/SKILL.md +349 -0
  95. package/skills/visual-effects/recipes/fire/SKILL.md +387 -0
  96. package/skills/visual-effects/recipes/hit-spark/SKILL.md +330 -0
  97. package/skills/visual-effects/recipes/lightning/SKILL.md +372 -0
  98. package/skills/visual-effects/recipes/magic-glow/SKILL.md +377 -0
  99. package/skills/visual-effects/recipes/muzzle-flash/SKILL.md +326 -0
  100. package/skills/visual-effects/recipes/smoke/SKILL.md +381 -0
  101. package/skills/visual-effects/recipes/water-ripple/SKILL.md +340 -0
@@ -0,0 +1,326 @@
1
+ ---
2
+ name: adaptive-music
3
+ description: Use when wiring music stems to game state — combat / explore / boss / tension crossfades on a shared bus. Pairs generated stems with a state machine and AudioBus structure. Trigger on "adaptive music", "dynamic music", "music changes when combat starts", "layered music", "vertical music", "wire stems".
4
+ license: MIT
5
+ compatibility: [Cursor, Claude Code, Windsurf, Codex]
6
+ category: audio
7
+ user-invocable: true
8
+ allowed-tools: Read Grep Glob Write Edit summer_generate_audio summer_search_assets summer_import_from_url summer_add_node summer_set_prop summer_connect_signal summer_inspect_node summer_get_scene_tree summer_project_setting
9
+ paths: ["audio/music/**", "scripts/audio/**", "**/*.tscn", "default_bus_layout.tres"]
10
+ ---
11
+
12
+ # /adaptive-music — Wire Stems to Game State
13
+
14
+ ## Overview
15
+
16
+ This is the **wiring** skill, not the generation skill. The job is to take 3–5 music stems (or full tracks) and connect them to a game state machine so the music shifts as the player's situation changes — exploration → tension → combat → victory → exploration — with crossfades, ducking, and bus routing the player never consciously notices but always feels.
17
+
18
+ This skill assumes the stems already exist. If they don't, run `/music-track` first for each state, or generate matching stems (same tempo, same key, same bar count) so they layer/crossfade cleanly.
19
+
20
+ ## When to use
21
+
22
+ - The audio bible specifies a dynamic music model (layered stems / vertical re-orchestration / horizontal sequencing).
23
+ - You have at least two music tracks for two different states and want the engine to switch between them.
24
+ - Player-state-driven crossfades, not random shuffling.
25
+
26
+ ## When NOT to use
27
+
28
+ - Generating the stems themselves → `/music-track`.
29
+ - Single static loop with no state changes → `/music-track` and an `AudioStreamPlayer` is enough.
30
+
31
+ ## Steps
32
+
33
+ ### 1. Read the audio bible's dynamic music plan
34
+
35
+ ```
36
+ Read .summer/audio-bible.md
37
+ ```
38
+
39
+ Quote the plan back. Example:
40
+
41
+ > Bible plan: Layered stems. States CALM / TENSION / COMBAT / VICTORY. CALM → TENSION on enemy detect (2s crossfade). TENSION → COMBAT on first damage (instant drum stem). COMBAT → VICTORY on last enemy dies (drum cuts, brass swell). VICTORY → CALM after 4s. Music ducks `-6 dB` on dialogue.
42
+
43
+ If the plan is absent, run `/audio-direction` step 6 first.
44
+
45
+ ### 2. Pick the wiring model
46
+
47
+ | Model | When | Implementation |
48
+ |---|---|---|
49
+ | **Horizontal sequencing** | 3–5 full tracks, one per state, crossfade | One `AudioStreamPlayer` per track on the `Music` bus, tween volumes on state change |
50
+ | **Layered stems** | One base loop + drum/brass/strings stems that turn on with intensity | All stems play simultaneously at silence; mute/unmute via volume tween |
51
+ | **Vertical re-orchestration** | Same melody, different orchestration per state, snap on bar | All variants play simultaneously muted; swap on next bar boundary |
52
+ | **Procedural / generative** | Rules generate music in real-time | Beyond this skill; needs a custom system |
53
+
54
+ For 90% of indie games, **horizontal sequencing** is correct. It's cheaper to author, easier to swap, and players don't notice the difference.
55
+
56
+ ### 3. Confirm stems exist (or generate them)
57
+
58
+ ```
59
+ summer_search_assets(query="music", filter={ kind: "audio" })
60
+ ```
61
+
62
+ You need (typical case):
63
+
64
+ - `audio/music/calm.mp3` — exploration loop, sparse
65
+ - `audio/music/tension.mp3` — same tempo/key, denser, no drums
66
+ - `audio/music/combat.mp3` — same tempo/key, full drums, urgent
67
+ - (optional) `audio/music/boss.mp3` — distinct from combat
68
+ - (optional) `audio/music/victory.mp3` — short linear sting (4–8s)
69
+
70
+ If any are missing, run `/music-track` for each. Critically: **same BPM, same key, same time signature, same bar length** so they overlap cleanly.
71
+
72
+ ### 4. Verify / extend the bus layout
73
+
74
+ The Music bus must exist and route to Master. If `/audio-direction` ran, it does. Verify:
75
+
76
+ ```
77
+ Read default_bus_layout.tres # or wherever the project stores it
78
+ ```
79
+
80
+ You want this structure:
81
+
82
+ ```
83
+ Master
84
+ ├── Music (-8 dB; ducks -6 dB when Voice is active)
85
+ ├── SFX (0 dB; routes to Reverb buses)
86
+ ├── UI (-3 dB)
87
+ ├── Voice (0 dB)
88
+ ├── Ambient (-12 dB)
89
+ ├── Reverb_Small
90
+ ├── Reverb_Large
91
+ └── Reverb_Outdoor
92
+ ```
93
+
94
+ If `Music` bus is missing, run `/audio-direction` step 8.
95
+
96
+ For ducking, add a sidechain compressor on the `Music` bus with the `Voice` bus as the sidechain source. Or implement ducking in code (simpler — see step 7).
97
+
98
+ ### 5. Build the player rig
99
+
100
+ For horizontal sequencing, instantiate one `AudioStreamPlayer` per state, all on the Music bus, all autoplay, all looping, all starting at `-60 dB` except the active state:
101
+
102
+ ```
103
+ summer_add_node(parentPath="/root/Game/Music", type="AudioStreamPlayer", name="Calm")
104
+ summer_set_prop(path="/root/Game/Music/Calm", property="stream", value="res://audio/music/calm.mp3")
105
+ summer_set_prop(path="/root/Game/Music/Calm", property="bus", value="Music")
106
+ summer_set_prop(path="/root/Game/Music/Calm", property="volume_db", value=0.0)
107
+ summer_set_prop(path="/root/Game/Music/Calm", property="autoplay", value=true)
108
+
109
+ summer_add_node(parentPath="/root/Game/Music", type="AudioStreamPlayer", name="Tension")
110
+ summer_set_prop(path="/root/Game/Music/Tension", property="stream", value="res://audio/music/tension.mp3")
111
+ summer_set_prop(path="/root/Game/Music/Tension", property="bus", value="Music")
112
+ summer_set_prop(path="/root/Game/Music/Tension", property="volume_db", value=-60.0)
113
+ summer_set_prop(path="/root/Game/Music/Tension", property="autoplay", value=true)
114
+
115
+ summer_add_node(parentPath="/root/Game/Music", type="AudioStreamPlayer", name="Combat")
116
+ summer_set_prop(path="/root/Game/Music/Combat", property="stream", value="res://audio/music/combat.mp3")
117
+ summer_set_prop(path="/root/Game/Music/Combat", property="bus", value="Music")
118
+ summer_set_prop(path="/root/Game/Music/Combat", property="volume_db", value=-60.0)
119
+ summer_set_prop(path="/root/Game/Music/Combat", property="autoplay", value=true)
120
+ ```
121
+
122
+ All three play simultaneously, in sync, but only one is audible. State transitions tween volumes — the music never restarts, only fades.
123
+
124
+ This is why same BPM / same key / same bar length matter: all three are at the same playhead at all times.
125
+
126
+ ### 6. The MusicDirector script
127
+
128
+ ```gdscript
129
+ # scripts/audio/MusicDirector.gd
130
+ class_name MusicDirector
131
+ extends Node
132
+
133
+ enum State { CALM, TENSION, COMBAT, VICTORY, BOSS }
134
+
135
+ @export var calm: AudioStreamPlayer
136
+ @export var tension: AudioStreamPlayer
137
+ @export var combat: AudioStreamPlayer
138
+ @export var boss: AudioStreamPlayer
139
+ @export var victory_sting: AudioStreamPlayer # one-shot, not looping
140
+
141
+ @export var crossfade_seconds: float = 2.0
142
+ @export var instant_states: Array[State] = [State.COMBAT] # snap, no fade
143
+ @export var active_db: float = 0.0
144
+ @export var inactive_db: float = -60.0
145
+
146
+ var _state: State = State.CALM
147
+ var _tween: Tween
148
+
149
+ func _ready() -> void:
150
+ _apply_state(_state, true)
151
+
152
+ func set_state(new_state: State) -> void:
153
+ if new_state == _state:
154
+ return
155
+ var instant := new_state in instant_states
156
+ _state = new_state
157
+ _apply_state(new_state, instant)
158
+ if new_state == State.VICTORY:
159
+ victory_sting.play()
160
+ await get_tree().create_timer(4.0).timeout
161
+ set_state(State.CALM)
162
+
163
+ func _apply_state(state: State, instant: bool) -> void:
164
+ if _tween and _tween.is_valid():
165
+ _tween.kill()
166
+ _tween = create_tween().set_parallel(true)
167
+ var dur := 0.0 if instant else crossfade_seconds
168
+ for pair in [
169
+ [calm, state == State.CALM],
170
+ [tension, state == State.TENSION],
171
+ [combat, state == State.COMBAT],
172
+ [boss, state == State.BOSS],
173
+ ]:
174
+ var player: AudioStreamPlayer = pair[0]
175
+ var should_be_active: bool = pair[1]
176
+ if player == null:
177
+ continue
178
+ var target_db := active_db if should_be_active else inactive_db
179
+ _tween.tween_property(player, "volume_db", target_db, dur)
180
+ ```
181
+
182
+ Attach it under `/root/Game/Music`, hook the exported player slots to the four players. The director is the single point of truth for music state.
183
+
184
+ ### 7. Drive state from gameplay signals
185
+
186
+ Where does `set_state()` get called from? The game state machine. Hook signals from combat / detection / encounter logic:
187
+
188
+ ```gdscript
189
+ # scripts/audio/MusicHooks.gd
190
+ extends Node
191
+ @export var director: MusicDirector
192
+
193
+ func _ready() -> void:
194
+ EventBus.enemy_detected_player.connect(_on_tension)
195
+ EventBus.player_took_damage.connect(_on_combat)
196
+ EventBus.encounter_cleared.connect(_on_victory)
197
+ EventBus.boss_started.connect(_on_boss)
198
+ EventBus.boss_ended.connect(_on_calm)
199
+ EventBus.dialogue_started.connect(_on_duck)
200
+ EventBus.dialogue_ended.connect(_on_unduck)
201
+
202
+ func _on_tension() -> void: director.set_state(MusicDirector.State.TENSION)
203
+ func _on_combat() -> void: director.set_state(MusicDirector.State.COMBAT)
204
+ func _on_victory() -> void: director.set_state(MusicDirector.State.VICTORY)
205
+ func _on_boss() -> void: director.set_state(MusicDirector.State.BOSS)
206
+ func _on_calm() -> void: director.set_state(MusicDirector.State.CALM)
207
+
208
+ func _on_duck() -> void:
209
+ var bus_idx := AudioServer.get_bus_index(&"Music")
210
+ create_tween().tween_method(
211
+ func(db: float) -> void: AudioServer.set_bus_volume_db(bus_idx, db),
212
+ AudioServer.get_bus_volume_db(bus_idx), -14.0, 0.3
213
+ )
214
+
215
+ func _on_unduck() -> void:
216
+ var bus_idx := AudioServer.get_bus_index(&"Music")
217
+ create_tween().tween_method(
218
+ func(db: float) -> void: AudioServer.set_bus_volume_db(bus_idx, db),
219
+ AudioServer.get_bus_volume_db(bus_idx), -8.0, 0.5
220
+ )
221
+ ```
222
+
223
+ If your project doesn't have an `EventBus` autoload yet, add one — adaptive music depends on event-driven signals, not polling.
224
+
225
+ Wire the connections via `summer_connect_signal` so they survive a scene reload.
226
+
227
+ ### 8. Layered stems variant (alternative to step 6)
228
+
229
+ If the bible says **layered stems** instead of horizontal sequencing, the rig is the same (multiple `AudioStreamPlayer`s, all looping in sync, all on Music bus) but the volume map is different:
230
+
231
+ | State | Base | Drums | Strings | Brass |
232
+ |---|---|---|---|---|
233
+ | CALM | 0 | -60 | -60 | -60 |
234
+ | TENSION | 0 | -60 | -10 | -60 |
235
+ | COMBAT | 0 | 0 | -3 | -10 |
236
+ | BOSS | -60 | 0 | 0 | 0 |
237
+
238
+ The director method is the same `_apply_state` pattern — just more players and per-player target_db tables.
239
+
240
+ ### 9. Bar-locked snap (vertical re-orchestration)
241
+
242
+ For the third model, snapping must happen on a bar boundary (not mid-phrase). All players run in sync; on state change, schedule the snap for the next bar:
243
+
244
+ ```gdscript
245
+ const BPM := 128.0
246
+ const BEATS_PER_BAR := 4
247
+ const SECONDS_PER_BAR := (60.0 / BPM) * BEATS_PER_BAR
248
+
249
+ func snap_at_next_bar(callable: Callable) -> void:
250
+ var pos := calm.get_playback_position()
251
+ var into_bar := fmod(pos, SECONDS_PER_BAR)
252
+ var wait := SECONDS_PER_BAR - into_bar
253
+ get_tree().create_timer(wait).timeout.connect(callable)
254
+ ```
255
+
256
+ Use this for boss reveals, story beats — anywhere a snap-on-bar is more dramatic than a crossfade.
257
+
258
+ ### 10. Test the transitions
259
+
260
+ Run the game (`summer_play`) and trigger each transition. Listen for:
261
+
262
+ - Crossfade artifacts at `crossfade_seconds = 2.0`. Too fast: fades are choppy. Too slow: state changes feel disconnected from gameplay.
263
+ - Out-of-sync stems. If combat enters and the drums hit on the wrong beat, the stems weren't generated at the same BPM/bar length. Regenerate.
264
+ - Ducking that's too aggressive (music disappears) or too subtle (voice still buried). The `-6 dB` is a starting value; tune in step 7.
265
+ - Victory sting that loops (it shouldn't). Set `victory_sting` to a non-loop AudioStream.
266
+
267
+ ### 11. Edge case: scene transitions
268
+
269
+ When the player changes scenes, the `MusicDirector` either:
270
+
271
+ a) **Persists** as an autoload. New scenes call `MusicDirector.set_state()` to take over.
272
+ b) **Per-scene**, fades out the previous before the scene changes (`set_state(CALM)` then crossfade to `-60 dB` on all then change scene).
273
+
274
+ Persistent is preferred for open-world; per-scene for level-based games.
275
+
276
+ ## Reference card — bus map
277
+
278
+ ```
279
+ Master
280
+ ├── Music -8 dB (ducks to -14 dB on Voice)
281
+ │ └── (one AudioStreamPlayer per state, all looping in sync)
282
+ ├── SFX 0 dB
283
+ │ └── routes to Reverb_Small / Reverb_Large / Reverb_Outdoor sends
284
+ ├── UI -3 dB
285
+ ├── Voice 0 dB
286
+ ├── Ambient -12 dB
287
+ ├── Reverb_Small (return)
288
+ ├── Reverb_Large (return)
289
+ └── Reverb_Outdoor (return)
290
+ ```
291
+
292
+ ## Anti-patterns
293
+
294
+ - **Stems generated at different BPM/key.** They will not crossfade cleanly. Same BPM, same key, same bar count.
295
+ - **Restarting tracks on state change.** Players hear the restart and the magic dies. Always crossfade volumes; never `play()` mid-state.
296
+ - **No autoplay on the silent stems.** They can't fade in to phase if they never started.
297
+ - **Polling state in `_process`.** Use signals. Music director listens to `EventBus`, not to game state every frame.
298
+ - **Hardcoding volumes in `set_state`.** Use the table-driven `_apply_state` so adding a new state doesn't fork the function.
299
+ - **Ducking music to silence on dialogue.** The bible says `-6 dB` (so music drops from `-8` to `-14`), not muted. Voice should sit on a present bed, not a dead one.
300
+ - **Boss music as a louder combat track.** Distinct, named instrument; ideally different tempo or key. The bible's `Boss music is its own track` rule.
301
+
302
+ ## Edge cases
303
+
304
+ - **Player triggers TENSION → COMBAT in 0.2s.** The `instant_states = [State.COMBAT]` makes COMBAT snap so it lands on the swing connect. The intermediate fade is wrong — combat must feel immediate.
305
+ - **Player flees combat back to TENSION → CALM.** Use the regular `crossfade_seconds = 2.0`; the de-escalation can breathe.
306
+ - **Multiple enemies aware → one dies → none aware.** The state machine should hysteresis: only fall back to CALM after `no_enemies_aware AND no_recent_damage_for=8s`.
307
+ - **Audio service not ready on `_ready`.** If `AudioServer.get_bus_index("Music")` returns `-1`, the bus layout failed to load. Verify `default_bus_layout.tres`.
308
+ - **Stems generated at 60s but you want a 5-minute combat.** They loop. Crossfade is volume-only, not time-based, so loops are invisible to the director.
309
+
310
+ ## Fallback (no MCP)
311
+
312
+ Print the bus layout, the node tree, and the GDScript. User constructs in the Godot editor manually.
313
+
314
+ ## Handoff
315
+
316
+ > MusicDirector wired. Calm / Tension / Combat play in sync, only one audible. State driven by `EventBus`. Music ducks `-6 dB` on dialogue. Next:
317
+ > - Run `summer_play` and trigger each transition; listen for sync issues.
318
+ > - If COMBAT lands on the wrong beat, the stems are off-tempo — regenerate via `/music-track` with identical BPM and bar count.
319
+ > - For boss-specific music, generate `boss.mp3` with `/music-track` and the director picks it up automatically.
320
+
321
+ ## See also
322
+
323
+ - `audio/audio-direction` — defines the dynamic music plan and bus layout
324
+ - `audio/music-track` — generates the stems
325
+ - `audio/voice-line` — the voice bus that triggers ducking
326
+ - `_shared/godot-version.md` — Godot 4.5 AudioServer and Tween API
@@ -0,0 +1,278 @@
1
+ ---
2
+ name: ambient-bed
3
+ description: Use when generating a long looping location ambience — forest at dawn, dungeon air, city street, spaceship hum, cave drip. Non-melodic, low-energy, never draws attention. Wires as a looping AudioStreamPlayer on the Ambient bus with a marked seamless loop. Trigger on "ambient bed", "room tone", "background ambience", "forest ambience", "dungeon air", "make it sound like a cave".
4
+ license: MIT
5
+ compatibility: [Cursor, Claude Code, Windsurf, Codex]
6
+ category: audio
7
+ user-invocable: true
8
+ allowed-tools: Read Grep Glob Write Edit summer_generate_audio summer_search_assets summer_import_from_url summer_add_node summer_set_prop summer_inspect_node
9
+ paths: ["audio/ambient/**", "**/*.tscn", "**/*.import"]
10
+ ---
11
+
12
+ # /ambient-bed — Long Looping Location Ambience
13
+
14
+ ## Overview
15
+
16
+ An ambient bed is the *air* of a place. Forest at dawn, dungeon corridor, spaceship hum, market street, ocean cave. It plays at low energy under everything else — never melodic, never drawing attention, but its absence is immediately felt. Players don't notice an ambient bed; they notice the silence when you forget one.
17
+
18
+ This is **not** music. Use `summer_generate_audio({capability: 'sound_effects'})`, not `{capability: 'music'}`. SFX renders textures; Music renders melody. You want texture.
19
+
20
+ Generated at 15–22 seconds (the SFX duration ceiling) and looped seamlessly with a Godot loop point and optional crossfade.
21
+
22
+ ## When to use
23
+
24
+ - Location bed for a level, room, biome, or zone.
25
+ - Background hum / drone under dialogue scenes.
26
+ - Atmosphere layer that complements a music track.
27
+
28
+ ## When NOT to use
29
+
30
+ - Short event SFX (<5s) → `audio/sound-effect`.
31
+ - Music with melody / chord progression → `audio/music-track`.
32
+ - Voice barks → `audio/voice-line`.
33
+
34
+ ## Steps
35
+
36
+ ### 1. Read the audio bible
37
+
38
+ ```
39
+ Read .summer/audio-bible.md
40
+ ```
41
+
42
+ The bible's `Ambient layer` SFX class names the character — usually "continuous, low presence, no looping melody". Honor it. The mix rules sit ambient at the bus level the bible defined (typically `Ambient` bus around `-12 dB`).
43
+
44
+ ### 2. Identify the location
45
+
46
+ Ask if it's not pinned by context:
47
+
48
+ > Which location's ambience? Be specific — "forest at dawn with light wind and distant birds" beats "outdoors". The model needs the time of day, the weather, and 2–3 sound sources.
49
+
50
+ ### 3. Build the prompt — environment + time + weather + 2–3 sources + density + duration
51
+
52
+ ElevenLabs SFX renders ambient textures well when the prompt names sources, not vibes.
53
+
54
+ Prompts that work:
55
+
56
+ ```
57
+ forest at dawn, gentle wind through leaves, distant bird calls every 6 seconds,
58
+ soft creaking branches, no music, no human sound, 22s seamless loop
59
+
60
+ stone dungeon corridor, distant water drip, low rumble, faint draft, no music,
61
+ no creature sound, 22s seamless loop
62
+
63
+ spaceship corridor, low hum at 60Hz, occasional electronic chirp, ventilation
64
+ whoosh, no human voices, 22s seamless loop
65
+
66
+ medieval marketplace at noon, distant chatter, footsteps on stone, occasional
67
+ horse, distant blacksmith hammer, no recognisable words, 22s loop
68
+
69
+ ocean cave, slow waves entering, low rumble, water drip echoes, no seagulls,
70
+ 22s seamless loop
71
+
72
+ night swamp, crickets, distant owl, slow water lapping, occasional frog,
73
+ 22s seamless loop
74
+
75
+ abandoned office building, fluorescent hum, distant air conditioning, faint
76
+ paper rustle from a vent, no human sound, 22s loop
77
+ ```
78
+
79
+ Prompts that DON'T work:
80
+
81
+ | Bad prompt | Failure mode |
82
+ |---|---|
83
+ | `forest ambience` | No time / weather / sources. Returns generic forest with random energy spikes. |
84
+ | `creepy dungeon` | Vibe word, not a description. Model adds horror stings you don't want. |
85
+ | `peaceful background music` | Triggers musical interpretation. Use SFX-shaped prompts. |
86
+ | `loud bustling market` | Energy too high for a bed; use as a one-shot SFX layer instead. |
87
+ | `forest with wolves howling` | Wolf howl is an event, not a bed; promotes itself in the mix. |
88
+
89
+ ### 4. Density — keep it low
90
+
91
+ A bed should be **boring**. Ask for sparse events.
92
+
93
+ - `every 6 seconds` rather than `often`.
94
+ - `occasional` not `frequent`.
95
+ - `distant` not `close`.
96
+ - `low` not `loud`.
97
+
98
+ If the user wants a busier bed (market, party), still prevent foreground events: `no recognisable words`, `no individual loud sounds`, `general crowd murmur`.
99
+
100
+ ### 5. Generate at the duration ceiling
101
+
102
+ ```
103
+ summer_generate_audio(
104
+ capability: "sound_effects",
105
+ prompt: "forest at dawn, gentle wind through leaves, distant bird calls every 6 seconds, soft creaking branches, no music, no human sound, 22s seamless loop",
106
+ durationSeconds: 22
107
+ )
108
+ // Result: { asset: { fileUrl, ... } }
109
+ // Then: summer_import_from_url(url: "<fileUrl>", path: "res://audio/ambient/forest_dawn.wav")
110
+ ```
111
+
112
+ 22s is the SFX ceiling. Always generate at the ceiling for ambient — longer cycle means less recognized repetition.
113
+
114
+ If the model returns a clip with a loud event near the start or end (a sudden bird right at 0:00), regenerate. That kills loop seamlessness.
115
+
116
+ ### 6. Mark the seamless loop in Godot's import dock
117
+
118
+ This step is what makes the bed sound like a place rather than a 22-second sample.
119
+
120
+ In the Godot editor, select the `.wav`, open the Import dock:
121
+
122
+ | Setting | Value |
123
+ |---|---|
124
+ | Loop | On |
125
+ | Loop Mode | Forward |
126
+ | Loop Begin | 0 (or 0.5 if there's a fade-in artifact) |
127
+
128
+ The `.import` file:
129
+
130
+ ```
131
+ [params]
132
+ loop = true
133
+ loop_offset = 0.0
134
+ loop_mode = 1
135
+ ```
136
+
137
+ After changing, click `Reimport`.
138
+
139
+ ### 7. Crossfade-loop pattern for seamlessness
140
+
141
+ ElevenLabs SFX returns are not gapless. Even with `loop = true`, you'll hear a tiny click on the loop point. Fix it with a two-player crossfade in code:
142
+
143
+ ```gdscript
144
+ # scripts/audio/AmbientBed.gd
145
+ extends Node
146
+ @export var stream: AudioStream
147
+ @export var fade_seconds: float = 1.5
148
+ @export var bus: StringName = &"Ambient"
149
+ @export var volume_db: float = -12.0
150
+ var _a: AudioStreamPlayer
151
+ var _b: AudioStreamPlayer
152
+ var _active: AudioStreamPlayer
153
+
154
+ func _ready() -> void:
155
+ _a = _make_player()
156
+ _b = _make_player()
157
+ _active = _a
158
+ _active.play()
159
+ _schedule_next()
160
+
161
+ func _make_player() -> AudioStreamPlayer:
162
+ var p := AudioStreamPlayer.new()
163
+ p.stream = stream
164
+ p.bus = bus
165
+ p.volume_db = volume_db
166
+ add_child(p)
167
+ return p
168
+
169
+ func _schedule_next() -> void:
170
+ var len := stream.get_length()
171
+ var t := get_tree().create_timer(len - fade_seconds)
172
+ t.timeout.connect(_crossfade)
173
+
174
+ func _crossfade() -> void:
175
+ var next := _b if _active == _a else _a
176
+ next.volume_db = -60.0
177
+ next.play()
178
+ var tw := create_tween().set_parallel(true)
179
+ tw.tween_property(next, "volume_db", volume_db, fade_seconds)
180
+ tw.tween_property(_active, "volume_db", -60.0, fade_seconds)
181
+ tw.chain().tween_callback(func() -> void: _active.stop())
182
+ _active = next
183
+ _schedule_next()
184
+ ```
185
+
186
+ This eliminates the click and adds variation through phase-overlap (the loop overlaps itself by `fade_seconds` so the bed never repeats identically).
187
+
188
+ ### 8. Wire the bed as `AudioStreamPlayer` on the Ambient bus
189
+
190
+ For most beds, non-positional is correct (the player is *in* the place; the bed isn't an emitter):
191
+
192
+ ```
193
+ summer_add_node(parentPath="/root/Game/Level", type="AudioStreamPlayer", name="AmbientBed")
194
+ summer_set_prop(path="/root/Game/Level/AmbientBed", property="stream", value="res://audio/ambient/forest_dawn.wav")
195
+ summer_set_prop(path="/root/Game/Level/AmbientBed", property="bus", value="Ambient")
196
+ summer_set_prop(path="/root/Game/Level/AmbientBed", property="volume_db", value=-12.0)
197
+ summer_set_prop(path="/root/Game/Level/AmbientBed", property="autoplay", value=true)
198
+ ```
199
+
200
+ Or attach the script in step 7 to a Node, set its `stream` to the imported audio, and skip the `AudioStreamPlayer` node — the script provides its own.
201
+
202
+ For positional beds (ambience emitting from a fountain, a campfire, a vent) use `AudioStreamPlayer3D` with attenuation. The bible's `3D attenuation: inverse-square, 25m` rule applies.
203
+
204
+ ### 9. Layer multiple beds for depth
205
+
206
+ Real places have layered ambiences. A forest at dawn might be:
207
+
208
+ - Bed A (loud): wind through leaves loop, `-12 dB`
209
+ - Bed B (medium): distant bird calls loop, `-18 dB`
210
+ - Bed C (occasional): one-shot SFX of a single bird call, fired by a Timer with random 8–20s interval
211
+
212
+ This is more expensive (3 generations) but produces a bed that doesn't sound like a 22-second loop. Reserve for hub locations or extended scenes.
213
+
214
+ ## Reference card — prompts by environment
215
+
216
+ ```
217
+ Forest dawn: forest at dawn, gentle wind, distant birds every 6s,
218
+ creaking branches, 22s loop
219
+ Forest night: forest at night, crickets, distant owl every 10s, soft
220
+ breeze, no music, 22s loop
221
+ Stone dungeon: stone dungeon, distant water drip every 4s, low rumble,
222
+ faint draft, no creatures, 22s loop
223
+ Cave: wet cave, water drip echoes, low air movement, distant
224
+ rumble, 22s loop
225
+ Spaceship: spaceship corridor, 60Hz hum, occasional electronic chirp,
226
+ ventilation whoosh, no voices, 22s loop
227
+ Marketplace: medieval marketplace at noon, distant chatter no recognisable
228
+ words, footsteps on stone, occasional horse, distant
229
+ blacksmith, 22s loop
230
+ Tavern: medieval tavern interior, low chatter, fireplace crackle,
231
+ distant lute, occasional mug clink, 22s loop
232
+ Ocean cave: ocean cave, slow waves, low rumble, water drip echoes,
233
+ no seagulls, 22s loop
234
+ Office: empty office at night, fluorescent hum, distant AC,
235
+ faint paper rustle, 22s loop
236
+ Snowy plain: snowy plain in wind, low howling wind, faint distant
237
+ branches, no music, no creatures, 22s loop
238
+ Underwater: underwater, low rumble, distant bubbles, soft mid hum,
239
+ no marine life, 22s loop
240
+ Boss arena: vast stone arena, low ominous drone, distant rumble,
241
+ occasional debris fall, no melody, 22s loop
242
+ ```
243
+
244
+ ## Anti-patterns
245
+
246
+ - **Using `summer_generate_audio({capability: 'music'})` for an ambient bed.** Music model adds melody. You don't want melody.
247
+ - **Foreground events in the bed.** A wolf howl, an explosion, a clear word — these promote themselves and the bed becomes annoying.
248
+ - **Bed louder than `-10 dB` on its bus.** Beds belong under everything; default is `-12 dB` to `-15 dB`.
249
+ - **No loop point set.** The clip plays once and silence follows.
250
+ - **Single-clip looping without crossfade.** Audible click every 22s.
251
+ - **Same bed everywhere.** Different rooms / biomes need different beds. The transition (room A → room B) is what sells the world.
252
+
253
+ ## Edge cases
254
+
255
+ - **Bed too busy on regen.** Add `sparse`, `quiet`, `low`, `distant` to the prompt.
256
+ - **Bed has musical content you didn't ask for.** Add `no music, no melody, no chords`.
257
+ - **Bed has voices when you wanted abstract.** Add `no voices, no recognisable words`.
258
+ - **Loop click after import dock setup.** Use the crossfade script (step 7).
259
+ - **Need positional bed (a campfire crackle).** Use `AudioStreamPlayer3D` with `max_distance` matching the campfire's reasonable hearing radius (4–8m typical).
260
+ - **Multi-room transitions.** Use Areas; on player enter, fade in destination bed and fade out source bed over 1.5s.
261
+
262
+ ## Fallback (no MCP)
263
+
264
+ Print the SFX prompt and the import dock settings. User runs via the Summer dashboard and imports manually.
265
+
266
+ ## Handoff
267
+
268
+ > Bed `forest_dawn.wav` wired to `Level/AmbientBed`, looping with crossfade. Next:
269
+ > - Generate the night counterpart for time-of-day transitions.
270
+ > - Add a layered occasional bird-call SFX with a randomized Timer for depth.
271
+ > - For multi-room transitions, gate beds with Area3D triggers and fade with the same crossfade pattern.
272
+
273
+ ## See also
274
+
275
+ - `audio/audio-direction` — defines the Ambient layer class
276
+ - `audio/sound-effect` — short event SFX layered over the bed
277
+ - `audio/music-track` — melodic music layered over the bed
278
+ - `audio/adaptive-music` — state-driven crossfades use the same pattern