use super::correlation::{ candidate_index_offsets, collapse_segments_by_phase, correlate_onsets, estimate_phase, index_onsets_by_spacing, marker_index_offsets, marker_onsets, shortest_wrapped_difference, }; use super::{ PulseSegment, VideoColorFrame, correlate_coded_segments, correlate_segments, detect_audio_onsets, detect_audio_segments, detect_coded_audio_segments, detect_color_coded_video_segments, detect_video_onsets, detect_video_segments, median, }; use crate::sync_probe::analyze::report::SyncAnalysisReport; use std::collections::BTreeMap; #[test] /// Keeps `detect_video_onsets_finds_bright_transitions` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn detect_video_onsets_finds_bright_transitions() { let timestamps = (0..60).map(|idx| idx as f64 / 10.0).collect::>(); let brightness = timestamps .iter() .enumerate() .map(|(idx, _)| { if idx == 0 || idx == 10 || idx == 20 { 250 } else { 5 } }) .collect::>(); let onsets = detect_video_onsets(×tamps, &brightness).expect("video onsets"); assert_eq!(onsets, vec![0.0, 0.95, 1.95]); } #[test] /// Keeps `detect_audio_onsets_finds_click_bursts` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn detect_audio_onsets_finds_click_bursts() { let mut samples = vec![0i16; 48_000]; for start in [0usize, 48_000 / 2] { for sample in samples.iter_mut().skip(start).take(300) { *sample = 18_000; } } let onsets = detect_audio_onsets(&samples, 48_000, 5).expect("audio onsets"); assert_eq!(onsets.len(), 2); assert!((onsets[0] - 0.0).abs() < 0.01); assert!((onsets[1] - 0.5).abs() < 0.02); } #[test] fn detect_video_segments_keeps_regular_and_marker_durations_distinct() { let timestamps = (0..30).map(|idx| idx as f64 / 30.0).collect::>(); let brightness = [ 0, 255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; let segments = detect_video_segments(×tamps, &brightness).expect("video segments"); assert_eq!(segments.len(), 2); assert!(segments[1].duration_s > segments[0].duration_s); } #[test] /// Keeps `detect_video_segments_ignores_dim_positioning_prelude` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn detect_video_segments_ignores_dim_positioning_prelude() { let timestamps = (0..90).map(|idx| idx as f64 / 30.0).collect::>(); let brightness = timestamps .iter() .enumerate() .map(|(idx, _)| { if (45..49).contains(&idx) || (75..79).contains(&idx) { 245 } else { 8 } }) .collect::>(); let segments = detect_video_segments(×tamps, &brightness).expect("video segments"); assert_eq!(segments.len(), 2); assert!( segments[0].start_s > 1.4, "dim pre-start positioning screen must not become a fake onset" ); } #[test] /// Keeps `detect_color_coded_video_segments_ignores_generic_bright_changes` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn detect_color_coded_video_segments_ignores_generic_bright_changes() { let timestamps = (0..80).map(|idx| idx as f64 / 20.0).collect::>(); let frames = timestamps .iter() .enumerate() .map(|(idx, _)| match idx { 0..=4 => VideoColorFrame { r: 245, g: 245, b: 245, }, 20..=22 => VideoColorFrame { r: 255, g: 45, b: 45, }, 40..=42 => VideoColorFrame { r: 0, g: 230, b: 118, }, 60..=63 => VideoColorFrame { r: 137, g: 133, b: 101, }, _ => VideoColorFrame { r: 0, g: 0, b: 0 }, }) .collect::>(); let segments = detect_color_coded_video_segments(×tamps, &frames, &[1, 2], 0.12).expect("segments"); assert_eq!(segments.len(), 2); assert!(segments[0].start_s > 0.9); assert!((segments[0].duration_s - 0.12).abs() < 0.001); assert!((segments[1].duration_s - 0.24).abs() < 0.001); } #[test] /// Keeps `detect_color_coded_video_segments_accepts_camera_washed_palette` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn detect_color_coded_video_segments_accepts_camera_washed_palette() { let timestamps = (0..90).map(|idx| idx as f64 / 30.0).collect::>(); let frames = timestamps .iter() .enumerate() .map(|(idx, _)| match idx { 10..=12 => VideoColorFrame { r: 184, g: 72, b: 68, }, 30..=34 => VideoColorFrame { r: 76, g: 168, b: 111, }, 50..=55 => VideoColorFrame { r: 82, g: 125, b: 188, }, 70..=76 => VideoColorFrame { r: 190, g: 173, b: 60, }, _ => VideoColorFrame { r: 22, g: 22, b: 24, }, }) .collect::>(); let segments = detect_color_coded_video_segments(×tamps, &frames, &[1, 2, 3, 4], 0.12) .expect("segments"); assert_eq!(segments.len(), 4); assert!((segments[0].duration_s - 0.12).abs() < 0.001); assert!((segments[1].duration_s - 0.24).abs() < 0.001); assert!((segments[2].duration_s - 0.36).abs() < 0.001); assert!((segments[3].duration_s - 0.48).abs() < 0.001); } #[test] /// Keeps `detect_audio_segments_keeps_regular_and_marker_durations_distinct` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn detect_audio_segments_keeps_regular_and_marker_durations_distinct() { let mut samples = vec![0i16; 48_000]; for sample in samples.iter_mut().take(3_000) { *sample = 18_000; } for sample in samples.iter_mut().skip(24_000).take(6_000) { *sample = 18_000; } let segments = detect_audio_segments(&samples, 48_000, 5).expect("audio segments"); assert_eq!(segments.len(), 2); assert!(segments[1].duration_s > segments[0].duration_s); } #[test] /// Keeps `detect_audio_segments_merges_short_internal_dropouts_inside_one_pulse` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn detect_audio_segments_merges_short_internal_dropouts_inside_one_pulse() { let mut samples = vec![0i16; 48_000]; for sample in samples.iter_mut().skip(4_800).take(5_760) { *sample = 18_000; } for sample in samples.iter_mut().skip(7_200).take(1_920) { *sample = 0; } let segments = detect_audio_segments(&samples, 48_000, 5).expect("audio segments"); assert_eq!(segments.len(), 1); assert!(segments[0].duration_s > 0.11); } #[test] /// Keeps `detect_audio_segments_accepts_faint_probe_tones` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn detect_audio_segments_accepts_faint_probe_tones() { let mut samples = vec![0i16; 48_000]; for start in [4_800usize, 24_000] { for sample in samples.iter_mut().skip(start).take(5_760) { *sample = 40; } } let segments = detect_audio_segments(&samples, 48_000, 5).expect("faint audio segments"); assert_eq!(segments.len(), 2); assert!((segments[0].start_s - 0.1).abs() < 0.01); assert!((segments[1].start_s - 0.5).abs() < 0.01); } #[test] fn detect_audio_segments_locks_onto_probe_tone_over_background_hum() { let mut samples = vec![0i16; 96_000]; add_sine(&mut samples, 48_000, 0.0, 2.0, 120.0, 7_000.0); add_sine(&mut samples, 48_000, 0.25, 0.12, 880.0, 1_800.0); add_sine(&mut samples, 48_000, 1.25, 0.12, 880.0, 1_800.0); let segments = detect_audio_segments(&samples, 48_000, 10).expect("tone segments"); assert_eq!(segments.len(), 2); assert!((segments[0].start_s - 0.25).abs() < 0.03); assert!((segments[1].start_s - 1.25).abs() < 0.03); } #[test] /// Keeps `detect_coded_audio_segments_uses_probe_tone_frequency_for_event_code` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn detect_coded_audio_segments_uses_probe_tone_frequency_for_event_code() { let mut samples = vec![0i16; 96_000]; add_sine(&mut samples, 48_000, 0.0, 2.0, 120.0, 7_000.0); add_sine(&mut samples, 48_000, 0.25, 0.07, 620.0, 2_000.0); add_sine(&mut samples, 48_000, 1.25, 0.07, 1120.0, 2_000.0); let segments = detect_coded_audio_segments(&samples, 48_000, 10, &[1, 2, 3, 4], 0.12).expect("segments"); assert_eq!(segments.len(), 2); assert!((segments[0].start_s - 0.25).abs() < 0.03); assert!((segments[0].duration_s - 0.12).abs() < 0.001); assert!((segments[1].start_s - 1.25).abs() < 0.03); assert!((segments[1].duration_s - 0.48).abs() < 0.001); } #[test] /// Keeps `detect_audio_segments_merges_longer_probe_dropouts_inside_one_pulse` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn detect_audio_segments_merges_longer_probe_dropouts_inside_one_pulse() { let mut samples = vec![0i16; 48_000]; for sample in samples.iter_mut().skip(4_800).take(12_000) { *sample = 1_200; } for sample in samples.iter_mut().skip(7_200).take(5_760) { *sample = 0; } let segments = detect_audio_segments(&samples, 48_000, 5).expect("dropout audio segment"); assert_eq!(segments.len(), 1); assert!(segments[0].duration_s > 0.24); } /// Keeps `add_sine` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn add_sine( samples: &mut [i16], sample_rate_hz: u32, start_s: f64, duration_s: f64, frequency_hz: f64, amplitude: f64, ) { let start = (start_s * f64::from(sample_rate_hz)).round() as usize; let len = (duration_s * f64::from(sample_rate_hz)).round() as usize; for (offset, sample) in samples.iter_mut().skip(start).take(len).enumerate() { let t = offset as f64 / f64::from(sample_rate_hz); let value = f64::from(*sample) + amplitude * (2.0 * std::f64::consts::PI * frequency_hz * t).sin(); *sample = value .round() .clamp(f64::from(i16::MIN), f64::from(i16::MAX)) as i16; } } #[test] fn detect_video_segments_closes_a_pulse_that_stays_active_until_the_last_frame() { let timestamps = [0.0, 0.1, 0.2, 0.3]; let brightness = [0, 0, 255, 255]; let segments = detect_video_segments(×tamps, &brightness).expect("trailing video segment"); assert_eq!(segments.len(), 1); assert!(segments[0].end_s > segments[0].start_s); assert!(segments[0].end_s >= 0.3); } #[test] /// Keeps `detect_audio_segments_closes_a_click_that_stays_active_until_the_capture_ends` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn detect_audio_segments_closes_a_click_that_stays_active_until_the_capture_ends() { let mut samples = vec![0i16; 4_800]; let midpoint = samples.len() / 2; for sample in samples.iter_mut().skip(midpoint) { *sample = 18_000; } let segments = detect_audio_segments(&samples, 48_000, 5).expect("trailing audio segment"); assert_eq!(segments.len(), 1); assert!(segments[0].end_s > segments[0].start_s); } #[test] fn correlate_onsets_reports_skew_and_drift() { let report = correlate_onsets(&[0.0, 1.0, 2.0, 3.0], &[0.05, 1.04, 2.03, 3.02], 1.0, 0.2) .expect("correlated report"); assert_sync_report_shape(&report, 4); assert!((report.first_skew_ms - 50.0).abs() < 0.001); assert!((report.last_skew_ms - 20.0).abs() < 0.001); assert!((report.drift_ms + 30.0).abs() < 0.001); assert!(report.max_abs_skew_ms >= 50.0); } #[test] fn correlate_onsets_single_pulse_uses_phase_fallback() { let report = correlate_onsets(&[0.95], &[0.05], 1.0, 0.2).expect("single-pulse fallback"); assert_eq!(report.paired_event_count, 1); assert!((report.first_skew_ms - 100.0).abs() < 0.001); } #[test] /// Keeps `correlate_onsets_ignores_leading_video_cadence_before_audio_becomes_active` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn correlate_onsets_ignores_leading_video_cadence_before_audio_becomes_active() { let report = correlate_onsets( &[0.15, 1.15, 2.15, 3.15, 10.15, 11.15, 12.15], &[10.20, 11.20, 12.20], 1.0, 0.2, ) .expect("correlated report"); assert_eq!(report.video_event_count, 3); assert_eq!(report.audio_event_count, 3); assert_eq!(report.paired_event_count, 3); assert!((report.median_skew_ms - 50.0).abs() < 0.001); assert!(report.max_abs_skew_ms < 60.0); } #[test] /// Keeps `correlate_onsets_prefers_the_phase_consistent_basin_over_a_larger_alias_cluster` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn correlate_onsets_prefers_the_phase_consistent_basin_over_a_larger_alias_cluster() { let report = correlate_onsets( &[ 9.554, 10.5035, 11.487, 12.4365, 13.691, 14.505, 15.624, 16.438, 17.3535, 18.4385, 19.5575, 20.4395, 21.4905, 22.508, 23.4235, 24.5425, 25.5605, 26.4425, 27.4935, 28.5105, ], &[ 10.011041666666667, 11.011041666666667, 12.011041666666667, 13.011041666666667, 14.011041666666667, 15.011041666666667, 16.011041666666667, 17.011041666666667, 18.011041666666667, 19.011041666666667, 20.011041666666667, 21.011041666666667, 22.011041666666667, 23.011041666666667, 24.011041666666667, 25.011041666666667, 26.011041666666667, 27.011041666666667, 28.011041666666667, 29.011041666666667, ], 1.0, 0.5, ) .expect("phase-consistent basin"); assert!(report.first_skew_ms > 400.0); assert!(report.median_skew_ms > 350.0); assert!(report.paired_event_count >= 6); } #[test] fn detect_video_onsets_rejects_empty_low_contrast_and_missing_edges() { assert!(detect_video_onsets(&[], &[]).is_err()); assert!(detect_video_onsets(&[0.0, 0.1], &[10, 12]).is_err()); assert!(detect_video_onsets(&[0.0, 0.1, 0.2], &[255, 255, 255]).is_err()); } #[test] /// Keeps `detect_video_onsets_rejects_frame_to_frame_flicker` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn detect_video_onsets_rejects_frame_to_frame_flicker() { let timestamps = (0..120) .map(|index| index as f64 / 30.0) .collect::>(); let brightness = (0..120) .map(|index| if index % 2 == 0 { 0 } else { 6 }) .collect::>(); let err = detect_video_onsets(×tamps, &brightness).expect_err("flicker should be rejected"); assert!( err.to_string().contains("frame-to-frame flicker"), "unexpected error: {err}" ); } #[test] fn detect_audio_onsets_rejects_empty_invalid_and_too_quiet_inputs() { assert!(detect_audio_onsets(&[], 48_000, 5).is_err()); assert!(detect_audio_onsets(&[1, 2, 3], 0, 5).is_err()); assert!(detect_audio_onsets(&[1, 2, 3], 48_000, 0).is_err()); assert!(detect_audio_onsets(&vec![1i16; 4_800], 48_000, 5).is_err()); } #[test] fn correlate_onsets_rejects_empty_inputs_invalid_gap_and_unpairable_events() { assert!(correlate_onsets(&[], &[0.0], 1.0, 0.2).is_err()); assert!(correlate_onsets(&[0.0], &[], 1.0, 0.2).is_err()); assert!(correlate_onsets(&[0.0], &[0.0], 1.0, 0.0).is_err()); assert!(correlate_onsets(&[0.0, 1.0], &[2.0, 3.0], 1.0, 0.1).is_err()); assert!(correlate_onsets(&[0.0], &[0.0], 0.0, 0.1).is_err()); } #[test] /// Keeps `correlate_segments_validate_inputs_and_support_single_pulse_fallback` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn correlate_segments_validate_inputs_and_support_single_pulse_fallback() { let video = [PulseSegment { start_s: 0.95, end_s: 1.05, duration_s: 0.1, }]; let audio = [PulseSegment { start_s: 0.05, end_s: 0.15, duration_s: 0.1, }]; let report = correlate_segments(&video, &audio, 1.0, 0.1, 3, 0.2).expect("single segment fallback"); assert_eq!(report.paired_event_count, 1); assert!((report.first_skew_ms - 100.0).abs() < 0.001); assert!(correlate_segments(&[], &audio, 1.0, 0.1, 3, 0.2).is_err()); assert!(correlate_segments(&video, &[], 1.0, 0.1, 3, 0.2).is_err()); assert!(correlate_segments(&video, &audio, 0.0, 0.1, 3, 0.2).is_err()); assert!(correlate_segments(&video, &audio, 1.0, 0.0, 3, 0.2).is_err()); assert!(correlate_segments(&video, &audio, 1.0, 0.1, 0, 0.2).is_err()); assert!(correlate_segments(&video, &audio, 1.0, 0.1, 3, 0.0).is_err()); assert!(correlate_segments(&video, &audio, 1.0, 0.1, 3, 0.05).is_err()); } #[test] /// Keeps `correlate_segments_preserves_whole_period_delay_evidence` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn correlate_segments_preserves_whole_period_delay_evidence() { fn segment(start_s: f64, duration_s: f64) -> PulseSegment { PulseSegment { start_s, end_s: start_s + duration_s, duration_s, } } let video = (0..30) .map(|tick| segment(f64::from(tick), if tick % 5 == 0 { 0.24 } else { 0.12 })) .collect::>(); let audio = (0..30) .map(|tick| { segment( f64::from(tick) + 20.0, if tick % 5 == 0 { 0.24 } else { 0.12 }, ) }) .collect::>(); let report = correlate_segments(&video, &audio, 1.0, 0.12, 5, 0.5).expect("correlated report"); assert_eq!(report.activity_start_delta_ms, 20_000.0); assert!( report.max_abs_skew_ms < 1.0, "cadence aliasing still creates apparently good pairs" ); let verdict = report.verdict(); assert_eq!(verdict.status, "catastrophic_failure"); assert!( verdict.reason.contains("activity starts"), "unexpected verdict reason: {}", verdict.reason ); } #[test] /// Keeps `correlate_coded_segments_preserves_raw_activity_before_cadence_cleanup` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn correlate_coded_segments_preserves_raw_activity_before_cadence_cleanup() { fn segment(start_s: f64, code: u32) -> PulseSegment { let duration_s = 0.12 * f64::from(code); PulseSegment { start_s, end_s: start_s + duration_s, duration_s, } } let codes = [1, 2, 1, 3, 2, 4]; let video = codes .iter() .enumerate() .map(|(tick, code)| segment(8.3 + tick as f64, *code)) .collect::>(); let audio = codes .iter() .enumerate() .map(|(tick, code)| segment(6.7 + tick as f64, *code)) .collect::>(); let report = correlate_coded_segments(&video, &audio, 1.0, 0.12, &codes, 0.5).expect("coded report"); assert!(report.activity_start_delta_ms < -1_000.0); assert_eq!(report.verdict().status, "catastrophic_failure"); } #[test] /// Keeps `correlate_coded_segments_reports_large_but_decodable_skew` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn correlate_coded_segments_reports_large_but_decodable_skew() { fn segment(start_s: f64, code: u32) -> PulseSegment { let duration_s = 0.12 * f64::from(code); PulseSegment { start_s, end_s: start_s + duration_s, duration_s, } } let codes = [1, 2, 1, 3, 2, 4, 1, 1, 3, 1, 4, 2]; let video = codes .iter() .enumerate() .map(|(tick, code)| segment(tick as f64, *code)) .collect::>(); let audio = codes .iter() .enumerate() .map(|(tick, code)| segment(tick as f64 - 0.75, *code)) .collect::>(); let report = correlate_coded_segments(&video, &audio, 1.0, 0.12, &codes, 0.5).expect("coded report"); assert_eq!(report.paired_event_count, codes.len()); assert!((report.activity_start_delta_ms + 750.0).abs() < 1.0); assert!(report.max_abs_skew_ms > 700.0); assert_eq!(report.verdict().status, "gross_failure"); } #[test] /// Keeps `correlate_coded_segments_matches_preserved_event_width_codes` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn correlate_coded_segments_matches_preserved_event_width_codes() { fn segment(start_s: f64, code: u32) -> PulseSegment { let duration_s = 0.12 * f64::from(code); PulseSegment { start_s, end_s: start_s + duration_s, duration_s, } } let codes = [1, 2, 1, 3, 2, 4, 1, 1]; let video = codes .iter() .enumerate() .map(|(tick, code)| segment(tick as f64, *code)) .collect::>(); let audio = codes .iter() .enumerate() .map(|(tick, code)| segment(tick as f64 + 0.045, *code)) .collect::>(); let report = correlate_coded_segments(&video, &audio, 1.0, 0.12, &codes, 0.2).expect("coded report"); assert_eq!(report.paired_event_count, codes.len()); assert!((report.median_skew_ms - 45.0).abs() < 1.0); assert!(report.max_abs_skew_ms < 50.0); } #[test] /// Keeps `correlate_coded_segments_recovers_when_extra_video_detections_win_phase_collapse` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn correlate_coded_segments_recovers_when_extra_video_detections_win_phase_collapse() { fn segment(start_s: f64, code: u32) -> PulseSegment { let duration_s = 0.12 * f64::from(code); PulseSegment { start_s, end_s: start_s + duration_s, duration_s, } } let codes = [1, 2, 1, 3, 2, 4, 1, 1]; let mut video = Vec::new(); for (tick, code) in codes.iter().copied().enumerate() { video.push(segment(tick as f64, code)); video.push(segment(tick as f64 + 0.45, 4)); } let audio = codes .iter() .enumerate() .map(|(tick, code)| segment(tick as f64 + 0.045, *code)) .collect::>(); let report = correlate_coded_segments(&video, &audio, 1.0, 0.12, &codes, 0.2).expect("coded report"); assert_eq!(report.paired_event_count, codes.len()); assert!((report.median_skew_ms - 45.0).abs() < 1.0); assert!(report.max_abs_skew_ms < 50.0); } #[test] /// Keeps `correlate_coded_segments_rejects_nearby_wrong_width_codes` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn correlate_coded_segments_rejects_nearby_wrong_width_codes() { fn segment(start_s: f64, code: u32) -> PulseSegment { let duration_s = 0.12 * f64::from(code); PulseSegment { start_s, end_s: start_s + duration_s, duration_s, } } let codes = [1, 2, 1, 3, 2, 4]; let video = codes .iter() .enumerate() .map(|(tick, code)| segment(tick as f64, *code)) .collect::>(); let shifted_audio_codes = [3, 1, 4, 1, 2, 1]; let audio = shifted_audio_codes .iter() .enumerate() .map(|(tick, code)| segment(tick as f64 + 0.02, *code)) .collect::>(); assert!(correlate_coded_segments(&video, &audio, 1.0, 0.12, &codes, 0.2).is_err()); } #[test] /// Keeps `correlate_coded_segments_refuses_cadence_fallback_when_windows_do_not_overlap` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew. /// Inputs are the typed parameters; output is the return value or side effect. fn correlate_coded_segments_refuses_cadence_fallback_when_windows_do_not_overlap() { fn segment(start_s: f64, code: u32) -> PulseSegment { let duration_s = 0.12 * f64::from(code); PulseSegment { start_s, end_s: start_s + duration_s, duration_s, } } let codes = [1, 2, 3, 4, 5, 6]; let video = codes .iter() .enumerate() .map(|(tick, code)| segment(100.0 + tick as f64, *code)) .collect::>(); let audio = codes .iter() .enumerate() .map(|(tick, code)| segment(tick as f64, *code)) .collect::>(); let error = correlate_coded_segments(&video, &audio, 1.0, 0.12, &codes, 0.5) .expect_err("coded proof should not fall back to cadence-only pairing"); assert!( error.to_string().contains("refusing cadence-only fallback"), "unexpected error: {error}" ); } fn assert_sync_report_shape(report: &SyncAnalysisReport, paired_events: usize) { assert_eq!(report.video_event_count, paired_events); assert_eq!(report.audio_event_count, paired_events); assert_eq!(report.paired_event_count, paired_events); assert_eq!(report.skews_ms.len(), paired_events); assert_eq!(report.video_onsets_s.len(), paired_events); assert_eq!(report.audio_onsets_s.len(), paired_events); } mod correlation_helpers;