SFX organic overhaul — design
SFX organic overhaul — design
Section titled “SFX organic overhaul — design”Date: 2026-10-12 · Owner role: dark-cosmos/audio-direction · Status: approved design, plan next
Chris (2026-10-11): “we need to do serious work on the sfx — pretty much all need to be adjusted, need to sound more organic and less annoying.”
1. Why the current SFX are annoying (measured, not guessed)
Section titled “1. Why the current SFX are annoying (measured, not guessed)”Profiled every audio/*.wav (spectral centroid, 2–6 kHz energy share, RMS, crest factor, attack
time, spectral flatness):
- 11 live files are raw synthesized test tones from the 2026-06-25 placeholder pass:
level_up,ui_tap,wave_start,wave_clear,victory,defeat,special_frost(Chain — fires constantly),special_freeze,special_rally,special_aegis,special_stomp. A 12th,attack.wav, andenemy_hit.wavare orphans (nothing loads them); they are deleted in the cleanup, not replaced. Crest factor 0.7–7.9 dB (near-pure square/sine), RMS around −6 dB — the loudest files in the game. This is the beepy chiptune fatigue. - No loudness normalisation: RMS ranges −5.6 dB (
victory) to −59.4 dB (funzo_confetti), a 54 dB spread.enemy_death_heavy_1(−50 dB) andfunzo_confettiare close to inaudible. - Bright high-frequency content on the most frequent sounds:
enemy_death_heavy_*centroid 5.2–8.4 kHz, 33–43% of energy in the 2–6 kHz band (the ear’s fatigue band). - No shared acoustic space: the SFX bus is dry, with no EQ, saturation or reverb. Every sound sits separately, which reads as synthetic.
2. Direction
Section titled “2. Direction”Physical + warm synth. Each sound is built on a real-material layer (glass, air, water, wood,
metal resonance) with a soft filtered-synth tail for the neon feel, rounded transients, all in one
shared reverb space. The reference points are Nova Drift and Hades. The SFX must carve space around
the psytrance music set (branch psy-music, 14 area/boss tracks), which is mid-dense.
3. Phase 0 — mix foundation (no new assets)
Section titled “3. Phase 0 — mix foundation (no new assets)”Loudness tiers. Each file is normalised with ffmpeg loudnorm, baked into the file, so in-code
dB trims trend back to around 0. The targets are starting points, retuned by ear:
| Tier | Events | Target |
|---|---|---|
| Frequent | hits, deaths, pickups, auto-weapon fire | −24 LUFS |
| UI | menu taps, card select | −22 LUFS |
| Telegraph | enemy telegraphs + attacks | −20 LUFS |
| Player action | Rift Bolt, warp, level-up | −18 LUFS |
| Boss / stinger | boss cues, victory/defeat, wave cues | −16 LUFS |
SFX bus chain (each stage a registered Remote Console tunable, live mid-run):
- EQ: high shelf around 4 kHz at −3 dB, plus a low cut around 60 Hz (leaves the sub to the music’s kick).
- Soft-clip
AudioEffectDistortion, low drive — for rounding, not crunch. - Space: a short (~0.6 s), dark (heavy damping)
AudioEffectReverbinserted on the SFX bus, dry 1.0, wet around −14 dB. It’s an insert rather than a send because a Godot bus routes to exactly one parent and has no aux sends. The UI bus stays dry. - The existing Master
AudioEffectHardLimiterstays as the safety net.
Boss low end (2026-10-12). The low cut no longer sits on the SFX bus: it lives on its own
SfxMain bus (built by SfxMix.install()), which every non-boss SFX plays on and which sends to
SFX. Boss-tier keys (audio/sfx_tiers.json “boss”) play on Stinger, which sends straight to SFX,
so they bypass the low cut but still get the shelf, soft clip, Space and the SFX bus volume/mute.
LOW_CUT_HZ therefore applies to non-boss SFX only. Chris: “boss sounds should stand out by
having deep bassy elements”; ordinary SFX were “too boomy”.
Music ducking. Boss cues and stingers route through a Stinger bus that sidechain-keys an
AudioEffectCompressor on the Music bus. Ordinary SFX do not duck the music (that would pump
constantly).
Budget. Audio cost under 0.3 ms per audio window on ATV, measured with the existing per-window audio-cost telemetry. Bus effects do not run on web (Web Audio sample playback), so web gets the loudness normalisation and the new assets but none of the bus chain. That is accepted, not a bug.
4. Sourcing recipe (each replacement)
Section titled “4. Sourcing recipe (each replacement)”Prompt template: <material layer>, <motion/shape>, <warm synth tail>, <constraints>. The
constraints are always: “soft rounded attack, no harsh high frequencies, dry, no reverb, single hit,
no music”. The takes are dry because the Space bus supplies the room. Example (enemy death):
small glass bead shattering softly into dust, short airy exhale, faint warm analog synth sigh underneath, soft attack, dry, no harsh highs
Material vocabulary per family, so the roster feels related:
| Family | Materials |
|---|---|
| Enemies | glass, crystal, brittle mineral |
| Player weapons | air, plasma breath, taut string / metal resonance |
| Bosses | deep stone, large metal, low organic groan |
| UI | soft wood / felt taps, glass chime |
| Stingers | warm pad or bell swell — never a raw waveform |
Low end is reserved for bosses (2026-10-12). Boss takes carry a deep sub-bass layer plus an audible low-mid body (80–250 Hz), so they still read as heavy on TV and phone speakers that can’t reproduce the sub. Non-boss SFX keep a tight low end (prompt for it, and SfxMain’s low cut trims the rest), so the bottom of the mix belongs to the bosses.
Generation. The ElevenLabs sound-generation REST API (duration_seconds ≥ 0.5) produces 4
takes per event. Each is converted to mono 44.1 kHz WAV with a short fade-out and lands in
audio/candidates/<event>/, which the Sound Map tool already reads.
Metric pre-filter (tools/sound_map/profile.py, promoted from the 2026-10-11 profiling
script). A take is rejected before Chris hears it if any of these hold:
- 2–6 kHz energy share > 20%
- spectral centroid > 3.5 kHz (frequent tier) / > 5 kHz (others)
- crest factor < 8 dB (tone-like or over-compressed)
- attack lands in the first 2 ms (frequent tier only — a click)
Passing takes are loudness-normalised to their tier, so the comparison is like for like. If fewer than 2 takes pass, the prompt is revised and regenerated before Chris hears anything.
Round-robin variants. Sounds that fire several times per second (light and heavy deaths,
blaster_fire, turret_impact, melee_swish, enemy_ranged_fire; pickups are dead in store play
under AUTO_COLLECT_XP) get 3 variants played in a no-immediate-repeat rotation, on top of the
existing pitch/volume jitter. In AudioManager a key can map to [key_1 .. key_n]. The death
sounds already do this informally.
5. Batches
Section titled “5. Batches”Order is by real play frequency. Telemetry only carries aggregate play counts (aud_plays), not
per-sound ones, so a headless tool (tools/sfx_freq/) runs a skilled-autopilot survival soak through
the real AudioManager routing and counts plays per sound key. The expected shape:
- Hits, deaths, pickups, blaster, Rift Bolt (+ round-robin)
- Auto-weapons, including the 5 that borrow another cue’s asset (railgun lance, chalice, thunder wake, wildfire, stasis web)
- The 12 synth-tone UI/stinger sounds
- Enemy telegraphs + attacks
- Bosses
The exact event list per batch is fixed in the implementation plan from the telemetry query.
6. A/B, review flow, delivery
Section titled “6. A/B, review flow, delivery”Bank toggle. New sounds are written to audio/sfx_v2/<key>.wav, never over the v1 file.
AudioManager resolves v2 when the registered tunable AudioManager.SFX_BANK == 2 (default) and
falls back to v1 when no v2 file exists. It’s flipped live mid-run through the console’s existing
set_tunables, with no new console command. Phase 0 seeds sfx_v2/ with tier-normalised copies of
every v1 file, so the bank toggle A/Bs Phase 0 itself; each batch then overwrites its keys’ v2
files. The three mastering-compensation trims (OVERCHARGE_SFX_DB, AEGIS_SFX_DB, WAVE_CLEAR_DB,
which exist only because those files were mastered hot) are ignored under bank 2. The v1 files are
deleted only after the last batch is approved.
Per-batch loop:
- Generate → filter → normalise → load into the Sound Map tool.
- Chris picks a winner per event (or “none, regenerate” with a note).
- Winners are wired into
sfx_v2/on a batch branch in its own worktree, thenscripts/gate.shand abh-reviewerpass. - Dev install on ATV and/or iPhone. Chris plays with the console bank toggle and gives a verdict: merge / retune levels (console tunables, no rebuild) / reject specific events back to step 1.
- Merge, push.
Phase 0 ships the same way (branch, Dev install, console toggles, merge).
Tracking. audio/sound_map.json gains a per-event v2 field: pending / candidates /
picked / shipped. The Sound Map tool shows it. No separate tracker.
Tests:
- bank resolution + v1 fallback
- round-robin never repeats back-to-back
- every event marked
shippedhas a v2 file that passes the profile thresholds and sits within ±1.5 LU of its tier (runs in the gate) - bus layout pinned (buses present, effect order, tunables registered)
7. Risks
Section titled “7. Risks”- The AI takes may not reach “organic”. ElevenLabs is strong at foley textures and weaker at controlled synth tails. If batch 1 fails Chris’s ear, stop and switch the stingers/UI to hand-layered sounds (a foley take plus a filtered synth tail mixed in ffmpeg) rather than pushing through all 5 batches.
- Web CPU/memory. The v2 bank roughly doubles SFX on disk (about 3 MB of PCM) until v1 is deleted, which is acceptable. The reverb cost on web is measured, and the Space bus can be bypassed.
- Ducking vs the psytrance set (merged, c0e2aabb): tune the Music compressor against those tracks on device, not against the legacy loop.
- Determinism: none. Everything here is render-side;
/simis untouched.
8. Non-goals
Section titled “8. Non-goals”Music changes; new sound events (the silent rows); splitting shared fx kinds
(the 8 flagged_source_shared rows — gameplay-programming follow-up); voice/dialogue.
9. Done means
Section titled “9. Done means”- Every
done/genericevent has a v2 sound Chris picked. - No file sits outside its loudness tier.
- No synth-tone files remain, and the orphan files are deleted.
- The v1 bank is deleted.
- Audio cost is within budget on ATV.
- Chris can play a 15-minute run without wanting to mute the SFX.