From 78435f71454727b0a26e7a7b7f31284c8bff3664 Mon Sep 17 00:00:00 2001 From: "Aaron D. Lee" Date: Sat, 4 Jul 2026 01:53:59 +0000 Subject: [PATCH] =?UTF-8?q?slice-4=20(dev-a):=20Task=209=20Step=201=20?= =?UTF-8?q?=E2=80=94=20primary-path=20barge-in=20e2e=20(wedge=20#1)=20(#10?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Aaron D. Lee Co-committed-by: Aaron D. Lee --- crates/rutster/tests/barge_in_integration.rs | 61 ++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 crates/rutster/tests/barge_in_integration.rs diff --git a/crates/rutster/tests/barge_in_integration.rs b/crates/rutster/tests/barge_in_integration.rs new file mode 100644 index 0000000..2a44731 --- /dev/null +++ b/crates/rutster/tests/barge_in_integration.rs @@ -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> 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" + ); +}