Files
Cozypaw-Hospital/docs/superpowers/specs/2026-05-10-sprint-22-character-ambient-sfx.md
Steven Wroblewski 52ebb78862 chore(audio): add download script, audio credits, and sprint 21/22 docs
- docs/download_audio.py: freesound batch downloader with all 22 confirmed IDs
  (API key removed — fill in locally from freesound.org)
- docs/credits-audio.md: generated CC-BY attribution table
- docs/superpowers/plans+specs: sprint 15, 21, 22 implementation plan/spec docs
- .claude/settings.json: enable experimental agent teams env var

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 14:57:27 +02:00

71 lines
3.3 KiB
Markdown

# Sprint 22 — Character & Ambient SFX Design Spec
## Goal
Two deferred SFX items from Sprint 21:
1. **UltrasoundMachine ambient heartbeat** — continuous looping audio that starts when the player enters the ultrasound room and stops when they leave.
2. **Character SFX** — pickup, place, and tap sounds wired into `character.gd`.
## New SFX Events
### AudioManager._SFX_MAP additions (3 new keys)
| Event key | Trigger |
|---|---|
| `character_pickup` | `Character._on_drag_picked_up()` |
| `character_place` | `Character._on_drag_released()` — drag distance ≥ tap threshold |
| `character_tap` | `Character._on_drag_released()` — drag distance < tap threshold |
### UltrasoundMachine (self-managed, not via _SFX_MAP)
The heartbeat is a looping ambient sound owned by the `UltrasoundMachine` node itself. It does not go through `AudioManager.play_sfx()` — that path is for one-shot SFX. Instead, `UltrasoundMachine` creates its own `AudioStreamPlayer` child, sets `stream.loop = true` at runtime, and starts/stops it in response to `RoomNavigator.room_changed`.
## Asset Specification
New files:
```
assets/audio/sfx/ultrasound_heartbeat.ogg — soft beep/blip, ~1s, loops seamlessly
assets/audio/sfx/character_pickup.ogg — happy soft squeak / whoosh
assets/audio/sfx/character_place.ogg — gentle thud / landing
assets/audio/sfx/character_tap.ogg — short happy chime / pop
```
All CC0 or CC-BY from freesound.org. Placeholder 0-byte files committed; real downloads documented in `docs/audio-assets-sprint19.md` (extend existing file).
## Integration Points
### UltrasoundMachine (`scripts/objects/ultrasound_machine.gd`)
Full replacement. New behaviour:
- `@export var trigger_floor: int = 2` and `@export var trigger_room: int = 0` (matches ultrasound room position in `_ROOM_NAMES`)
- In `_ready()`: create `AudioStreamPlayer` child, load stream with `loop = true`, connect to `RoomNavigator.room_changed`
- In `_on_room_changed(floor_index, room_index)`: if matches trigger position → `_audio.play()`, else → `_audio.stop()`
- `AudioServer.get_driver_name() == "Dummy"` guard wraps all audio operations
- `_exit_tree()`: disconnect signal (mirrors Ambulance pattern)
- Volume is inherited from the bus (no explicit volume set — ambient heartbeat is soft by design)
### Character (`scripts/characters/character.gd`)
Three one-liner additions:
- `_on_drag_picked_up()``AudioManager.play_sfx("character_pickup")` as first line
- `_on_drag_released()` tap branch → `AudioManager.play_sfx("character_tap")` before `_handle_outfit_tap()`
- `_on_drag_released()` place branch → `AudioManager.play_sfx("character_place")` before `character_placed.emit()`
## Testing
Append to `test/unit/test_audio_manager.gd`:
- `test_sfx_map_has_all_character_keys` — verifies `character_pickup`, `character_place`, `character_tap` exist in `_SFX_MAP`
No unit test for UltrasoundMachine audio start/stop — the trigger is room-navigation-driven and mirrors the Ambulance pattern (which also has no per-SFX unit test).
## Out of Scope
- Per-state-transition character sounds (e.g., happy sound when healed — separate sprint)
- Room-specific ambient audio for other rooms
- UltrasoundMachine volume linked to `GameState.sfx_volume` (ambient bus handles this via AudioServer)