feat: SH-052 - the living soundscape: river, wind loops, creature locomotion#37
Conversation
…omotion Built on EN-062 live spatial voices: - River: one looping emitter per water volume, tracking the closest bank point to the player every frame. World-file-driven. - Wind: the three forest sources are true detuned loops now (retrigger timer deleted); volume still rides the live wind amplitude. - Creature locomotion: every kind runs the tyrant's stride accumulator (KIND_STRIDE) and fires positional chitin skitters; a pool of 4 looping crawl-bed voices follows the nearest moving enemies so the thing you haven't seen is audible closing in between its steps. - 10 new synthesised stand-in assets (gen-sfx.ts, appended after existing emits so old files stay byte-identical). ASSET-TODO A7-A9. Verified via AITEST probe build: 351 skitters over 50s of combat, crawl slots assign/release correctly, no errors. Probe reverted. Claude-Session: https://claude.ai/code/session_015wJLdkwm71jQCAqypaFeNE
📝 WalkthroughWalkthroughThe change implements the SH-052 living soundscape with generated wind, skitter, and crawl assets; recorded river-loop conversion; runtime river and creature ambience; generalized enemy locomotion audio; and an ChangesLiving soundscape
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant MainLoop
participant Director
participant AudioMix
participant AudioEngine
participant EnemyState
MainLoop->>AudioMix: Update river and creature ambience
EnemyState-->>Director: Moving enemy and stride state
Director->>AudioMix: Trigger tyrant step or skitterStep
AudioMix->>AudioEngine: Update emitters and play locomotion sounds
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…idth) 'The water sounds fake' - it did; synthetic babble always does. river_loop is now a 32s seamless cut of a real Vermont babbling brook (Bolt Immersive Creek, Sonniss GDC 2024 - same license line as splash1.wav): - convert-audio.ts grew startAt + loopFade (equal-power tail-over-head cross-fade, chunk-aware WAV patching). The window at 49s was picked by scanning for minimum RMS+brightness variance; the seam verified numerically (wrap delta 0.034 vs body max 0.31, no clipping). - Each water volume now runs a detuned emitter TRIO (main at the closest bank point + flankers +/-8m up/downstream at 0.96/1.05) so the water has audible extent instead of reading as a point speaker. - gen-sfx.ts no longer emits a river (it would clobber the recording); the seed-stream shift regenerated skitter*/crawl_loop (equivalent variants, committed here). Claude-Session: https://claude.ai/code/session_015wJLdkwm71jQCAqypaFeNE
|
SH-052b pushed: the river is a real creek recording now. |
|
SH-052c pushed — the remaining "white noise" was the wind bed, two causes: (1) the synth asset was a fixed bandpass × volume LFO — a static spectrum is what the ear files under white noise; (2) SH-052 had removed the authored 40 m window, so the engine's long rolloff tail spread that hiss over the whole arena. Fixed both: the generator now moves the spectrum with the gust (dark bough murmur in lulls → bright leaf-sizzle in gusts + flutter grains, measured brightness-vs-level r=0.95 across the loop, was ~0) and the game re-applies the authored window (full <27 m of the treeline, silent past 45 m). Open field is quiet again; near the trees it breathes instead of hissing. Real canopy recording still tracked as ASSET-TODO A3. |
Two causes: the synth wind was a FIXED bandpass x volume LFO (a static spectrum is what the ear calls white noise), and SH-052 had dropped the authored 40m window so the engine's long inverse-model tail spread that hiss over the whole arena. - gen-sfx wind v2: spectrum rides the gust (bough murmur in lulls -> leaf sizzle in gusts, brightness ~ gust^1.8, measured r=0.95), leaf flutter grains only in strong gusts, slow seeded wander, 24s loop. Seam verified (delta 0.23 vs body max 0.53), no clipping. - updateWindAmbience: authored window restored on top of engine rolloff (full <27m, silent >45m). Engine keeps pan/absorption/close-range. Skitter/crawl regenerated (shared seed stream, same contract as before). Claude-Session: https://claude.ai/code/session_015wJLdkwm71jQCAqypaFeNE
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@assets/sounds/SOURCES.md`:
- Around line 58-67: Update the synthesized-asset listing in SOURCES.md to
remove river_loop.wav from that section, preserving its recorded-loop
classification and 32-second duration from the existing source definitions.
Correct ambient_wind.wav’s listed duration from 16 seconds to 24 seconds,
leaving the other asset entries unchanged.
In `@src/director.ts`:
- Around line 779-780: Base movement audio on post-resolution displacement: in
src/director.ts lines 779-780, update the enStepPhase accumulation to use the
final realized nx/nz displacement rather than requested vx/vz; in
src/audio-mix.ts lines 465-470, use the corrected realized velocity for crawl
eligibility and loudness.
In `@src/main.ts`:
- Around line 1993-1994: Update the updateCreatureLoops method and its call site
in the gameplay loop to accept the current playing state. When inactive, clear
all pooled crawl-voice assignments and volumes, and skip movement-based updates;
preserve the existing behavior while playing.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: bede531b-93d6-42b7-b24e-9bbaba621251
⛔ Files ignored due to path filters (11)
assets/sounds/ambient_wind.wavis excluded by!**/*.wavassets/sounds/crawl_loop.wavis excluded by!**/*.wavassets/sounds/river_loop.wavis excluded by!**/*.wavassets/sounds/skitter_heavy1.wavis excluded by!**/*.wavassets/sounds/skitter_heavy2.wavis excluded by!**/*.wavassets/sounds/skitter_heavy3.wavis excluded by!**/*.wavassets/sounds/skitter_heavy4.wavis excluded by!**/*.wavassets/sounds/skitter_light1.wavis excluded by!**/*.wavassets/sounds/skitter_light2.wavis excluded by!**/*.wavassets/sounds/skitter_light3.wavis excluded by!**/*.wavassets/sounds/skitter_light4.wavis excluded by!**/*.wav
📒 Files selected for processing (10)
CLAUDE.mdassets/sounds/SOURCES.mddocs/ASSET-TODO.mddocs/tickets.mdsrc/audio-mix.tssrc/director.tssrc/enemies.tssrc/main.tstools/convert-audio.tstools/gen-sfx.ts
| Deterministic (seeded), so re-running the tool reproduces byte-identical | ||
| files. See docs/ASSET-TODO.md A1/A2/A3/A5/A6/A7/A8/A9 for the intended real | ||
| replacements. | ||
|
|
||
| `step_grass{1-4}.wav`, `step_dirt{1-4}.wav`, `step_heavy1.wav`, | ||
| `reload_start.wav`, `reload_end.wav`, `ui_move.wav`, `ui_select.wav`, | ||
| `{rifle,blaster,chain,cannon}_tail.wav`, `ambient_wind.wav` (16 s seamless | ||
| loop), and the SH-052 set: `river_loop.wav` (14 s seamless), | ||
| `skitter_light{1-4}.wav`, `skitter_heavy{1-4}.wav`, `crawl_loop.wav` | ||
| (3.5 s seamless). |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Correct the synthesized-asset provenance and durations.
This section incorrectly labels river_loop.wav as a 14-second synthesized asset despite Line 49 and tools/convert-audio.ts defining it as a 32-second recorded loop. It also lists the rebuilt wind loop as 16 seconds instead of 24 seconds. This makes the licensing/provenance record internally inconsistent.
Proposed documentation fix
-`{rifle,blaster,chain,cannon}_tail.wav`, `ambient_wind.wav` (16 s seamless
-loop), and the SH-052 set: `river_loop.wav` (14 s seamless),
-`skitter_light{1-4}.wav`, `skitter_heavy{1-4}.wav`, `crawl_loop.wav`
-(3.5 s seamless).
+`{rifle,blaster,chain,cannon}_tail.wav`, `ambient_wind.wav` (24 s seamless
+loop), and the SH-052 synthesized set: `skitter_light{1-4}.wav`,
+`skitter_heavy{1-4}.wav`, `crawl_loop.wav` (3.5 s seamless).📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Deterministic (seeded), so re-running the tool reproduces byte-identical | |
| files. See docs/ASSET-TODO.md A1/A2/A3/A5/A6/A7/A8/A9 for the intended real | |
| replacements. | |
| `step_grass{1-4}.wav`, `step_dirt{1-4}.wav`, `step_heavy1.wav`, | |
| `reload_start.wav`, `reload_end.wav`, `ui_move.wav`, `ui_select.wav`, | |
| `{rifle,blaster,chain,cannon}_tail.wav`, `ambient_wind.wav` (16 s seamless | |
| loop), and the SH-052 set: `river_loop.wav` (14 s seamless), | |
| `skitter_light{1-4}.wav`, `skitter_heavy{1-4}.wav`, `crawl_loop.wav` | |
| (3.5 s seamless). | |
| Deterministic (seeded), so re-running the tool reproduces byte-identical | |
| files. See docs/ASSET-TODO.md A1/A2/A3/A5/A6/A7/A8/A9 for the intended real | |
| replacements. | |
| `step_grass{1-4}.wav`, `step_dirt{1-4}.wav`, `step_heavy1.wav`, | |
| `reload_start.wav`, `reload_end.wav`, `ui_move.wav`, `ui_select.wav`, | |
| `{rifle,blaster,chain,cannon}_tail.wav`, `ambient_wind.wav` (24 s seamless | |
| loop), and the SH-052 synthesized set: `skitter_light{1-4}.wav`, | |
| `skitter_heavy{1-4}.wav`, `crawl_loop.wav` (3.5 s seamless). |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@assets/sounds/SOURCES.md` around lines 58 - 67, Update the synthesized-asset
listing in SOURCES.md to remove river_loop.wav from that section, preserving its
recorded-loop classification and 32-second duration from the existing source
definitions. Correct ambient_wind.wav’s listed duration from 16 seconds to 24
seconds, leaving the other asset entries unchanged.
| const spd = Math.sqrt(vx * vx + vz * vz); | ||
| enStepPhase[i] = enStepPhase[i] + spd * dt; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Base movement audio on post-resolution displacement.
Both systems treat requested steering velocity as realized movement, although collision and navigation resolution may cancel or redirect it.
src/director.ts#L779-L780: accumulateenStepPhasefrom the finalnx/nzdisplacement.src/audio-mix.ts#L465-L470: use the corrected realized velocity for crawl eligibility and loudness.
📍 Affects 2 files
src/director.ts#L779-L780(this comment)src/audio-mix.ts#L465-L470
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/director.ts` around lines 779 - 780, Base movement audio on
post-resolution displacement: in src/director.ts lines 779-780, update the
enStepPhase accumulation to use the final realized nx/nz displacement rather
than requested vx/vz; in src/audio-mix.ts lines 465-470, use the corrected
realized velocity for crawl eligibility and loudness.
| MIX.updateRiverAmbience(pp.x, pp.z); | ||
| MIX.updateCreatureLoops(pp.x, pp.z); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Mute pooled crawl voices when gameplay stops.
When playing becomes false, enemy movement state is no longer updated, while persistent crawl voices can retain living enemies and stale velocity. Pass the active state into updateCreatureLoops and clear all assignments and volumes when inactive.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/main.ts` around lines 1993 - 1994, Update the updateCreatureLoops method
and its call site in the gameplay loop to accept the current playing state. When
inactive, clear all pooled crawl-voice assignments and volumes, and skip
movement-based updates; preserve the existing behavior while playing.
…the 3D Wind: two rebuilds could not make a spectral-only always-on synth bed stop reading as white noise, so it is LEFT OUT - sfxWind=NO_SOUND, every wind path early-returns as for a missing file. System stays live and dormant; one line re-enables it when a real canopy recording lands (ASSET-TODO A3). River/footsteps/creatures/reverb untouched. --audiotest orbit|flyby|river: a deterministic way to HEAR the spatialisation instead of inferring it from combat. - orbit: a broadband voice circles the listener at 6m (pan + rear cue) - flyby: it flies through you at ~14 m/s (doppler + front/back) - river: free-walk, river-only, dist-to-bank on the HUD Waves suppressed in all modes; orbit/flyby silence the river so the probe is the single source; river mode skips the probe. Top-centre HUD readout. Dormant unless --audiotest is passed. Verified: voice live, orbit angle advancing, all modes + normal path boot clean. Claude-Session: https://claude.ai/code/session_015wJLdkwm71jQCAqypaFeNE
|
SH-052d pushed — two things: Wind is left out. Two rebuilds couldn't stop a spectral-only, always-on synth bed from reading as white noise (you heard it both times — that's the ground truth). So
Waves off in all modes; orbit/flyby mute the river so the probe is the single source; river mode skips the probe. Top-centre HUD readout. Dormant unless the flag is passed. |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/tickets.md (1)
1721-1747: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winMark the superseded SH-052 claims as historical.
The earlier SH-052 summary still says
river_loopis synthesized and reports active wind voices, while these follow-ups establish that the river is now recorded and wind is dormant. Label the original bullets as “as initially shipped” or update them so the ticket has one unambiguous current-state description.Also applies to: 1777-1805
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/tickets.md` around lines 1721 - 1747, Update the superseded SH-052 sections in docs/tickets.md, including the related content around the second referenced range, to clearly label claims about synthesized river audio and active wind voices as “as initially shipped” or revise them to the current recorded-river and dormant-wind state. Ensure the ticket presents one unambiguous current-state description while preserving historical details where appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/ASSET-TODO.md`:
- Line 27: Update the status legend in docs/ASSET-TODO.md to define 🔴 as the
status used for intentionally left-out or unavailable assets, matching the
meaning assigned to A3. Keep the existing 🔵, 🟡, and ✅ definitions unchanged.
---
Outside diff comments:
In `@docs/tickets.md`:
- Around line 1721-1747: Update the superseded SH-052 sections in
docs/tickets.md, including the related content around the second referenced
range, to clearly label claims about synthesized river audio and active wind
voices as “as initially shipped” or revise them to the current recorded-river
and dormant-wind state. Ensure the ticket presents one unambiguous current-state
description while preserving historical details where appropriate.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 00d9b1a6-5936-4116-b439-7b0909e7e9ca
📒 Files selected for processing (4)
docs/ASSET-TODO.mddocs/tickets.mdsrc/audio-mix.tssrc/main.ts
| | A1 | Footstep SFX — grass ×4, dirt ×4, heavy ×1 | 🟡 | **Synthesised stand-ins shipped** (`bun tools/gen-sfx.ts`). A step is an *impact* plus a *scuff*: grass is nearly all scuff (bright, grainy), dirt is mostly impact (low thud + grit), the tyrant footfall is a 48 Hz body with debris on top. They are audible and they sit right in the mix — but they are synthesis, not recordings. Replace with the Sonniss GDC bundle (a documented source in `SOURCES.md`) as a straight file drop: same names, no code change. | | ||
| | A2 | Weapon tail layers — rifle, blaster, chaingun, cannon | 🟡 | **Synthesised stand-ins shipped.** The "BOOM" half of crack-BOOM: band-limited decaying noise + a low body, routed to the reverb send by the mixer. Cannon runs 2.4 s and is still audible when the next shot lands. Real recordings would be better; the *system* is no longer silent. | | ||
| | A3 | Wind / leaf-rustle ambience loop | 🟡 | **Synthesised stand-in shipped** (16 s, seamless — the tail is cross-faded over the head). SH-001 is now wired: three 3D sources placed on the forest's own centroids (computed from the world file, so moving the trees in the editor moves the sound), volume from distance **and from the live wind amplitude**, so the rustle swells with the same vector that bends the grass. | | ||
| | A3 | Wind / leaf-rustle ambience loop | 🔴 | **LEFT OUT for now (SH-052d).** Two rebuilds (SH-052c moved the spectrum with the gust, r=0.95) still could not make synthesis pass as a spectral-only, always-on ambience — the user heard white noise, and they were right. The whole wind SYSTEM stays live and DORMANT: `audio-mix.ts` forces `sfxWind = NO_SOUND`, so every wind path early-returns exactly as for a missing file; the three world-driven emitters, the wind-amplitude coupling and the 45 m window are all intact. Re-enable in ONE line (restore the `optional('assets/sounds/ambient_wind.wav', …)` call) the moment a real recording lands — steady broadleaf canopy, no birds, no mic buffet, cut to a seamless loop with convert-audio's `loopFade`. The synth asset + generator are kept for that A/B. Sonniss 1of9 has no usable wind; check 2–9 or a CC0 source. | |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Define the new 🔴 status in the legend.
The legend only defines 🔵, 🟡, and ✅, so readers cannot determine what the newly introduced red A3 status means. Add 🔴 to the legend or use an existing status with equivalent semantics.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/ASSET-TODO.md` at line 27, Update the status legend in
docs/ASSET-TODO.md to define 🔴 as the status used for intentionally left-out or
unavailable assets, matching the meaning assigned to A3. Keep the existing 🔵,
🟡, and ✅ definitions unchanged.
What
"We want 3D sounds for the wind, the river, the animals/spiders crawling closer" — built on engine EN-062 (Bloom-Engine/engine#116, merge that first).
KIND_STRIDE) and fires positional chitin skitters (light/heavy banks by kind), gated on actually moving;gen-sfx.ts(river bed+babble, skitter tap-bursts, granular crawl loop), appended after the existing emits so every old file stays byte-identical. ASSET-TODO A7–A9 track real recordings.Engine-side (from EN-062, free here): equal-power pan, air absorption, rear head-shadow, doppler — and the stereo field is no longer mirrored.
Testing
AITEST probe build (numeric counters, not ears): 351 skitter steps over 50 s of live combat; crawl slots assign/release as enemies move and park (a dretch standing still at 1.5 m goes silent — the moving gate); wind voices 5/6/7, river voice 8; zero errors, no command-ring pressure. Probe reverted; shipped-config build boots clean for 15 s.
https://claude.ai/code/session_015wJLdkwm71jQCAqypaFeNE
Summary by CodeRabbit
New Features
Bug Fixes
Documentation