Sound Map fight simulator (brief, 2026-10-16)
Sound Map fight simulator: brief
Section titled “Sound Map fight simulator: brief”Chris approved this on 2026-10-16: “create a game/sound simulator in the sound tool, it launches the sounds in a way that mirrors how they would happen in the game”. The goal is to judge candidate SFX inside a realistic fight mix without a device, and to let Claude measure the mix, which is the closest Claude gets to hearing it.
Build (implementer agent in its own worktree, branched off sfx-synth-all)
Section titled “Build (implementer agent in its own worktree, branched off sfx-synth-all)”- Record. Extend
tools/sfx_freq/sfx_freq.gd, which runs headless autopilot through the realAudioManagerrouting. Add a--timeline=<out.json>option that writes everyplay()/ UI play as{t, key, file_variant, volume_db, pitch, priority, bus, pos?}in sim time, plus the main.gd one-shots it already mirrors. Check that itsCountingAudio.playoverride still matchesAudioManager.play’s current signature, which has gained abusparam. Presets: early game (mortal), a busy wave about 8 min in (--invuln), and a Warden, Graviton and FunZo fight (--force_boss). Commit the small preset timelines undertools/sound_map/timelines/. - Replay. Add a “Fight” panel to the Sound Map (
tools/sound_map/src) that plays a timeline with WebAudio:- a 16-voice cap with the game’s steal rule (
AudioManagerPOOL_SIZE 16, boss reserve, priority); - an approximate SfxMix chain: low cut 80 Hz on non-boss, Space reverb wet −32 dB, Stinger ducking Music;
- optional music bed. Per key, the panel swaps in any candidate: either a “use this take in the fight” toggle on a take row, or “all assigned/picked”. Add transport (play/stop/seek), a solo for one key, and a voice-count meter.
- a 16-voice cap with the game’s steal rule (
- Measure. Add an offline Python mixdown of the same timeline (
tools/sound_map/fight_mix.py) that writes a WAV plus metrics: voices in use over time, dropped plays, low-mid (180-410 Hz) energy, and duck time. This is the 10-14 mud diagnosis, automated. - Add node and py tests. Browser-verify with chrome-devtools on a spare port: 8080 and 8081 are in use, never kill them.
Caveats to state in the UI
Section titled “Caveats to state in the UI”The mix chain approximates the game’s, and laptop speakers are not a TV or phone, so picks still get one real ATV run before shipping.
How to use it (built 2026-10-16)
Section titled “How to use it (built 2026-10-16)”In the Sound Map (python3 tools/sound_map/server.py, then the Fight button top right):
- Pick a preset (
early,busy8,warden,warden_end,graviton,funzo; 90 s each). ▶ plays the fight through a WebAudio copy of the game’s 16-voice pool and SfxMix chain; click the track to seek. The meter shows voices in use (the violet three are the boss reserve), the sparkline the whole fight’s voice count. - Swap takes in. Swaps go into the game’s round-robin slots (
<key>,<key>_2, …) the way Assign does; slots nobody swaps keep the game’s file. On a take row, Fight puts the shown take in the slot its slot selector points at, and Fight ×N puts the whole take family (v1, v2, …) in slots 1..N. In the drawer: Use families (per key, the first take family whose root matches, defaultsynth_.*), Use v1 only (that family’s v1 in slot 1, the game’s other slots still play), Use all assigned, Clear. When a swap changes a key’s slot count, the replay re-runs the game’s round-robin pick (uniform, never the same slot twice running) on a fixed per-key random stream; otherwise it keeps the game’s recorded picks. - Solo one key (the pool still runs with every sound, so cut-offs stay real); Music adds the area / boss track, ducked by the Stinger bus (approximately).
- Analyze runs
fight_mix.pyon the server for the current swap set and draws each check as a lane; click a mark (or a row of the warning list) to jump there. Compare puts live sounds vs the swap set side by side, metric by metric. “What the checks approximate” lists every simplification and threshold. Loudness is measured after the game’s master hard limiter (audio/bus_layout.tres), so the squash measure is the limiter’s gain reduction (max, time over 1/3/6 dB), not a clipping peak.
From the shell:
bash scripts/record-fight-timelines.sh # re-record every preset (tools/sound_map/timelines/)python3 tools/sound_map/fight_mix.py graviton # checks on the live soundspython3 tools/sound_map/fight_mix.py graviton --swap-family 'synth_.*' --compare # every synth family into the slotspython3 tools/sound_map/fight_mix.py graviton --swap-pattern 'synth_.*_v1' --compare # v1 into slot 1 onlypython3 tools/sound_map/fight_mix.py graviton --swap pulse_zap_2=synth_pulse_zap_v3 # one slotpython3 tools/sound_map/fight_mix.py warden --swap-picked --out /tmp/warden.wav --report /tmp/r.json --musicThe mix chain approximates the game’s, and laptop speakers are not a TV or phone, so picks still get one real ATV run before shipping.