slice-4 (dev-a): MediaThread + session_map rewire (Task 6 + 7) #12

Merged
alee merged 3 commits from slice-4-dev-a-reflex into main 2026-07-04 04:37:32 +00:00
Showing only changes of commit 86d55c3e67 - Show all commits

View File

@@ -0,0 +1,61 @@
// crates/rutster/tests/barge_in_integration.rs
//
// PRIMARY-path barge-in e2e proof (slice-4 spec §3.4, §6.1, §7 done-criteria #8).
//
// Constructs LocalVadReflex<Reflex<TapAudioPipe>> directly and proves that loud
// caller audio kills playout within one tick, with only the local VAD as the
// trigger source — no brain advisory is involved. This is wedge #1 from
// README:98-100 and ARCHITECTURE.md:79-81.
use rutster_media::{AudioSink, AudioSource, PcmFrame, VAD_DEBOUNCE_FRAMES};
use rutster_media::{LocalVadReflex, Reflex, ReflexMetrics};
use rutster_tap::{TapAudioPipe, TapMetrics};
use tokio::sync::mpsc;
#[tokio::test]
async fn primary_path_local_vad_kills_playout_without_brain() {
let (tx_pcm_in, _rx_pcm_in) = mpsc::channel(32);
let (tx_audio_out, rx_audio_out) = mpsc::channel(32);
let tap_metrics = TapMetrics::new();
let pipe = TapAudioPipe::new(tx_pcm_in, rx_audio_out, tap_metrics);
let (advisory_tx, advisory_rx) = mpsc::channel(16);
let reflex_metrics = ReflexMetrics::new();
let reflex = Reflex::new(pipe, advisory_rx, reflex_metrics.clone());
let mut stack = LocalVadReflex::new(reflex, advisory_tx);
// Pre-load a brain audio_out frame into the ring (drain via next_pcm_frame).
tx_audio_out.send(PcmFrame::zeroed()).await.unwrap();
let _ = stack.next_pcm_frame(); // drain into ring
stack.next_pcm_frame(); // pop one (brain is playing)
// Loud caller audio × N → local VAD trips.
let mut loud = PcmFrame::zeroed();
for s in loud.samples.iter_mut() {
*s = 1000;
}
for _ in 0..VAD_DEBOUNCE_FRAMES {
stack.on_pcm_frame(loud.clone());
}
// Next playout tick: kill applied, None returned (ring cleared).
let f = stack.next_pcm_frame();
assert!(
f.is_none(),
"primary-path local VAD must kill playout within 1 tick"
);
assert_eq!(
reflex_metrics
.barge_in_count
.load(std::sync::atomic::Ordering::Relaxed),
1
);
// Fresh brain audio_out → resume.
tx_audio_out.send(PcmFrame::zeroed()).await.unwrap();
let resumed = stack.next_pcm_frame();
assert!(
resumed.is_some(),
"first fresh audio_out post-barge must resume playout"
);
}