Audit du sous-système audio (M44)

VérifiéPrudence

Audit approfondi du sous-système audio M44 (backend kira, sous-pistes spatiales, envoi de réverbe, musique en streaming) et de ses appelants moteur, orchestré via agents.

Spar Skills Guide Bot
DeveloppementAvancé
07/11/2026
Claude Code
#code-audit#rust#audio#game-engine#kira

Recommandé pour

Synthèse automatique

Audite le sous-système audio M44 (backend kira, sous-pistes spatiales, envoi de reverb, filtre sous-marin, voix et musique en streaming) ainsi que les systèmes du moteur qui en sont les seuls appelants, sous forme de constats dédupliqués par dimension.

Points forts

  • Orchestre un agent par dimension (3 simultanés au maximum) en s'appuyant sur un protocole partagé de cadrage delta-first et de format de constats.
  • Délimite explicitement les responsabilités avec les audits voisins (état de l'eau pour la physique, ordonnancement pour l'ECS et la concurrence) afin d'éviter les doublons.
  • Sépare nettement ce qui est livré de ce qui ne l'est pas encore et cite les tickets ouverts (#3816, #3086, #5526, #5464, #5912…) pour chaque lacune.
  • Ancre la vérification dans des références concrètes : docstring de module de lib.rs, docs/feature-matrix.md, fichiers de tests et de fuzzing du crate.

Limites

  • Très couplé à un dépôt précis (chemins, noms de composants, numéros de tickets) : difficilement réutilisable tel quel ailleurs.
  • Dépend de fichiers compagnons (_audit-common.md, _audit-severity.md) absents du SKILL.md fourni, et le texte s'interrompt en pleine phrase sur le point #3086.
  • L'état des fonctionnalités est daté du 2026-10-20 et doit être revérifié à chaque exécution.
Quand l'utiliser

À utiliser lorsqu'il faut une revue approfondie du crate audio et de ses points d'appel dans le moteur, par exemple avant une release ou après une modification de l'audio.

Quand l'éviter

À ne pas utiliser pour un autre sous-système ou un autre dépôt, ni si le protocole d'audit partagé et l'arborescence de crate attendue ne sont pas disponibles.

Analyse de sécurité

Prudence
Score qualité84/100

The skill is a read/verify-oriented audit workflow: git/grep inspection, targeted cargo test runs, subagent fan-out, and a report written into the repo. Its only destructive action is deleting its own /tmp scratch directory, and nothing sends data out or touches secrets. It is rated caution rather than safe because it drives an execution-capable toolchain (cargo test, ignored/device test lane, shell, 3 concurrent subagents) and mutates the working tree, so a reviewer should expect real command execution against a local repository.

Points d'attention
  • •Executes shell commands: `mkdir -p /tmp/audit/audio`, `cargo test -p byroredux-audio`, `git log`, `git grep` — cargo test compiles and runs arbitrary repository code, and the skill instructs re-running the suite with `-- --ignored` (device-gated/FFN-data lifecycle tests), expanding the executed surface.
  • •Destructive command present but tightly scoped: `rm -rf /tmp/audit/audio` at merge time. Low blast radius, but a literal `rm -rf` on a path variable is a pattern worth hardening (quote/validate the path).
  • •Spawns up to 3 concurrent Task subagents with open read access to the repo and tooling, then writes output into `docs/audits/AUDIT_AUDIO_<TODAY>.md`, mutating the working tree without an explicit confirmation step.
  • •Depends on reading sibling instruction files (`.claude/commands/_audit-common.md`, `_audit-severity.md`) whose contents are not shown here; a modified/compromised common protocol file would silently steer this audit.
  • •Recommends a follow-on command (`/audit-publish`) as the final step, chaining execution beyond the audited scope.
  • •No network fetches, credential access, exfiltration, obfuscated payloads, or safety-disabling instructions found; no prompt-injection attempts detected in the skill body.
  • •Directs the agent to execute repository build/test binaries (`cargo test -p byroredux-audio`, `cargo test -p byroredux -- audio dialogue_voice`, and `-- --ignored` device-gated lanes), i.e. compiling and running arbitrary in-repo code, plus `git log`/`git grep` shell invocations. Legitimate for an audit skill, but it is real code execution with no declared allowed-tools.
  • •Contains a scoped destructive command: `mkdir -p /tmp/audit/audio` ... `rm -rf /tmp/audit/audio`. The path is narrow and self-owned, but an agent that mis-substitutes the variable could widen the blast radius; the cleanup should be quoted/guarded.
  • •Describes a bridge that pipes untrusted game-asset WMA payloads through an external `ffmpeg` process (crates/audio/src/fuz.rs). Not an instruction to the auditor, but the audited surface parses attacker-controllable binary containers via a third-party binary — worth noting as a risk the skill is asking the agent to reason about.
  • •No prompt-injection, exfiltration, credential access, obfuscated payload, or safety-disabling instruction found in the content; the document reads as genuine internal audit runbook material.
