Skip to content

Sound Phase 2 — Weapon Fire Sounds

Date: 2026-07-01 Status: Design — approved, ready for planning Goal: Give every one of the 6 base weapons a distinct fire sound, sourced from a licensed sample pack, per the sound-effects overhaul’s Phase 2 (see audio/SOUND_MAP.md and the 5-phase plan in docs/superpowers/specs/2026-07-01-sound-effects-audit-plan-design.md).

Phase 1 (bus layout + bug fixes) shipped 2026-07-01. Per SOUND_MAP.md, every weapon fire event is currently SILENT, and Orbit/Turret/Scatter don’t even emit an fx_events kind at the moment of firing (their projectiles/shards ARE already visible via existing renderers — the gap is specifically the firing-moment cue, audio and, for Turret/Scatter, a muzzle accent).

Chris downloaded the free 99Sounds Sci-Fi Sound Effects bundle (two Rescopic Sound libraries: “Parallax” and “Sci-Fi Energy Weapons”), confirmed under a royalty-free commercial license that explicitly covers “computer/video games” (no attribution required beyond not claiming authorship of the raw files).

Seven source files were selected from the ~140 available, trimmed to short punchy one-shots (mono, 44.1kHz, 16-bit — matching every existing sound in the game) using ffmpeg, with cut points chosen from measured RMS envelopes (not guessed timestamps — two earlier guessed trims were audibly wrong and had to be redone once the envelope was actually measured). Final files, staged at /private/tmp/.../scratchpad/audition/ pending copy into audio/:

File (→ audio/ name) Weapon Sound type Source
pulse_zap.wav Pulse zap-cast Parallax “Zap Short 02”
melee_swish.wav Melee/Blade whoosh-cast Parallax “Swish Crisp Large 01”
nova_whoosh.wav Nova whoosh-cast Energy Weapons “Whoosh Energy 06”
beam_zap.wav Beam zap-cast Energy Weapons “Shot Plasma PP 01”
turret_impact.wav Turret impact Parallax “Hit Small Tight 02”
scatter_explosion.wav Scatter explosion Parallax “Blast 03”
orbit_ambience.wav Orbit drone-ambient Parallax “Ambience Low 02” — 10s loop-source segment from a stable (non-fading) region of the original 36s ambience; the exact seamless loop point is set via Godot’s audio-import loop settings at implementation time, not baked into the file

Remaining ~130 files in the pack are not used by this phase — they’re candidate material for Phase 3 (enemy/boss telegraphs: Pings, more Hits/Blasts variants, Robotic Glitch/Morph textures), sourced then, not now.

Six one-shot weapons (Pulse, Melee, Nova, Beam, Turret, Scatter) all funnel through the existing AudioManager.consume(fx_events) path (audio/audio_manager.gd). No new architecture needed for these — just:

  • Extend SOUND_FOR_FX with entries for the fx kinds Pulse/Melee/Nova/Beam already emit (bolt, slash, nova, beam — currently emitted but unmapped, hence silent).
  • Add two brand-new fx kinds, turret_fire and scatter_fire, emitted once per discrete shot (WeaponTurret._fire() and WeaponScatter.update() respectively) — these did not exist before. Each needs both an AudioManager mapping AND a small visual accent in FxManager.consume (a brief muzzle spark, reusing the existing pooled-sprite pattern from the cycle-22 hit-flash) so the firing moment isn’t silent AND invisible — matching this project’s established “every fx kind needs a visual case” rule.
  • Known accepted overlap: the beam fx kind is already shared between the Beam weapon and the Lancer enemy / Eye boss telegraph (per SOUND_MAP.md). Mapping beambeam_zap.wav now means enemy beams incidentally get the same sound until Phase 3 gives them a distinct one. This mirrors the existing GENERIC-sound pattern elsewhere in the map and is not a new problem introduced by this phase.
  • Evolutions layer, not replace (already-decided project convention, matches the “power grows, doesn’t replace” balance philosophy): an evolved weapon’s fire event still plays its base sound; no separate evolution sound is added in this phase. (Layering a second texture on top of the base sound for evolved weapons is an explicit future nice-to-have, not built now.)

Orbit is architecturally different — it’s continuous, not event-based. WeaponOrbit.update() runs every tick with no cooldown (constant shard rotation), so funneling it through the one-shot fx_events/pool system would either need a per-tick event (spam) or an awkward “is this still ongoing” heuristic. Instead:

  • AudioManager gains a small looping-audio concern separate from the one-shot pool: a single dedicated AudioStreamPlayer (bus SFX, stream.loop_mode set on the imported orbit_ambience.wav) that main.gd starts/stops once per frame based on whether "orbit" is in sim.active_weapon_ids (a plain boolean check, not an event) — start if active and not already playing, stop if inactive and still playing. No /sim involvement; this is 100% render-side state, matching how QualityManager and other main-driven per-frame checks already work in this codebase.
  • This is a new, small addition to AudioManager’s public surface (e.g. set_orbit_active(bool)), not a rework of the existing one-shot path.

Extend tests/test_audio_manager.gd per the project’s existing pattern:

  • Every one-shot weapon fx kind (bolt/slash/nova/beam/turret_fire/scatter_fire) has a SOUND_FOR_FX entry.
  • set_orbit_active(true) starts the loop player exactly once (idempotent on repeated calls); set_orbit_active(false) stops it.
  • New fx kinds (turret_fire, scatter_fire) each have a matching FxManager.consume visual case (boot-check/playtest-verified, per this codebase’s convention that render/UI visual classes aren’t unit-tested — see funzo_renderer.gd precedent — but the fx-kind-has-a-case invariant itself is testable by asserting the match statement’s known kinds, same shape as prior fx-coverage tests).

Every change is render-side (AudioManager, FxManager, main.gd) plus new fx_events entries — fx_events is excluded from both snapshot_string() and state_checksum() by design (established in Phase 1 and every prior fx-adding cycle). No /sim logic changes beyond appending to fx_events at existing call sites (WeaponTurret._fire, WeaponScatter.update), which is the same shape as every other weapon’s existing fx emission. Determinism re-verification is expected to be a no-op confirmation, matching the original Phase plan’s framing.

  • Phase 3 content (enemy/boss telegraph sounds, the remaining pack files).
  • Evolution-specific layered sounds (noted above as a future nice-to-have).
  • A precise crossfade-authored seamless loop baked into orbit_ambience.wav — loop points are a Godot import-setting decision made during implementation, not a pre-baked audio edit.
  • Any UI sound (Phase 4) or music (Phase 5).