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.
À 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.
À 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é
PrudenceThe 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.
- •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.
Exemples
Run the M44 audio audit at deep depth, focusing on the kira backend and the spatial sub-tracks.Audit the reverb send, reverb zones and the underwater filter at shallow depth and report findings in the shared format.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/OneShotSoundcomponents,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 ofbyroredux/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(scriptSound.Play),SoundCache/VoiceSoundCacheboot inserts (boot/world.rs) + telemetry (ownership_sample.rs), the footstep / impact / water-audio resources inbyroredux/src/components/audio.rs(FootstepConfig,ImpactConfig,WaterAudioConfig) andRegionAmbientResinbyroredux/src/components/environment.rs,byroredux/src/asset_provider/{audio,texture}.rs(dispatch_region_ambient_music,SoundArchiveProvider,sound_is_folder,try_load_default_footstep), wired fromcell_loader/load.rsandscene::apply_cell_region_ambient. - Handoffs: water state (
SubmersionState, splash/ripple production) is/audit-physicsDim 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
mkdir -p /tmp/audit/audio; dedup per_audit-common.md; read the latestdocs/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.cargo test -p byroredux-audioandcargo 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 trioplay_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-- --ignoredor 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-emitterSpatialTrackHandle,SoundSource::Emitter, marker removed,AudioEmitterkept) and queue path (PendingOneShot, never loops:play_oneshot→SoundSource::Detached, orplay_oneshot_following→SoundSource::Anchored(entity), #5410 d1a7ee8c6). Both early-return without a listener id and build their sub-track through the onebuild_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 playingEmitter/Anchoredsound of the entity and drops its queued segments; its production caller isdialogue_voice::stop_line_voice(150 ms fade;apply_selection,end_open_conversation,reconcile_dead_actor, and ahead of each ObScriptSayline inscripted_line_voice_system; #5432). The entity path has NO engine producer —spawn_oneshot_atis test/tooling-only, so its dispatch is exercised bytests.rsalone (#5612 81207d34d). ActiveSound::trackis 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 inactive_soundsbefore the helper returns.Arc<StaticSoundData>is shared, never deep-cloned; volume applies via(*sound).clone().volume(db).oneshots_requestedis a monotonic count ofplay_oneshot/play_oneshot_followingcalls including headless discards (incremented before the manager gate; read bycombat.statusincommands/view.rsfor 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 ownMAX_PENDING) with O(1)pop_frontand drops up front when the manager isNone; the manager-active gate precedes the drain; a drain of more than 32 warns once per tick (footstep tempo gone wrong).linear_volume_to_dbis the single dB conversion (floorSILENCE_DB-60). - Entity path reads post-propagation
GlobalTransform, notTransform. World is Bethesda units (70/m), kira is metres:bu_to_audio_spaceis the only seam, applied to the listener pose and both dispatch paths;Attenuationis 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_seamscans the listener sites, both dispatch paths and (since #5411, bb31c5fb3) the follow site, with a negative on rawset_position. - Emitter follow (#3086, b4f08089b):
sync_emitter_positions(pass 2, after the listener sync) pushes the sub-track of everyActiveSoundwhoseSoundSource::followed_entity()is set (EmitterandAnchored) to its source's currentGlobalTransformthrough the samebu_to_audio_space; change-gated onActiveSound::last_position(seeded at dispatch on both paths) so a stationary emitter costs one compare and zero kira commands;Detachedqueue sounds and music are never touched; a despawned/transform-less entity is skipped (prune ownsEmitteron emitter removal andAnchoredonGlobalTransformloss —orphaned_anchored_entity, #5678 74d841165 — andstop_sounds_forowns the explicitAnchoredstop).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
AudioListenerhas a resolvedGlobalTransform; the handle is sticky (never cleared on despawn: kira listener capacity is 8, a clear/re-add loop exhausts it); a failedadd_listenerretries next frame; multi-listener warns once and uses the first. Pose usesTween::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::newfromREVERB_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: Nonenever cascades into anunwrap(). - Send opt-in is build-time only (
with_sendat track construction, gatedis_finite() && > -60 dB) and both paths use the shared helper. Default levelNEG_INFINITY(dry). Playing sounds keep their construction-time level: the documented "next-dispatch knob" contract, not a bug — kira 0.12'sSpatialTrackHandle::set_sendcould 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 atSILENCE_DBfrom construction). - Every spatial sub-track carries a low-pass built by ONE shared
apply_underwater_filter;update_underwater_filtersretargets live tracks when the listener's submersion changes. Above water is a genuine bypass viaMix::DRY(a defaultFilterBuilderis 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_underwateris written each frame bywater_audio_system(Dim 5), beforeaudio_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,loopingsince #3775; re-verify the arity before citing) crossfades any existing handle. Music routes through the main track, never a spatial sub-track. It usesStreamingSoundData(from_cursor/from_file), neverStaticSoundData(full PCM decode = OOM on long tracks). stop_musicfades forfade_out_secs.max(0.0)then drops the handle (an instant stop clicks);is_music_activegoes false the momentstop_musicreturns (the slot is cleared while kira renders the fade tail): documented, so a caller must not read it as "audibly occupied".SoundCachekeys lowercase once per entry point (sound_archive_pathalso drops aData\game-root prefix that Skyrim.esm's pull-chain descriptors author);clear()never invalidatesArcs held by liveActiveSounds. It is live since 2026-09-27: inserted at boot (boot/world.rs), sampled byownership_sample.rs(len()+bytes_estimate()), and its consumer iscombat_anim.rs::play_oneshot_cached(three fixed combat paths; voice moved toVoiceSoundCache), which M47.2'sscripted_sound_systemnow also calls with script-chosen SNDR files — an open-ended key set landing onSoundCache, the shape this section calls the finding (every distinct file stays resident, no eviction). Footstep/splash loaders still bypass it intoFootstepConfig/WaterAudioConfig. The map isOption<Arc<_>>:Noneis a negative-cache entry (loader returned no bytes, or decode failed) thatget_or_loadnever re-probes andlen()/bytes_estimate()exclude — so a sound missing at first use stays missing untilclear(), which nothing in production calls (check whether the--sounds-bsaset can change at runtime). Still no eviction: a consumer keyed on open-ended paths (FOOT, REGN, per-NPC SFX) that lands onSoundCacheis the finding.VoiceSoundCache(#5382, 8410ad1e5) is the open-ended-key cache, used only by dialogue voice: sameget_or_loadcontract, LRU-evicted pastDEFAULT_BUDGET_BYTES(64 MiB; the just-inserted entry is never its own victim), negatives capped atVOICE_NEGATIVE_CAP(4096; the set clears and re-probes past it) and counted (negative_len). Telemetry reportsvoice_cache_{entries,bytes,negatives}throughownership_sample.rs; the save registry allowlists it. A new open-ended consumer belongs here (or in its own bounded cache), not inSoundCache. 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::newfailure leavesmanager = None; every public API gates on it with nounwrap(); headless/CI boot must succeed (report as PASS/FAIL in the summary).AudioWorld::headless()never contacts a device; tests use it, not the device-dependentdefault(). SUB_TRACK_CAPACITY(512) andSEND_TRACK_CAPACITY(32) exceed kira's defaults and are applied innew().- Field-drop order (single source of truth):
active_sounds→pending_oneshots→ (oneshots_requested) →music→reverb_send→reverb_send_db→listener→manager, then the trailingmulti_listener_warned/underwaterflags and theemitter_position_updatescounter. A reorder that movesmanagerup asserts in kira's Drop.AudioWorld::new()is boot-only, never on cell transition or resize. - Schedule (all
Stage::Late):audio_systemis an exclusive registered afterfootstep_system(exclusive, pinned to Late by #4185's test) andwater_audio_system(exclusive, aftersubmersion_system);reverb_zone_systemis 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_systembody order:sync_listener_pose→sync_emitter_positions→update_underwater_filters→drain_pending_oneshots→dispatch_new_oneshots→prune_stopped_sounds. The conversation voice producer (Latenpc_dialogue_selection_system) registers AFTERaudio_system, so a line's segments dispatch on the next frame — one frame of latency, not a loss; the ObScriptSayproducerscripted_line_voice_systemis Update, so its segments dispatch the same frame. - Despawn truncation: only
SoundSource::Emitterentries are emitter-coupled (emitter_coupled()): an entity that losesAudioEmittergets a tweenedstop()overActiveSound.unload_fade_ms(10 ms default, looping and not);stop_issueddebounces (and is set bystop_sounds_fortoo); retain drops only onStopped.Detachedruns to natural end;Anchoredsurvives SFX emitter churn on the same NPC by design and stops viastop_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 noGlobalTransform(#5678 74d841165; theanchored_voice_follows_survives_prune_and_stopslifecycle test is#[ignore]d, the orphan selection is pinned default-lane byonly_transformless_anchored_sources_are_prune_orphans). Guards are#[ignore]d (Phase 1): trace the code. - Cross-cell continuity:
dispatch_region_ambient_musicruns only whenmusic_formchanged (bothcell_loader/load.rspaths andscene::apply_cell_region_ambientcompare against the priorRegionAmbientRes); 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 afterfootstep_system: drainsPhysicsWorld::take_contact_force_events— thestartedforce crossings, belowImpactConfig::min_force_magnitudeskipped — and plays one{1.0, 24.0}m one-shot per impact atPhysicsWorld::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_playskips only an unmapped impact, neverbreaks the frame, #5554; the whole system returns early with nodefault_soundand nosurface_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-physicsDim 2)),water_audio_systemandcombat_anim.rs::play_oneshot_cached(PostUpdate, enqueued before Lateaudio_system) are liveplay_oneshotcallers, withsystems/scripted_sound.rs::scripted_sound_system(Update exclusive, M47.2 5046c7af8/f093e7393: drainsPendingSoundPlays, resolves the SNDR/SOUN throughindex.sounds, skips looping descriptors, takes multi-ANAMvariants in turn, positions at the source'sGlobalTransformand plays throughplay_oneshot_cached— note this puts script-authored, open-ended sound paths on the unboundedSoundCache, Dim 3) andbyroredux/src/systems/dialogue_voice.rsas further callers (#5367 Phase V: per-line voice — FO3/FNV.ogg, Oblivion race/sex.mp3#5526, Skyrim+.fuzthroughbyroredux_audio::fuz+ optionalffmpeg#5912 — fromapply_selectionand the Updatescripted_line_voice_system(ObScriptSay/SayTo, whose resolved duration timesSayToDone), throughplay_oneshot_following, anchored to the speaker and skipped when it has noGlobalTransform(#5410) and cut bystop_line_voiceon topic change / close / speaker death (#5432); segments chained viabyroredux_audio::with_start_delay, each named by its authoredresponse_number;Attenuation::default(), volume 1.0; gated onAudioWorld::is_activebefore any archive work; decoded throughVoiceSoundCachewith the provider read inside the loader (#5382/#5383); file name and owning plugin perdocs/engine/dialogue-trees.md(#5393/#5395, 5b68792b7;.ogg/.fuznames 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_systemthe onlyset_reverb_send_dbcaller,dispatch_region_ambient_musicthe onlyplay_music/stop_musiccaller. 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 toGlobalTransformdeltas with no conversion at the compare site; reset to 0 on fire (subtract-remainder multiplies footsteps on a teleport); first tick seedslast_positionand fires nothing. Attenuation{0.5, 12.0}in metres (no pre-scaling by 70, no widening). TheFootstepScratchVec(capacity 32) is restored on the success and theAudioWorld-absent path; no component lock is held acrossplay_oneshotand the scratch lock drops beforeAudioWorldis taken. The player-body gate (CharacterControllergrounded flag + body handle) is resolved as an owned(body, gated)pair BEFORE anyGlobalTransform/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 throughPhysicsWorld::surface_material_under, which takes a MANDATORYexcluded_body(the player'sRapierHandles.body, else the capsule centre self-hits at toi 0 and every player step reads material 0, #5545 3909778eb);select_footstep_soundis a pure function of aFootstepConfig.surface_soundssnapshot cloned once, after the no-trigger early return and BEFORE theAudioWorldguard (noAudioWorld→FootstepConfiglock edge, #5556 ac710964e); the Access row declares thePhysicsWorld/RapierHandlesreads (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 — theActiveCameraentity whilePlayerModeisCharacter, 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--flyor toggled. The body accumulates only whileCharacterController.is_grounded; the gate is!is_groundedALONE since #5347 (7068c16d3) becauseresolve_ground_contactnever grounds a swimmer (character::tests::a_swimmer_is_never_grounded) — if swimming ever stops clearingis_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 noPhysicsWorldeach 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 thePlayerEntity/PlayerMode/ActiveCamera/CharacterControllerreads and still lists aWaterContactread the system no longer takes (harmless over-declaration since #5347). - Water audio:
water_audio_systemwritesAudioWorld::set_underwaterfrom the active camera'sSubmersionState.head_submergedevery frame (before the config early-return), then playsSplashEventone-shots (range 1-24 m) and, when no splash fired, one cooldown-limitedRippleEvent(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_soundisNonewithout an archive (valid headless state);try_load_default_footstepandtry_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 theBYRO_NO_AI_LOCOMOTIONblock since #4709 (70d9896fb) — a move back inside silently mutes them; hardcoded Skyrim paths perdocs/engine/p2-combat-anim-sound-fixture.md, each request counted per kind inCombatSoundRequests(swing / impact / death_voice; incremented only when the one-shot was actually requested, surfaced bycombat.status, #5148 7208af67c), attenuation{0.5, 24.0}m (uncited, like the water range); lookup goes throughSoundCache::get_or_loadwhose loader readsSoundArchiveProviderwhile theSoundCachewrite guard is held (resource-in-resource nesting). Dialogue voice uses the same cache → provider order onVoiceSoundCache(#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_orderdrives 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 dBinterior /NEG_INFINITYexterior; the transition is bit-equality gated (to_bits), no-ops withoutCellLightingRes(pre-load) orAudioWorld(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);loopingthreads fromsound_loops(SOUN Loop bit) intoplay_music.SounRecord::sound_pathis a file or a trailing-separator folder (about half of FNV's FNAM-bearing records):sound_is_folderfails closed before any archive lookup, and any futureresolve_sound_pathconsumer must gate on it (variant selection is an unmade policy, so no "extract any file under this folder" fallback exists).- Archive precedence:
SoundArchiveProvider::extractiterates.rev()(last-listed wins, as mesh/texture/material; FNV'sFallout - Sound.bsa+Update.bsaoverlap with differing bytes): forward iteration is the regression.ScriptProvideris 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 annexesCe 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.mdDepuis 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.
Bonnes pratiques React Hook Form
Developpement
Maîtrisez React Hook Form : évitez les anti-patterns, gérez les abonnements, et traitez correctement reset, dirty state et entrées contrôlées.
Migration Emotion vers Mantine
Developpement
Migrer les styled-components Emotion vers des composants Mantine avec style props et modules CSS. Pour convertir .styled.tsx ou supprimer les imports @emotion.
Détecteur de dérive de périmètre
Developpement
Analyse les diffs git par rapport à une intention pour détecter la dérive de périmètre, fichiers non liés et PR trop larges, puis suggère de garder, diviser ou justifier.