Comment cette analyse est faite

Exemples

Deep spatial audio audit
Run the M44 audio audit at deep depth, focusing on the kira backend and the spatial sub-tracks.
Reverb and underwater paths
Audit the reverb send, reverb zones and the underwater filter at shallow depth and report findings in the shared format.
Engine callers check
Check the engine-side callers of the audio crate — footsteps, water audio, REGN ambient music and voice — for stale or missing API usage.

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 Arcs held by live ActiveSounds. 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 breaks 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).

Du fichier au premier essai

Le téléchargement contient exactement le contenu affiché sur cette fiche. Cette copie peut différer de la dernière version du dépôt source.

Consulter la source et ses fichiers annexes

Ce téléchargement contient uniquement SKILL.md. Les scripts, références, modèles et dépendances éventuels ne sont pas inclus. Récupérez les fichiers nécessaires depuis la source en conservant leur arborescence avant de poursuivre.

1. Télécharger le fichier

Télécharger SKILL.md

Depuis la racine d’un projet de test, enregistrez le fichier sous .skill-downloads/audio-subsystem-audit-m44-mvhm5m25/SKILL.md, ou utilisez la commande suivante. Ce dossier sert à examiner la skill avant son installation.

Commande pour macOS/Linux ou WSL : un shell POSIX et curl sont nécessaires. Sous Windows sans WSL, utilisez le bouton de téléchargement et créez le dossier manuellement.

mkdir -p '.skill-downloads/audio-subsystem-audit-m44-mvhm5m25' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/audio-subsystem-audit-m44-mvhm5m25/download' --output '.skill-downloads/audio-subsystem-audit-m44-mvhm5m25/SKILL.md.part' && mv '.skill-downloads/audio-subsystem-audit-m44-mvhm5m25/SKILL.md.part' '.skill-downloads/audio-subsystem-audit-m44-mvhm5m25/SKILL.md'

2. Vérifier les prérequis

Lisez SKILL.md avant de l’utiliser. Si le fichier cite des scripts ou services externes, examinez-les et préparez les accès nécessaires dans votre environnement de test.

Aucune section de prérequis reconnue dans ce fichier. Cela ne signifie pas qu’il est autonome : les bibliothèques, outils et accès peuvent être cités ailleurs dans le contenu ou dans le dépôt source.

Les plateformes de la fiche sont des indications du catalogue. Les chemins ci-dessous suivent la documentation des outils ; ils ne prouvent pas que cette skill y a été exécutée. Aucune version minimale de ces outils n’est attestée pour cette fiche.

3. Charger la skill

Le fichier ne fournit pas un en-tête name/description exploitable pour cette procédure native. Le téléchargement reste disponible pour lecture ; utilisez le mode manuel ci-dessous.

Mode manuel ou autre outil : joignez SKILL.md à une conversation, ou ouvrez-le dans un agent capable de lire les fichiers. Ajoutez les ressources annexes nécessaires. Il n’existe pas de dossier universel reconnu par tous les outils.

4. Faire un premier essai

Utilisez une entrée courte et fictive : un endpoint d’API, un petit document ou un brief d’interface. Dans une nouvelle conversation, joignez le fichier ou donnez son chemin à l’agent, puis adaptez cette demande :

Lis .skill-downloads/audio-subsystem-audit-m44-mvhm5m25/SKILL.md et applique ses instructions à cet exemple : [décrire une entrée de test]. Indique les fichiers, dépendances ou accès qui manquent. Produis le résultat demandé par la skill sans inventer de données ni exécuter de script non examiné.

Résultat attendu : une sortie correspondant aux instructions de la skill, ou une liste précise des prérequis manquants. Une simple confirmation de lecture ne valide pas le premier usage. Si une ressource externe est indisponible, ce parcours reste à tester.

Skills similaires