---
description: "Deep audit of the M44 audio subsystem — kira backend, spatial sub-tracks, listener/unit seam, reverb send, underwater filter, streaming music — plus its engine callers (footsteps, water audio, reverb zones, REGN ambient music)"
argument-hint: "--focus <dimensions> --depth shallow|deep"
---

# Audio Subsystem Audit (M44)

Read `.claude/commands/_audit-common.md` (delta-first scoping, dedup, finding format) and `_audit-severity.md` for shared protocol.

Audits `crates/audio/` (kira `0.12`, `default-features = false` with an explicit feature list in the workspace `Cargo.toml` — cpal, wav+pcm, ogg+vorbis, mp3, flac; 0.12 split container from codec features, so dropping a codec silently stops decoding (mp3 now carries Oblivion voice); `src/lib.rs` + `src/tests.rs` + `src/fuz.rs`, the `.fuz`/xWMA container reader and ffmpeg bridge) and the engine systems that are the ONLY callers of its API. Orchestrator; one Task agent per dimension (max 3 concurrent). Verify the shipped surface against the `lib.rs` module docstring and `docs/feature-matrix.md` (Audio); if `lib.rs` splits, re-derive each dimension's paths first.

## Scope

- **Crate**: `AudioWorld` (`Option<AudioManager>` resource; `headless()` is the no-device constructor), `AudioListener` / `AudioEmitter` / `OneShotSound` components, `Attenuation`, `audio_system`, `SoundCache`, `VoiceSoundCache`, `load_*sound*`, `with_start_delay`.
- **Engine side**: `byroredux/src/systems/audio.rs` (`footstep_system`, `impact_audio_system`, `reverb_zone_system`, `water_audio_system`), the combat-feedback sound half of `byroredux/src/systems/combat_anim.rs` (`play_oneshot_cached`; take/animation half is `/audit-gameplay`), `byroredux/src/systems/dialogue_voice.rs` (voice consumer), `byroredux/src/systems/scripted_sound.rs` (script `Sound.Play`), `SoundCache` / `VoiceSoundCache` boot inserts (`boot/world.rs`) + telemetry (`ownership_sample.rs`), the footstep / impact / water-audio resources in `byroredux/src/components/audio.rs` (`FootstepConfig`, `ImpactConfig`, `WaterAudioConfig`) and `RegionAmbientRes` in `byroredux/src/components/environment.rs`, `byroredux/src/asset_provider/{audio,texture}.rs` (`dispatch_region_ambient_music`, `SoundArchiveProvider`, `sound_is_folder`, `try_load_default_footstep`), wired from `cell_loader/load.rs` and `scene::apply_cell_region_ambient`.
- **Handoffs**: water *state* (`SubmersionState`, splash/ripple production) is `/audit-physics` Dim 5; stage/lock shape is `/audit-ecs` / `/audit-concurrency` (report audio-specific ordering only).

