2026-04-24 14:49:57 -03:00
use super ::correlation ::{
2026-04-25 05:35:16 -03:00
candidate_index_offsets , collapse_segments_by_phase , correlate_onsets , estimate_phase ,
index_onsets_by_spacing , marker_index_offsets , marker_onsets , shortest_wrapped_difference ,
2026-04-24 14:49:57 -03:00
} ;
use super ::{
2026-05-01 12:38:16 -03:00
PulseSegment , VideoColorFrame , correlate_coded_segments , correlate_segments ,
2026-05-02 21:40:45 -03:00
detect_audio_onsets , detect_audio_segments , detect_coded_audio_segments ,
detect_color_coded_video_segments , detect_video_onsets , detect_video_segments , median ,
2026-04-24 14:49:57 -03:00
} ;
use crate ::sync_probe ::analyze ::report ::SyncAnalysisReport ;
use std ::collections ::BTreeMap ;
#[ test ]
2026-05-06 05:50:59 -03:00
/// 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.
2026-04-24 14:49:57 -03:00
fn detect_video_onsets_finds_bright_transitions ( ) {
let timestamps = ( 0 .. 60 ) . map ( | idx | idx as f64 / 10.0 ) . collect ::< Vec < _ > > ( ) ;
let brightness = timestamps
. iter ( )
. enumerate ( )
. map ( | ( idx , _ ) | {
if idx = = 0 | | idx = = 10 | | idx = = 20 {
250
} else {
5
}
} )
. collect ::< Vec < _ > > ( ) ;
let onsets = detect_video_onsets ( & timestamps , & brightness ) . expect ( " video onsets " ) ;
assert_eq! ( onsets , vec! [ 0.0 , 0.95 , 1.95 ] ) ;
}
#[ test ]
2026-05-06 05:50:59 -03:00
/// 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.
2026-04-24 14:49:57 -03:00
fn detect_audio_onsets_finds_click_bursts ( ) {
let mut samples = vec! [ 0 i16 ; 48_000 ] ;
for start in [ 0 usize , 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 ::< Vec < _ > > ( ) ;
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 ( & timestamps , & brightness ) . expect ( " video segments " ) ;
assert_eq! ( segments . len ( ) , 2 ) ;
assert! ( segments [ 1 ] . duration_s > segments [ 0 ] . duration_s ) ;
}
2026-05-01 12:38:16 -03:00
#[ test ]
2026-05-06 05:50:59 -03:00
/// 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.
2026-05-01 12:38:16 -03:00
fn detect_video_segments_ignores_dim_positioning_prelude ( ) {
let timestamps = ( 0 .. 90 ) . map ( | idx | idx as f64 / 30.0 ) . collect ::< Vec < _ > > ( ) ;
let brightness = timestamps
. iter ( )
. enumerate ( )
. map ( | ( idx , _ ) | {
if ( 45 .. 49 ) . contains ( & idx ) | | ( 75 .. 79 ) . contains ( & idx ) {
245
} else {
8
}
} )
. collect ::< Vec < _ > > ( ) ;
let segments = detect_video_segments ( & timestamps , & 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 ]
2026-05-06 05:50:59 -03:00
/// 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.
2026-05-01 12:38:16 -03:00
fn detect_color_coded_video_segments_ignores_generic_bright_changes ( ) {
let timestamps = ( 0 .. 80 ) . map ( | idx | idx as f64 / 20.0 ) . collect ::< Vec < _ > > ( ) ;
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 ,
} ,
2026-05-04 15:50:26 -03:00
40 ..= 42 = > VideoColorFrame {
2026-05-01 12:38:16 -03:00
r : 0 ,
g : 230 ,
b : 118 ,
} ,
2026-05-01 13:35:59 -03:00
60 ..= 63 = > VideoColorFrame {
r : 137 ,
g : 133 ,
b : 101 ,
} ,
2026-05-01 12:38:16 -03:00
_ = > VideoColorFrame { r : 0 , g : 0 , b : 0 } ,
} )
. collect ::< Vec < _ > > ( ) ;
let segments =
detect_color_coded_video_segments ( & timestamps , & 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 ) ;
}
2026-05-02 21:40:45 -03:00
#[ test ]
2026-05-06 05:50:59 -03:00
/// 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.
2026-05-02 21:40:45 -03:00
fn detect_color_coded_video_segments_accepts_camera_washed_palette ( ) {
let timestamps = ( 0 .. 90 ) . map ( | idx | idx as f64 / 30.0 ) . collect ::< Vec < _ > > ( ) ;
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 ::< Vec < _ > > ( ) ;
let segments = detect_color_coded_video_segments ( & timestamps , & 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 ) ;
}
2026-04-24 14:49:57 -03:00
#[ test ]
2026-05-06 05:50:59 -03:00
/// 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.
2026-04-24 14:49:57 -03:00
fn detect_audio_segments_keeps_regular_and_marker_durations_distinct ( ) {
let mut samples = vec! [ 0 i16 ; 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 ) ;
}
2026-05-01 13:35:59 -03:00
#[ test ]
2026-05-06 05:50:59 -03:00
/// 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.
2026-05-01 13:35:59 -03:00
fn detect_audio_segments_merges_short_internal_dropouts_inside_one_pulse ( ) {
let mut samples = vec! [ 0 i16 ; 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 ) ;
}
2026-05-02 16:32:03 -03:00
#[ test ]
2026-05-06 05:50:59 -03:00
/// 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.
2026-05-02 16:32:03 -03:00
fn detect_audio_segments_accepts_faint_probe_tones ( ) {
let mut samples = vec! [ 0 i16 ; 48_000 ] ;
for start in [ 4_800 usize , 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 ) ;
}
2026-05-02 21:40:45 -03:00
#[ test ]
fn detect_audio_segments_locks_onto_probe_tone_over_background_hum ( ) {
let mut samples = vec! [ 0 i16 ; 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 ]
2026-05-06 05:50:59 -03:00
/// 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.
2026-05-02 21:40:45 -03:00
fn detect_coded_audio_segments_uses_probe_tone_frequency_for_event_code ( ) {
let mut samples = vec! [ 0 i16 ; 96_000 ] ;
add_sine ( & mut samples , 48_000 , 0.0 , 2.0 , 120.0 , 7_000.0 ) ;
2026-05-04 15:50:26 -03:00
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 ) ;
2026-05-02 21:40:45 -03:00
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 ) ;
}
2026-05-02 16:32:03 -03:00
#[ test ]
2026-05-06 05:50:59 -03:00
/// 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.
2026-05-02 16:32:03 -03:00
fn detect_audio_segments_merges_longer_probe_dropouts_inside_one_pulse ( ) {
let mut samples = vec! [ 0 i16 ; 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 ) ;
}
2026-05-06 05:50:59 -03:00
/// 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.
2026-05-02 21:40:45 -03:00
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 ;
}
}
2026-04-24 14:49:57 -03:00
#[ 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 ( & timestamps , & 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 ]
2026-05-06 05:50:59 -03:00
/// 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.
2026-04-24 14:49:57 -03:00
fn detect_audio_segments_closes_a_click_that_stays_active_until_the_capture_ends ( ) {
let mut samples = vec! [ 0 i16 ; 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 ) ;
}
2026-04-27 04:49:44 -03:00
#[ test ]
2026-05-06 05:50:59 -03:00
/// 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.
2026-04-27 04:49:44 -03:00
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 ) ;
}
2026-04-27 06:52:14 -03:00
#[ test ]
2026-05-06 05:50:59 -03:00
/// 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.
2026-04-27 06:52:14 -03:00
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 ) ;
}
2026-04-24 14:49:57 -03:00
#[ 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 ( ) ) ;
}
2026-04-27 13:35:18 -03:00
#[ test ]
2026-05-06 05:50:59 -03:00
/// 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.
2026-04-27 13:35:18 -03:00
fn detect_video_onsets_rejects_frame_to_frame_flicker ( ) {
2026-04-29 01:25:06 -03:00
let timestamps = ( 0 .. 120 )
. map ( | index | index as f64 / 30.0 )
. collect ::< Vec < _ > > ( ) ;
2026-04-27 13:35:18 -03:00
let brightness = ( 0 .. 120 )
. map ( | index | if index % 2 = = 0 { 0 } else { 6 } )
. collect ::< Vec < _ > > ( ) ;
2026-04-29 01:25:06 -03:00
let err =
detect_video_onsets ( & timestamps , & brightness ) . expect_err ( " flicker should be rejected " ) ;
2026-04-27 13:35:18 -03:00
assert! (
2026-04-29 01:25:06 -03:00
err . to_string ( ) . contains ( " frame-to-frame flicker " ) ,
2026-04-27 13:35:18 -03:00
" unexpected error: {err} "
) ;
}
2026-04-24 14:49:57 -03:00
#[ 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! [ 1 i16 ; 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 ]
2026-05-06 05:50:59 -03:00
/// 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.
2026-04-24 14:49:57 -03:00
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 ( ) ) ;
}
2026-05-01 01:26:24 -03:00
#[ test ]
2026-05-06 05:50:59 -03:00
/// 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.
2026-05-01 01:26:24 -03:00
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 ::< Vec < _ > > ( ) ;
let audio = ( 0 .. 30 )
. map ( | tick | {
segment (
f64 ::from ( tick ) + 20.0 ,
if tick % 5 = = 0 { 0.24 } else { 0.12 } ,
)
} )
. collect ::< Vec < _ > > ( ) ;
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
) ;
}
2026-05-01 13:35:59 -03:00
#[ test ]
2026-05-06 05:50:59 -03:00
/// 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.
2026-05-01 13:35:59 -03:00
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 ::< Vec < _ > > ( ) ;
let audio = codes
. iter ( )
. enumerate ( )
. map ( | ( tick , code ) | segment ( 6.7 + tick as f64 , * code ) )
. collect ::< Vec < _ > > ( ) ;
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 ]
2026-05-06 05:50:59 -03:00
/// 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.
2026-05-01 13:35:59 -03:00
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 ::< Vec < _ > > ( ) ;
let audio = codes
. iter ( )
. enumerate ( )
. map ( | ( tick , code ) | segment ( tick as f64 - 0.75 , * code ) )
. collect ::< Vec < _ > > ( ) ;
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 " ) ;
}
2026-05-01 02:05:07 -03:00
#[ test ]
2026-05-06 05:50:59 -03:00
/// 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.
2026-05-01 02:05:07 -03:00
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 ::< Vec < _ > > ( ) ;
let audio = codes
. iter ( )
. enumerate ( )
. map ( | ( tick , code ) | segment ( tick as f64 + 0.045 , * code ) )
. collect ::< Vec < _ > > ( ) ;
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 ) ;
}
2026-05-02 21:40:45 -03:00
#[ test ]
2026-05-06 05:50:59 -03:00
/// 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.
2026-05-02 21:40:45 -03:00
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 ::< Vec < _ > > ( ) ;
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 ) ;
}
2026-05-01 02:05:07 -03:00
#[ test ]
2026-05-06 05:50:59 -03:00
/// 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.
2026-05-01 02:05:07 -03:00
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 ::< Vec < _ > > ( ) ;
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 ::< Vec < _ > > ( ) ;
assert! ( correlate_coded_segments ( & video , & audio , 1.0 , 0.12 , & codes , 0.2 ) . is_err ( ) ) ;
}
2026-05-04 16:58:55 -03:00
#[ test ]
2026-05-06 05:50:59 -03:00
/// 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.
2026-05-04 16:58:55 -03:00
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 ::< Vec < _ > > ( ) ;
let audio = codes
. iter ( )
. enumerate ( )
. map ( | ( tick , code ) | segment ( tick as f64 , * code ) )
. collect ::< Vec < _ > > ( ) ;
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} "
) ;
}
2026-04-24 14:49:57 -03:00
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 ) ;
}
2026-04-29 01:25:06 -03:00
mod correlation_helpers ;