Files
rutster/crates/rutster/tests/barge_in_integration.rs
Aaron D. Lee 6aac5b148b
All checks were successful
CI / fmt (pull_request) Successful in 1m1s
CI / clippy (pull_request) Successful in 1m24s
CI / test (1.85) (pull_request) Successful in 4m10s
CI / test (stable) (pull_request) Successful in 4m17s
CI / deny (pull_request) Successful in 1m16s
test(slice-4): primary-path barge-in e2e — local VAD kills playout without brain
This integration test proves wedge #1: loud synthetic caller audio
(samples = 1000, well above VAD_RMS_THRESHOLD = 500.0) sent through
LocalVadReflex<Reflex<TapAudioPipe>> trips the local VAD and kills
playout within one tick, without any brain advisory.

Cites README:98-100 and ARCHITECTURE.md:79-81 ("local reflexes that
don't need the brain"); see slice-4 spec §3.4, §6.1, and §7
done-criteria #8.

Signed-off-by: Aaron D. Lee <himself@adlee.work>
2026-07-03 09:51:37 -04:00

62 lines
2.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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"
);
}