**Not shipped as of 2026-10-20 (re-verify against `docs/feature-matrix.md`; audit the mechanism that will carry them)**: FOOT/IPDS/IPCT records → per-material footstep and clutter-impact *content* (the Wave-4 mechanism is live — Havok `surface_material` u32 → `FootstepConfig`/`ImpactConfig` `surface_sounds` — but both maps and `ImpactConfig::default_sound` ship empty, so `impact_audio_system` is a production no-op and footsteps always take the default), CELL-level MUSC routing (`default_music`/`music_type_form` are parsed; every construction site still hardcodes `music_type_form: None`), per-cell acoustics beyond binary interior/exterior reverb, occlusion, REGN `incidental`/ambient-loop selection (#3301 open). Dialogue voice now covers every voiced title (Dim 5): Oblivion's race/sex `.mp3` (#5526 fa7591ca2, through kira's mp3 feature) and Skyrim+ `.fuz` (FUZE + raw `.lip` + RIFF **xWMA**, not XMA2 — #5464 1c69389b7; `crates/audio/src/fuz.rs` parses it and bridges the WMA payload through the host's OPTIONAL `ffmpeg` to a hand-headed WAV, since no workspace decoder reads WMA — no ffmpeg ⇒ subtitle estimate, #5912 acd577a79).
**Known-open** (verified 2026-10-20): #3816 (MUSC/MUST/MSET/RDMD decode: REGN `music_form` never resolves as a `SOUN` on any supported game, so the dispatch *mechanism* is live and tested but plays nothing in production; audit the mechanism, cite #3816 for the gap). #3086 (entity-path spatial position frozen at dispatch) is closed (b4f08089b): `sync_emitter_positions` now follows the source `GlobalTransform` (Dim 1) — a frozen position is a regression. #4146 (reverb-zone ordering comment drift) is closed — a third drift is a regression. #5432 is closed (40b77828b): `AudioWorld::stop_sounds_for` (#5410) now has production callers through `dialogue_voice::stop_line_voice` — topic change, conversation close and speaker death cut the line (Dim 1/5); a new line-ending path that skips it is the regression. #5148 is closed (7208af67c): `combat.status` reports per-kind request counts (`CombatSoundRequests`: swing / impact / death_voice, counted only when `play_oneshot_cached` actually requested a one-shot) and the Skyrim P2 gate requires >= 1 impact + >= 1 death-voice, so `oneshots_requested` is no longer the gate's only signal. #4743 is closed (519bb02c4): impact one-shots queue per `HitEvent` ahead of the `Dead` / active-take branches (mid-stagger hits and the killing blow sound) and the swing sound rides the marked aggressor's own attack take; pinned by `a_hit_during_an_active_take_still_sounds` beside the existing killing-blow / aggressor-swing tests.

## Parameters

`--focus <dims>` (default all 5) · `--depth shallow|deep` (`deep` traces per-frame data flow + lifecycle).
**Extra field**: **Dimension**: Spatial Dispatch & Listener | Send Graph (Reverb + Underwater) | Music & SoundCache | Manager & ECS Lifecycle | Engine Consumers.

## Phase 1: Setup

1. `mkdir -p /tmp/audit/audio`; dedup per `_audit-common.md`; read the latest `docs/audits/AUDIT_AUDIO_*.md` (date D = delta baseline). A re-flag of a closed finding is a regression claim: check its guard is gone before reporting.
2. `cargo test -p byroredux-audio` and `cargo test -p byroredux -- audio dialogue_voice`; record counts. **The lifecycle guards are `#[ignore]`d** (need an audio device, some FNV data): `looping_emitter_survives_natural_duration_and_stops_on_emitter_remove`, `non_looping_emitter_stops_on_emitter_remove_regression_858`, `play_music_looping_survives_track_end`, `play_music_drives_streaming_playback_on_real_ogg`, `emitter_position_follows_the_source_entity_regression_3086`, `anchored_voice_follows_survives_prune_and_stops`, plus the playback/decode trio `play_oneshot_queue_drives_real_playback`, `audio_system_full_lifecycle_on_real_fnv_sound`, `real_fnv_sounds_decode_through_kira`. Despawn truncation, follow, anchored-voice stop and music looping are unguarded in the default lane: run `-- --ignored` or say so.

## Phase 2: Dimensions

### Dim 1: Spatial Dispatch, Listener & the Unit Seam
**Paths**: `crates/audio/src/lib.rs` (`dispatch_new_oneshots`, `drain_pending_oneshots`, `build_spatial_track`, `prune_stopped_sounds`, `sync_listener_pose`, `sync_emitter_positions`, `bu_to_audio_space`, `linear_volume_to_db`, `ActiveSound`, `SoundSource`, `play_oneshot_following`, `stop_sounds_for`)
**First step**: `git log --since=D -- crates/audio/src/lib.rs`
**Guards** (default lane): `tests.rs::{every_kira_position_site_goes_through_the_unit_seam, emitter_at_max_distance_in_bu_lands_on_max_distance_in_audio_space, drain_pending_oneshots_drains_in_place_instead_of_stranding_capacity, play_oneshot_queue_caps_at_max_pending_when_active, oneshot_marker_is_consumed_on_both_dispatch_failure_arms, with_start_delay_sets_the_start_time_and_shares_frames, play_oneshot_following_drops_on_inactive_audio, stop_sounds_for_is_a_no_op_on_a_headless_world}`; `#[ignore]`d device-gated `emitter_position_follows_the_source_entity_regression_3086` (#3086) and `anchored_voice_follows_survives_prune_and_stops` (#5410).
- Two dispatch paths stay observably equivalent: entity path (`OneShotSound`+`AudioEmitter` → per-emitter `SpatialTrackHandle`, `SoundSource::Emitter`, marker removed, `AudioEmitter` kept) and queue path (`PendingOneShot`, never loops: `play_oneshot` → `SoundSource::Detached`, or `play_oneshot_following` → `SoundSource::Anchored(entity)`, #5410 d1a7ee8c6). Both early-return without a listener id and build their sub-track through the one `build_spatial_track` (distance window, reverb send, underwater filter, kira sub-track; each loop keeps only its play tail and error policy, #5738 eac602644) — a loop that hand-builds the chain again is the regression. `stop_sounds_for(entity, fade_ms)` fades every playing `Emitter`/`Anchored` sound of the entity and drops its queued segments; its production caller is `dialogue_voice::stop_line_voice` (150 ms fade; `apply_selection`, `end_open_conversation`, `reconcile_dead_actor`, and ahead of each ObScript `Say` line in `scripted_line_voice_system`; #5432). The entity path has NO engine producer — `spawn_oneshot_at` is test/tooling-only, so its dispatch is exercised by `tests.rs` alone (#5612 81207d34d).
- `ActiveSound::track` is held for its Drop side effect AND carries the #3086 position pushes (renamed from `_track`; the track must still be kept alive): it must land in `active_sounds` before the helper returns. `Arc<StaticSoundData>` is shared, never deep-cloned; volume applies via `(*sound).clone().volume(db)`.
- `oneshots_requested` is a monotonic count of `play_oneshot` / `play_oneshot_following` calls **including headless discards** (incremented before the manager gate; read by `combat.status` in `commands/view.rs` for device-less smoke gates) — it proves a request, never audible playback.
- Producer queue caps at 256 (in both `play_oneshot*` entry points, each with its own `MAX_PENDING`) with O(1) `pop_front` and drops up front when the manager is `None`; the manager-active gate precedes the drain; a drain of more than 32 warns once per tick (footstep tempo gone wrong). `linear_volume_to_db` is the single dB conversion (floor `SILENCE_DB` -60).
- Entity path reads post-propagation `GlobalTransform`, not `Transform`. World is Bethesda units (70/m), kira is metres: `bu_to_audio_space` is the only seam, applied to the listener pose and both dispatch paths; `Attenuation` is authored in metres (`min <= max`, `RangeInclusive`) and callers must never pre-scale by 70. A missed site is silent (inaudible past ~43 cm, no log); `every_kira_position_site_goes_through_the_unit_seam` scans the listener sites, both dispatch paths and (since #5411, bb31c5fb3) the follow site, with a negative on raw `set_position`.
- Emitter follow (#3086, b4f08089b): `sync_emitter_positions` (pass 2, after the listener sync) pushes the sub-track of every `ActiveSound` whose `SoundSource::followed_entity()` is set (`Emitter` and `Anchored`) to its source's current `GlobalTransform` through the same `bu_to_audio_space`; change-gated on `ActiveSound::last_position` (seeded at dispatch on both paths) so a stationary emitter costs one compare and zero kira commands; `Detached` queue sounds and music are never touched; a despawned/transform-less entity is skipped (prune owns `Emitter` on emitter removal and `Anchored` on `GlobalTransform` loss — `orphaned_anchored_entity`, #5678 74d841165 — and `stop_sounds_for` owns the explicit `Anchored` stop). `AudioWorld::emitter_position_updates()` is the observable (kira has no position readback). The behaviour guards are `#[ignore]`d (device) — trace the code in the default lane.
- Listener: created lazily on the first frame an `AudioListener` has a resolved `GlobalTransform`; the handle is **sticky** (never cleared on despawn: kira listener capacity is 8, a clear/re-add loop exhausts it); a failed `add_listener` retries next frame; multi-listener warns once and uses the first. Pose uses `Tween::default()` (smooth follow); orientation is already renderer-space (no second Z-up conversion).
**Output**: `/tmp/audit/audio/dim_1.md`

### Dim 2: Send Graph: Reverb Send & Underwater Filter
**Paths**: `crates/audio/src/lib.rs` (`AudioWorld::new`, `apply_reverb_send`, `reverb_send_gate_open`, `set_reverb_send_db`, `apply_underwater_filter`, `update_underwater_filters`, `set_underwater`)
**First step**: `git log --since=D -- crates/audio/src/lib.rs` (send/filter regions)
**Guards**: `tests.rs::{reverb_send_defaults_to_silent, reverb_send_gate_matches_silence_db_boundary, above_water_filter_state_is_a_dry_bypass, above_water_cutoff_never_reaches_the_kira_clamp_ceiling, both_dispatch_paths_build_the_filter_through_one_call_site}`.
- One global reverb send is created in `AudioWorld::new` from `REVERB_FEEDBACK`/`REVERB_DAMPING`/`REVERB_STEREO_WIDTH` (0.85/0.6/1.0). They have **no recorded provenance** and are not kira's defaults: flag the doc gap, never invent a rationale. `reverb_send: None` never cascades into an `unwrap()`.
- Send opt-in is build-time only (`with_send` at track construction, gated `is_finite() && > -60 dB`) and both paths use the shared helper. Default level `NEG_INFINITY` (dry). Playing sounds keep their construction-time level: the documented "next-dispatch knob" contract, not a bug — kira 0.12's `SpatialTrackHandle::set_send` could retarget an existing route, but the engine does not, and a track built while the send was muted has no route at all (doc corrected #5677 b0b9bc987; a live fader needs every track routed at `SILENCE_DB` from construction).
- Every spatial sub-track carries a low-pass built by ONE shared `apply_underwater_filter`; `update_underwater_filters` retargets live tracks when the listener's submersion changes. Above water is a genuine bypass via `Mix::DRY` (a default `FilterBuilder` is fully wet and colours the signal); the parked 20 kHz cutoff stays below kira's Nyquist clamp (beyond it the filter degenerates rather than turning transparent). `set_underwater` is written each frame by `water_audio_system` (Dim 5), before `audio_system`.
**Output**: `/tmp/audit/audio/dim_2.md`

### Dim 3: Streaming Music & SoundCache
**Paths**: `crates/audio/src/lib.rs` (`play_music`, `stop_music`, `is_music_active`, `load_streaming_sound_from_*`, `SoundCache`, `VoiceSoundCache`)
**First step**: `git log --since=D -- crates/audio/src/lib.rs`
**Guards**: `tests.rs::{play_music_no_op_when_inactive, sound_cache_hits_are_case_insensitive_and_share_arc, sound_cache_get_or_load_invokes_loader_only_on_miss, sound_cache_clear_drops_entries_and_bytes_estimate_tracks_pcm_size, voice_cache_evicts_lru_under_byte_budget, voice_cache_negatives_are_capped_and_counted}`.
- Single music slot: `play_music(streaming_sound, volume, fade_in_secs, looping)` (4 parameters, `looping` since #3775; re-verify the arity before citing) crossfades any existing handle. Music routes through the **main track**, never a spatial sub-track. It uses `StreamingSoundData` (`from_cursor`/`from_file`), never `StaticSoundData` (full PCM decode = OOM on long tracks).
- `stop_music` fades for `fade_out_secs.max(0.0)` then drops the handle (an instant stop clicks); `is_music_active` goes false the moment `stop_music` returns (the slot is cleared while kira renders the fade tail): documented, so a caller must not read it as "audibly occupied".
- `SoundCache` keys lowercase once per entry point (`sound_archive_path` also drops a `Data\` game-root prefix that Skyrim.esm's pull-chain descriptors author); `clear()` never invalidates `Arc`s held by live `ActiveSound`s. **It is live since 2026-09-27**: inserted at boot (`boot/world.rs`), sampled by `ownership_sample.rs` (`len()` + `bytes_estimate()`), and its consumer is `combat_anim.rs::play_oneshot_cached` (three fixed combat paths; voice moved to `VoiceSoundCache`), which M47.2's `scripted_sound_system` now also calls with script-chosen SNDR files — an open-ended key set landing on `SoundCache`, the shape this section calls the finding (every distinct file stays resident, no eviction). Footstep/splash loaders still bypass it into `FootstepConfig`/`WaterAudioConfig`. The map is `Option<Arc<_>>`: `None` is a **negative-cache** entry (loader returned no bytes, or decode failed) that `get_or_load` never re-probes and `len()`/`bytes_estimate()` exclude — so a sound missing at first use stays missing until `clear()`, which nothing in production calls (check whether the `--sounds-bsa` set can change at runtime). Still no eviction: a consumer keyed on open-ended paths (FOOT, REGN, per-NPC SFX) that lands on `SoundCache` is the finding.
- `VoiceSoundCache` (#5382, 8410ad1e5) is the open-ended-key cache, used only by dialogue voice: same `get_or_load` contract, LRU-evicted past `DEFAULT_BUDGET_BYTES` (64 MiB; the just-inserted entry is never its own victim), negatives capped at `VOICE_NEGATIVE_CAP` (4096; the set clears and re-probes past it) and counted (`negative_len`). Telemetry reports `voice_cache_{entries,bytes,negatives}` through `ownership_sample.rs`; the save registry allowlists it. A new open-ended consumer belongs here (or in its own bounded cache), not in `SoundCache`.
**Output**: `/tmp/audit/audio/dim_3.md`

### Dim 4: Manager, ECS Lifecycle & Schedule
**Paths**: `crates/audio/src/lib.rs` (struct, `AudioWorld::new`/`headless`, `audio_system`, `prune_stopped_sounds`), `byroredux/src/boot/schedule/late.rs`, `byroredux/src/cell_loader/{load,unload}.rs`
**First step**: `git log --since=D -- crates/audio/src/lib.rs byroredux/src/boot/schedule/late.rs`
**Guards**: `tests.rs::{audio_world_constructs_without_panic_on_any_environment, explicit_headless_world_discards_playback_without_retaining_sound, manager_capacities_exceed_kira_defaults}`; `scheduler_access_tests.rs::{footstep_runs_after_camera_follow_in_late, reverb_zone_is_parallel_and_audio_is_exclusive_in_late}`.
- Graceful degradation: `AudioManager::new` failure leaves `manager = None`; every public API gates on it with no `unwrap()`; headless/CI boot must succeed (report as PASS/FAIL in the summary). `AudioWorld::headless()` never contacts a device; tests use it, not the device-dependent `default()`.
- `SUB_TRACK_CAPACITY` (512) and `SEND_TRACK_CAPACITY` (32) exceed kira's defaults and are applied in `new()`.
- **Field-drop order** (single source of truth): `active_sounds` → `pending_oneshots` → (`oneshots_requested`) → `music` → `reverb_send` → `reverb_send_db` → `listener` → `manager`, then the trailing `multi_listener_warned` / `underwater` flags and the `emitter_position_updates` counter. A reorder that moves `manager` up asserts in kira's Drop. `AudioWorld::new()` is boot-only, never on cell transition or resize.
- **Schedule** (all `Stage::Late`): `audio_system` is an *exclusive* registered after `footstep_system` (exclusive, pinned to Late by #4185's test) and `water_audio_system` (exclusive, after `submersion_system`); `reverb_zone_system` is a *parallel* registration, and the parallel batch finishing before any exclusive (not registration order) is what sets the send level before new tracks are built. `audio_system` body order: `sync_listener_pose` → `sync_emitter_positions` → `update_underwater_filters` → `drain_pending_oneshots` → `dispatch_new_oneshots` → `prune_stopped_sounds`. The conversation voice producer (Late `npc_dialogue_selection_system`) registers AFTER `audio_system`, so a line's segments dispatch on the next frame — one frame of latency, not a loss; the ObScript `Say` producer `scripted_line_voice_system` is Update, so its segments dispatch the same frame.
- Despawn truncation: only `SoundSource::Emitter` entries are emitter-coupled (`emitter_coupled()`): an entity that loses `AudioEmitter` gets a tweened `stop()` over `ActiveSound.unload_fade_ms` (10 ms default, looping and not); `stop_issued` debounces (and is set by `stop_sounds_for` too); retain drops only on `Stopped`. `Detached` runs to natural end; `Anchored` survives SFX emitter churn on the same NPC by design and stops via `stop_sounds_for` (topic change, close, death, #5432) or, for a speaker despawned mid-line (cell unload), through the prune sweep's tweened stop once the followed entity has no `GlobalTransform` (#5678 74d841165; the `anchored_voice_follows_survives_prune_and_stops` lifecycle test is `#[ignore]`d, the orphan selection is pinned default-lane by `only_transformless_anchored_sources_are_prune_orphans`). Guards are `#[ignore]`d (Phase 1): trace the code.
- Cross-cell continuity: `dispatch_region_ambient_music` runs only when `music_form` changed (both `cell_loader/load.rs` paths and `scene::apply_cell_region_ambient` compare against the prior `RegionAmbientRes`); an unconditional redispatch audibly restarts the track on every connected-cell crossing. A future CELL-level MUSC caller needs its own gate.
**Output**: `/tmp/audit/audio/dim_4.md`

### Dim 5: Engine-Side Consumers
**Paths**: `byroredux/src/systems/{audio,dialogue_voice,scripted_sound}.rs`, `crates/audio/src/fuz.rs`, `byroredux/src/components/{audio,environment}.rs` (footstep/impact/water-audio/region resources), `byroredux/src/asset_provider/{audio,texture}.rs`, `byroredux/src/scene.rs`
**First step**: `git log --since=D -- byroredux/src/systems/audio.rs byroredux/src/systems/dialogue_voice.rs byroredux/src/asset_provider/audio.rs`
**Guards** (`systems/audio.rs` tests): `first_tick_seeds_last_position_without_firing`, `stride_threshold_is_bethesda_units_not_metres`, `walking_for_one_second_fires_a_walking_cadence_not_one_per_frame`, `single_large_jump_fires_one_footstep_only`, `water_splash_event_reaches_audio_dispatcher`, `interior_to_exterior_transition_resets_send_to_dry`, the #5146 trio `orbiting_camera_with_stationary_body_fires_nothing` / `swimming_body_is_silent_and_exit_does_not_replay_the_swim_distance` / `airborne_body_is_silent_and_landing_does_not_replay_the_air_distance`, plus the `no_*_is_safe_noop` pair. Highest-yield dimension in the 2026-08/09 reports.
- **Callers**: `footstep_system`, `impact_audio_system` (Late exclusive registered right after `footstep_system`: drains `PhysicsWorld::take_contact_force_events` — the `started` force crossings, below `ImpactConfig::min_force_magnitude` skipped — and plays one `{1.0, 24.0}` m one-shot per impact at `PhysicsWorld::contact_event_position` — the deepest solver contact, not the body-origin midpoint (a static ghost / exterior terrain tile parks its body at the placement or world origin, so the midpoint played 300 m impacts at ~150 m, #5675 0d8ea5d6e); material = `resolve_impact_material` (collider 1's non-zero, else collider 2's, else 0 while either handle lives, #5549); `impact_sounds_to_play` skips only an unmapped impact, never `break`s the frame, #5554; the whole system returns early with no `default_sound` and no `surface_sounds`, and first syncs the physics subscription to that verdict — `set_contact_force_events_enabled(has_sound)`, so with no content dynamic colliders carry no force-event flag at all (#5711 e3a4d5e18; `/audit-physics` Dim 2)), `water_audio_system` and `combat_anim.rs::play_oneshot_cached` (PostUpdate, enqueued before Late `audio_system`) are live `play_oneshot` callers, with `systems/scripted_sound.rs::scripted_sound_system` (Update exclusive, M47.2 5046c7af8/f093e7393: drains `PendingSoundPlays`, resolves the SNDR/SOUN through `index.sounds`, skips looping descriptors, takes multi-`ANAM` variants in turn, positions at the source's `GlobalTransform` and plays through `play_oneshot_cached` — note this puts script-authored, open-ended sound paths on the unbounded `SoundCache`, Dim 3) and `byroredux/src/systems/dialogue_voice.rs` as further callers (#5367 Phase V: per-line voice — FO3/FNV `.ogg`, Oblivion race/sex `.mp3` #5526, Skyrim+ `.fuz` through `byroredux_audio::fuz` + optional `ffmpeg` #5912 — from `apply_selection` and the Update `scripted_line_voice_system` (ObScript `Say`/`SayTo`, whose resolved duration times `SayToDone`), through `play_oneshot_following`, anchored to the speaker and skipped when it has no `GlobalTransform` (#5410) and cut by `stop_line_voice` on topic change / close / speaker death (#5432); segments chained via `byroredux_audio::with_start_delay`, each named by its authored `response_number`; `Attenuation::default()`, volume 1.0; gated on `AudioWorld::is_active` before any archive work; decoded through `VoiceSoundCache` with the provider read inside the loader (#5382/#5383); file name and owning plugin per `docs/engine/dialogue-trees.md` (#5393/#5395, 5b68792b7; `.ogg`/`.fuz` names truncate only when quest + topic exceed 25 characters, one candidate, #5521; Oblivion's are untruncated); missing voice is a silent fallback to the subtitle estimate); `reverb_zone_system` the only `set_reverb_send_db` caller, `dispatch_region_ambient_music` the only `play_music`/`stop_music` caller. Re-grep for any new caller (`git grep -n 'play_oneshot\|play_music\|set_underwater\|stop_sounds_for\|take_contact_force_events' -- byroredux/src`) and audit it.
- **Footsteps**: XZ-only stride accumulation against `FootstepEmitter.stride_threshold`, authored in **Bethesda units** (`0.75 * BETHESDA_UNITS_PER_METER`), compared to `GlobalTransform` deltas with no conversion at the compare site; reset to 0 on fire (subtract-remainder multiplies footsteps on a teleport); first tick seeds `last_position` and fires nothing. Attenuation `{0.5, 12.0}` in metres (no pre-scaling by 70, no widening). The `FootstepScratch` `Vec` (capacity 32) is restored on the success and the `AudioWorld`-absent path; no component lock is held across `play_oneshot` and the scratch lock drops before `AudioWorld` is taken. The player-body gate (`CharacterController` grounded flag + body handle) is resolved as an owned `(body, gated)` pair BEFORE any `GlobalTransform`/`FootstepEmitter`/scratch guard (#5305, 6915fe783: taking it inside closed three lock-order cycles) — a per-entity get back inside that scope is the regression. Every missing resource is a silent no-op (no per-frame log). Opt-in is component-driven. **Surface selection (Wave 4)**: each trigger resolves its material at footstep cadence through `PhysicsWorld::surface_material_under`, which takes a MANDATORY `excluded_body` (the player's `RapierHandles.body`, else the capsule centre self-hits at toi 0 and every player step reads material 0, #5545 3909778eb); `select_footstep_sound` is a pure function of a `FootstepConfig.surface_sounds` snapshot cloned once, after the no-trigger early return and BEFORE the `AudioWorld` guard (no `AudioWorld` → `FootstepConfig` lock edge, #5556 ac710964e); the Access row declares the `PhysicsWorld`/`RapierHandles` reads (`scheduler_access_tests.rs::footstep_declares_its_physics_reads`). Material keys are u32 end to end (Skyrim's are CRC32s; 0 is STONE on Oblivion/FO3/FNV and the value of every unauthored body, #5542). **Which entity walks** (#5146, ccc743160; #5348 a9def9cb9): character mode adds an emitter on the player body, and only the stand-in camera emitter — the `ActiveCamera` entity while `PlayerMode` is `Character`, resolved once from those two resources (`skipped_camera`) — is re-seeded, never accumulated (the 180 BU third-person boom turned mouse-look into footsteps); every other emitter (a future per-NPC/FOOT one) walks, and FlyCam steps with the camera whether booted `--fly` or toggled. The body accumulates only while `CharacterController.is_grounded`; the gate is `!is_grounded` ALONE since #5347 (7068c16d3) because `resolve_ground_contact` never grounds a swimmer (`character::tests::a_swimmer_is_never_grounded`) — if swimming ever stops clearing `is_grounded`, an explicit swim operand plus a test holding it true through the swim leg must come back. Gated frames re-seed so a landing or water exit never replays as a burst. A FlyCam toggle leaves the frozen body silent while the camera steps (pinned in #5348). With no `PhysicsWorld` each stride trigger still keeps one surface result and takes the default sound instead of truncating the loop (#5714 77eea4311). The Access row (`boot/schedule/late.rs`) declares the `PlayerEntity`/`PlayerMode`/`ActiveCamera`/`CharacterController` reads and still lists a `WaterContact` read the system no longer takes (harmless over-declaration since #5347).
- **Water audio**: `water_audio_system` writes `AudioWorld::set_underwater` from the active camera's `SubmersionState.head_submerged` *every* frame (before the config early-return), then plays `SplashEvent` one-shots (range 1-24 m) and, when no splash fired, one cooldown-limited `RippleEvent` (per-surface cooldown, 0.45 damping). The range and the 0.45 factor are uncited (no authored SOUN range): note as unsourced, never invent one. `WaterAudioConfig.splash_sound` is `None` without an archive (valid headless state); `try_load_default_footstep` and `try_load_default_water_splash` (`asset_provider/texture.rs`) no-op cleanly when the arg or archive is absent.
- **Combat feedback sounds** (`play_oneshot_cached`): registered outside the `BYRO_NO_AI_LOCOMOTION` block since #4709 (70d9896fb) — a move back inside silently mutes them; hardcoded Skyrim paths per `docs/engine/p2-combat-anim-sound-fixture.md`, each request counted per kind in `CombatSoundRequests` (swing / impact / death_voice; incremented only when the one-shot was actually requested, surfaced by `combat.status`, #5148 7208af67c), attenuation `{0.5, 24.0}` m (uncited, like the water range); lookup goes through `SoundCache::get_or_load` whose loader reads `SoundArchiveProvider` **while the `SoundCache` write guard is held** (resource-in-resource nesting). Dialogue voice uses the same cache → provider order on `VoiceSoundCache` (#5383, 080b35f35; the old provider → cache order closed a lock-order cycle with this path); `dialogue_voice.rs::voice_and_combat_sound_paths_share_one_cache_provider_order` drives both consumers in one process — a third consumer taking the provider first is the regression. Headless / archive-less paths skip silently.
- **`reverb_zone_system`**: `-12 dB` interior / `NEG_INFINITY` exterior; the transition is bit-equality gated (`to_bits`), no-ops without `CellLightingRes` (pre-load) or `AudioWorld` (headless).
- **`dispatch_region_ambient_music`**: stops outstanding playback on every failure layer (no provider, no `--sounds-bsa`, unresolved FormID, folder FNAM, archive miss, decode failure, `music_form: None`) because what is audible belonged to the previous directive; the authored-but-unresolved log fires once per process (`std::sync::Once`); `looping` threads from `sound_loops` (SOUN Loop bit) into `play_music`. `SounRecord::sound_path` is a file **or a trailing-separator folder** (about half of FNV's FNAM-bearing records): `sound_is_folder` fails closed before any archive lookup, and any future `resolve_sound_path` consumer must gate on it (variant selection is an unmade policy, so no "extract any file under this folder" fallback exists).
- **Archive precedence**: `SoundArchiveProvider::extract` iterates `.rev()` (last-listed wins, as mesh/texture/material; FNV's `Fallout - Sound.bsa` + `Update.bsa` overlap with differing bytes): forward iteration is the regression. `ScriptProvider` is the deliberate first-wins exception.
**Output**: `/tmp/audit/audio/dim_5.md`

## Phase 3: Merge

Combine `/tmp/audit/audio/dim_*.md` into `docs/audits/AUDIT_AUDIO_<TODAY>.md` (header per `_audit-common.md` Report finalization): Executive Summary (severity counts, headless-boot PASS/FAIL), **Lifecycle Invariant Matrix** (field-drop order, sticky listener, despawn truncation, schedule order: verified or drifted), Findings (deduplicated), **Future-Phase Readiness** (FOOT, MUSC/MUST/MSET, occlusion). Then `rm -rf /tmp/audit/audio`; suggest `/audit-publish docs/audits/AUDIT_AUDIO_<TODAY>.md` (labels `audio`, plus `game:*` when one title's sound data is the cause).
