1use crate::report_violation;
2use crate::telemetry::{ViolationKind, ViolationSeverity};
3use crate::Frame;
4
5const DEFAULT_FRAME_WINDOW_SIZE: usize = 30;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32#[must_use = "TimeSyncConfig has no effect unless passed to SessionBuilder::with_time_sync_config()"]
33pub struct TimeSyncConfig {
34 pub window_size: usize,
41}
42
43impl Default for TimeSyncConfig {
44 fn default() -> Self {
45 Self {
46 window_size: DEFAULT_FRAME_WINDOW_SIZE,
47 }
48 }
49}
50
51impl std::fmt::Display for TimeSyncConfig {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 let Self { window_size } = self;
55 write!(f, "TimeSyncConfig {{ window_size: {} }}", window_size)
56 }
57}
58
59impl TimeSyncConfig {
60 pub fn new() -> Self {
62 Self::default()
63 }
64
65 pub fn responsive() -> Self {
70 Self { window_size: 15 }
71 }
72
73 pub fn smooth() -> Self {
78 Self { window_size: 60 }
79 }
80
81 pub fn lan() -> Self {
85 Self { window_size: 10 }
86 }
87
88 pub fn mobile() -> Self {
97 Self { window_size: 90 }
98 }
99
100 pub fn competitive() -> Self {
106 Self { window_size: 20 }
107 }
108}
109
110#[derive(Debug)]
120pub struct TimeSync {
121 local: Vec<i32>,
122 remote: Vec<i32>,
123 window_size: usize,
124}
125
126impl Default for TimeSync {
127 fn default() -> Self {
128 Self::with_config(TimeSyncConfig::default())
129 }
130}
131
132impl TimeSync {
133 #[must_use]
135 pub fn new() -> Self {
136 Self::default()
137 }
138
139 #[must_use]
141 pub fn with_config(config: TimeSyncConfig) -> Self {
142 let window_size = config.window_size.max(1); Self {
144 local: vec![0; window_size],
145 remote: vec![0; window_size],
146 window_size,
147 }
148 }
149
150 pub fn advance_frame(&mut self, frame: Frame, local_adv: i32, remote_adv: i32) {
152 if frame.is_null() || frame.as_i32() < 0 {
156 report_violation!(
157 ViolationSeverity::Warning,
158 ViolationKind::FrameSync,
159 "TimeSync::advance_frame called with invalid frame {:?}, skipping update",
160 frame
161 );
162 return;
163 }
164 let index = frame.as_i32() as usize % self.window_size;
165 if let Some(local_slot) = self.local.get_mut(index) {
166 *local_slot = local_adv;
167 }
168 if let Some(remote_slot) = self.remote.get_mut(index) {
169 *remote_slot = remote_adv;
170 }
171 }
172
173 #[must_use]
181 pub fn average_frame_advantage(&self) -> i32 {
182 let local_sum: i32 = self.local.iter().sum();
183 let remote_sum: i32 = self.remote.iter().sum();
184 let count = self.local.len() as i32;
186
187 (remote_sum - local_sum) / (2 * count)
190 }
191}
192
193#[cfg(test)]
198#[allow(
199 clippy::panic,
200 clippy::unwrap_used,
201 clippy::expect_used,
202 clippy::indexing_slicing
203)]
204mod sync_layer_tests {
205
206 use super::*;
207
208 const FRAME_WINDOW_SIZE: usize = 30;
210
211 #[test]
212 fn test_advance_frame_no_advantage() {
213 let mut time_sync = TimeSync::default();
214
215 for i in 0..60i32 {
216 let local_adv = 0;
217 let remote_adv = 0;
218 time_sync.advance_frame(Frame::new(i), local_adv, remote_adv)
219 }
220
221 assert_eq!(time_sync.average_frame_advantage(), 0);
222 }
223
224 #[test]
225 fn test_advance_frame_local_advantage() {
226 let mut time_sync = TimeSync::default();
227
228 for i in 0..60i32 {
229 let local_adv = 5;
230 let remote_adv = -5;
231 time_sync.advance_frame(Frame::new(i), local_adv, remote_adv)
232 }
233
234 assert_eq!(time_sync.average_frame_advantage(), -5);
235 }
236
237 #[test]
238 fn test_advance_frame_small_remote_advantage() {
239 let mut time_sync = TimeSync::default();
240
241 for i in 0..60i32 {
242 let local_adv = -1;
243 let remote_adv = 1;
244 time_sync.advance_frame(Frame::new(i), local_adv, remote_adv)
245 }
246
247 assert_eq!(time_sync.average_frame_advantage(), 1);
248 }
249
250 #[test]
251 fn test_advance_frame_remote_advantage() {
252 let mut time_sync = TimeSync::default();
253
254 for i in 0..60i32 {
255 let local_adv = -4;
256 let remote_adv = 4;
257 time_sync.advance_frame(Frame::new(i), local_adv, remote_adv)
258 }
259
260 assert_eq!(time_sync.average_frame_advantage(), 4);
261 }
262
263 #[test]
264 fn test_advance_frame_big_remote_advantage() {
265 let mut time_sync = TimeSync::default();
266
267 for i in 0..60i32 {
268 let local_adv = -40;
269 let remote_adv = 40;
270 time_sync.advance_frame(Frame::new(i), local_adv, remote_adv)
271 }
272
273 assert_eq!(time_sync.average_frame_advantage(), 40);
274 }
275
276 #[test]
277 fn test_new_creates_default() {
278 let time_sync = TimeSync::new();
279 assert_eq!(time_sync.average_frame_advantage(), 0);
281 }
282
283 #[test]
284 fn test_window_sliding_behavior() {
285 let mut time_sync = TimeSync::default();
286
287 for i in 0..FRAME_WINDOW_SIZE {
289 time_sync.advance_frame(Frame::new(i as i32), 10, -10);
290 }
291 assert_eq!(time_sync.average_frame_advantage(), -10);
292
293 for i in FRAME_WINDOW_SIZE..(FRAME_WINDOW_SIZE * 2) {
295 time_sync.advance_frame(Frame::new(i as i32), -10, 10);
296 }
297 assert_eq!(time_sync.average_frame_advantage(), 10);
299 }
300
301 #[test]
302 fn test_partial_window_fill() {
303 let mut time_sync = TimeSync::default();
304
305 for i in 0..(FRAME_WINDOW_SIZE / 2) {
307 time_sync.advance_frame(Frame::new(i as i32), 10, -10);
308 }
309
310 assert_eq!(time_sync.average_frame_advantage(), -5);
315 }
316
317 #[test]
318 fn test_asymmetric_advantages() {
319 let mut time_sync = TimeSync::default();
320
321 for i in 0..FRAME_WINDOW_SIZE {
323 time_sync.advance_frame(Frame::new(i as i32), 0, 6);
324 }
325
326 assert_eq!(time_sync.average_frame_advantage(), 3);
329 }
330
331 #[test]
332 fn test_frame_wraparound_modulo() {
333 let mut time_sync = TimeSync::default();
334
335 let large_frame = Frame::new(1000);
337 time_sync.advance_frame(large_frame, 5, -5);
338
339 assert_eq!(time_sync.local[10], 5);
341 assert_eq!(time_sync.remote[10], -5);
342 }
343
344 #[test]
345 fn test_advance_frame_null_frame_skipped() {
346 let mut time_sync = TimeSync::default();
347
348 time_sync.advance_frame(Frame::new(0), 10, 20);
350 assert_eq!(time_sync.local[0], 10);
351 assert_eq!(time_sync.remote[0], 20);
352
353 time_sync.advance_frame(Frame::NULL, 99, 99);
355
356 assert_eq!(time_sync.local[0], 10); }
361
362 #[test]
363 fn test_advance_frame_negative_frame_skipped() {
364 let mut time_sync = TimeSync::default();
365
366 time_sync.advance_frame(Frame::new(5), 10, 20);
368
369 time_sync.advance_frame(Frame::new(-5), 99, 99);
371
372 }
374
375 struct ConfigPresetTestCase {
381 name: &'static str,
382 config: TimeSyncConfig,
383 expected_window_size: usize,
384 }
385
386 #[test]
388 fn test_config_presets_data_driven() {
389 let test_cases = [
390 ConfigPresetTestCase {
391 name: "default",
392 config: TimeSyncConfig::default(),
393 expected_window_size: 30,
394 },
395 ConfigPresetTestCase {
396 name: "new",
397 config: TimeSyncConfig::new(),
398 expected_window_size: 30,
399 },
400 ConfigPresetTestCase {
401 name: "responsive",
402 config: TimeSyncConfig::responsive(),
403 expected_window_size: 15,
404 },
405 ConfigPresetTestCase {
406 name: "smooth",
407 config: TimeSyncConfig::smooth(),
408 expected_window_size: 60,
409 },
410 ConfigPresetTestCase {
411 name: "lan",
412 config: TimeSyncConfig::lan(),
413 expected_window_size: 10,
414 },
415 ConfigPresetTestCase {
416 name: "mobile",
417 config: TimeSyncConfig::mobile(),
418 expected_window_size: 90,
419 },
420 ConfigPresetTestCase {
421 name: "competitive",
422 config: TimeSyncConfig::competitive(),
423 expected_window_size: 20,
424 },
425 ];
426
427 for test_case in &test_cases {
428 let ts = TimeSync::with_config(test_case.config);
429 assert_eq!(
430 ts.window_size, test_case.expected_window_size,
431 "Config preset '{}' should have window_size={}, got={}",
432 test_case.name, test_case.expected_window_size, ts.window_size
433 );
434 assert_eq!(
435 ts.local.len(),
436 test_case.expected_window_size,
437 "Config preset '{}' should have local.len()={}, got={}",
438 test_case.name,
439 test_case.expected_window_size,
440 ts.local.len()
441 );
442 assert_eq!(
443 ts.remote.len(),
444 test_case.expected_window_size,
445 "Config preset '{}' should have remote.len()={}, got={}",
446 test_case.name,
447 test_case.expected_window_size,
448 ts.remote.len()
449 );
450 assert_eq!(
452 ts.average_frame_advantage(),
453 0,
454 "Config preset '{}' should have initial average=0, got={}",
455 test_case.name,
456 ts.average_frame_advantage()
457 );
458 }
459 }
460
461 #[test]
466 fn test_time_sync_config_display() {
467 let config = TimeSyncConfig { window_size: 30 };
468 assert_eq!(config.to_string(), "TimeSyncConfig { window_size: 30 }");
469
470 let config = TimeSyncConfig { window_size: 60 };
471 assert_eq!(config.to_string(), "TimeSyncConfig { window_size: 60 }");
472 }
473
474 #[test]
480 fn test_window_size_zero_corrected_to_one() {
481 let config = TimeSyncConfig { window_size: 0 };
482 let ts = TimeSync::with_config(config);
483
484 assert_eq!(ts.window_size, 1, "Window size 0 should be corrected to 1");
485 assert_eq!(ts.local.len(), 1, "Local vec should have length 1");
486 assert_eq!(ts.remote.len(), 1, "Remote vec should have length 1");
487 }
488
489 #[test]
491 fn test_window_size_minimum_one() {
492 let config = TimeSyncConfig { window_size: 1 };
493 let mut ts = TimeSync::with_config(config);
494
495 ts.advance_frame(Frame::new(0), 10, 5);
497 assert_eq!(ts.average_frame_advantage(), -2); ts.advance_frame(Frame::new(1), -10, 10);
500 assert_eq!(ts.average_frame_advantage(), 10); assert_eq!(ts.local[0], -10);
504 assert_eq!(ts.remote[0], 10);
505 }
506
507 #[test]
509 fn test_large_frame_number() {
510 let mut ts = TimeSync::default();
511
512 let large_frame = Frame::new(i32::MAX);
514 ts.advance_frame(large_frame, 5, 10);
515
516 let expected_index = (i32::MAX as usize) % 30;
518 assert_eq!(
519 ts.local[expected_index], 5,
520 "Large frame value should map to index {}, got local[{}]={}",
521 expected_index, expected_index, ts.local[expected_index]
522 );
523 }
524
525 #[test]
527 fn test_extreme_advantage_values() {
528 let mut ts = TimeSync::default();
529
530 for i in 0..30 {
533 ts.advance_frame(Frame::new(i), 1000, -1000);
534 }
535
536 let avg = ts.average_frame_advantage();
537 assert_eq!(
538 avg, -1000,
539 "Average should be -1000 for local=1000, remote=-1000"
540 );
541 }
542
543 #[test]
545 fn test_mixed_advantage_values() {
546 let mut ts = TimeSync::default();
547
548 for i in 0..30 {
550 if i % 2 == 0 {
551 ts.advance_frame(Frame::new(i), 10, -10);
552 } else {
553 ts.advance_frame(Frame::new(i), -10, 10);
554 }
555 }
556
557 let avg = ts.average_frame_advantage();
559 assert_eq!(avg, 0, "Mixed values should average to 0");
560 }
561
562 #[test]
564 fn test_frame_zero() {
565 let mut ts = TimeSync::default();
566
567 ts.advance_frame(Frame::new(0), 42, -42);
568
569 assert_eq!(ts.local[0], 42, "Frame 0 should map to index 0");
570 assert_eq!(ts.remote[0], -42, "Frame 0 should map to index 0");
571 }
572}
573
574#[cfg(test)]
583#[allow(
584 clippy::panic,
585 clippy::unwrap_used,
586 clippy::expect_used,
587 clippy::indexing_slicing
588)]
589mod property_tests {
590 use super::*;
591 use crate::test_config::miri_case_count;
592 use proptest::prelude::*;
593
594 const MAX_FRAME: i32 = 10_000;
596
597 const MAX_ADVANTAGE: i32 = 100;
599
600 fn valid_frame() -> impl Strategy<Value = Frame> {
602 (0..MAX_FRAME).prop_map(Frame::new)
603 }
604
605 fn advantage_value() -> impl Strategy<Value = i32> {
607 -MAX_ADVANTAGE..=MAX_ADVANTAGE
608 }
609
610 fn window_size() -> impl Strategy<Value = usize> {
612 1..=100usize
613 }
614
615 proptest! {
616 #![proptest_config(ProptestConfig {
617 cases: miri_case_count(),
618 ..ProptestConfig::default()
619 })]
620 #[test]
625 fn prop_window_index_in_bounds(
626 frame in valid_frame(),
627 local_adv in advantage_value(),
628 remote_adv in advantage_value(),
629 window_size in window_size(),
630 ) {
631 let config = TimeSyncConfig { window_size };
632 let mut ts = TimeSync::with_config(config);
633
634 ts.advance_frame(frame, local_adv, remote_adv);
636
637 let expected_index = frame.as_i32() as usize % window_size;
639 prop_assert!(expected_index < window_size);
640 prop_assert_eq!(ts.local[expected_index], local_adv);
641 prop_assert_eq!(ts.remote[expected_index], remote_adv);
642 }
643
644 #[test]
649 fn prop_average_bounded_by_inputs(
650 local_adv in advantage_value(),
651 remote_adv in advantage_value(),
652 ) {
653 let mut ts = TimeSync::default();
654
655 for i in 0..30 {
657 ts.advance_frame(Frame::new(i), local_adv, remote_adv);
658 }
659
660 let avg = ts.average_frame_advantage();
661 let expected = (remote_adv - local_adv) / 2;
662
663 prop_assert_eq!(avg, expected);
665 }
666
667 #[test]
671 fn prop_average_deterministic(
672 frames in proptest::collection::vec(
673 (valid_frame(), advantage_value(), advantage_value()),
674 1..100
675 ),
676 ) {
677 let mut ts1 = TimeSync::default();
678 let mut ts2 = TimeSync::default();
679
680 for (frame, local, remote) in &frames {
681 ts1.advance_frame(*frame, *local, *remote);
682 ts2.advance_frame(*frame, *local, *remote);
683 }
684
685 prop_assert_eq!(
686 ts1.average_frame_advantage(),
687 ts2.average_frame_advantage(),
688 "Same inputs should produce same average"
689 );
690 }
691
692 #[test]
696 fn prop_null_frame_no_effect(
697 initial_frames in proptest::collection::vec(
698 (0..30i32, advantage_value(), advantage_value()),
699 10..30
700 ),
701 ) {
702 let mut ts = TimeSync::default();
703
704 for (frame_val, local, remote) in &initial_frames {
706 ts.advance_frame(Frame::new(*frame_val), *local, *remote);
707 }
708
709 let avg_before = ts.average_frame_advantage();
710
711 ts.advance_frame(Frame::NULL, 999, 999);
713
714 let avg_after = ts.average_frame_advantage();
715
716 prop_assert_eq!(avg_before, avg_after, "NULL frame should not modify state");
718 }
719
720 #[test]
724 fn prop_negative_frame_no_effect(
725 initial_frames in proptest::collection::vec(
726 (0..30i32, advantage_value(), advantage_value()),
727 10..30
728 ),
729 neg_frame in -1000..-1i32,
730 ) {
731 let mut ts = TimeSync::default();
732
733 for (frame_val, local, remote) in &initial_frames {
735 ts.advance_frame(Frame::new(*frame_val), *local, *remote);
736 }
737
738 let avg_before = ts.average_frame_advantage();
739
740 ts.advance_frame(Frame::new(neg_frame), 999, 999);
742
743 let avg_after = ts.average_frame_advantage();
744
745 prop_assert_eq!(avg_before, avg_after, "Negative frame should not modify state");
746 }
747
748 #[test]
752 fn prop_window_slides(window_size in 5..50usize) {
753 let config = TimeSyncConfig { window_size };
754 let mut ts = TimeSync::with_config(config);
755
756 for i in 0..window_size {
758 ts.advance_frame(Frame::new(i as i32), 10, -10);
759 }
760
761 let avg_initial = ts.average_frame_advantage();
762
763 for i in 0..window_size {
765 ts.advance_frame(Frame::new((window_size + i) as i32), -10, 10);
766 }
767
768 let avg_after = ts.average_frame_advantage();
769
770 prop_assert_eq!(avg_initial, -10, "Initial average incorrect");
774 prop_assert_eq!(avg_after, 10, "After-slide average incorrect");
775 }
776
777 #[test]
781 fn prop_average_formula_correct(
782 local_adv in advantage_value(),
783 remote_adv in advantage_value(),
784 ) {
785 let mut ts = TimeSync::default();
786
787 for i in 0..30 {
789 ts.advance_frame(Frame::new(i), local_adv, remote_adv);
790 }
791
792 let avg = ts.average_frame_advantage();
793
794 let expected = (remote_adv - local_adv) / 2;
797 prop_assert_eq!(avg, expected);
798 }
799
800 #[test]
802 fn prop_custom_window_size_respected(window_size in 1..100usize) {
803 let config = TimeSyncConfig { window_size };
804 let ts = TimeSync::with_config(config);
805
806 prop_assert_eq!(ts.window_size, window_size);
807 prop_assert_eq!(ts.local.len(), window_size);
808 prop_assert_eq!(ts.remote.len(), window_size);
809 }
810 }
811}
812
813#[cfg(kani)]
827mod kani_proofs {
828 use super::*;
829
830 #[kani::proof]
839 fn proof_window_index_in_bounds() {
840 let frame_val: i32 = kani::any();
841 kani::assume(frame_val >= 0);
842
843 let window_size: usize = kani::any();
844 kani::assume(window_size >= 1 && window_size <= 1000);
845
846 let index = (frame_val as usize) % window_size;
847
848 kani::assert(index < window_size, "Index must be less than window size");
849 }
850
851 #[kani::proof]
860 fn proof_sum_no_overflow() {
861 let window_size: usize = kani::any();
863 kani::assume(window_size >= 1 && window_size <= 10);
864
865 let advantage: i32 = kani::any();
867 kani::assume(advantage >= -1000 && advantage <= 1000);
868
869 let sum = advantage.checked_mul(window_size as i32);
871 kani::assert(sum.is_some(), "Multiplication should not overflow");
872 }
873
874 #[kani::proof]
882 fn proof_division_safe() {
883 let window_size: usize = kani::any();
884 kani::assume(window_size >= 1 && window_size <= 1000);
885
886 let config = TimeSyncConfig { window_size };
887 let ts = TimeSync::with_config(config);
888
889 kani::assert(ts.local.len() >= 1, "Window length must be at least 1");
891 kani::assert(ts.remote.len() >= 1, "Window length must be at least 1");
892 }
893
894 #[kani::proof]
902 fn proof_advance_frame_safe() {
903 let frame_val: i32 = kani::any();
904 kani::assume(frame_val >= 0 && frame_val < 10000);
905
906 let local_adv: i32 = kani::any();
907 let remote_adv: i32 = kani::any();
908 kani::assume(local_adv >= -1000 && local_adv <= 1000);
909 kani::assume(remote_adv >= -1000 && remote_adv <= 1000);
910
911 let config = TimeSyncConfig { window_size: 30 };
912 let mut ts = TimeSync::with_config(config);
913
914 ts.advance_frame(Frame::new(frame_val), local_adv, remote_adv);
916
917 let idx = (frame_val as usize) % 30;
919 kani::assert(ts.local[idx] == local_adv, "Local value should be stored");
920 kani::assert(
921 ts.remote[idx] == remote_adv,
922 "Remote value should be stored",
923 );
924 }
925
926 #[kani::proof]
937 fn proof_window_size_minimum() {
938 let window_size: usize = kani::any();
939 kani::assume(window_size <= 10000);
944 let config = TimeSyncConfig { window_size };
946 let ts = TimeSync::with_config(config);
947
948 kani::assert(ts.window_size >= 1, "Window size must be at least 1");
949 kani::assert(
950 ts.local.len() >= 1,
951 "Local vec must have at least 1 element",
952 );
953 kani::assert(
954 ts.remote.len() >= 1,
955 "Remote vec must have at least 1 element",
956 );
957 }
958
959 #[kani::proof]
969 #[kani::unwind(32)]
970 fn proof_default_valid() {
971 let ts = TimeSync::default();
972
973 kani::assert(ts.window_size == 30, "Default window size should be 30");
974 kani::assert(
975 ts.local.len() == 30,
976 "Default local vec length should be 30",
977 );
978 kani::assert(
979 ts.remote.len() == 30,
980 "Default remote vec length should be 30",
981 );
982 kani::assert(
983 ts.average_frame_advantage() == 0,
984 "Initial average should be 0",
985 );
986 }
987
988 #[kani::proof]
999 fn proof_preset_configs_valid() {
1000 let preset_choice: u8 = kani::any();
1002 kani::assume(preset_choice < 5);
1003
1004 let config = match preset_choice {
1005 0 => TimeSyncConfig::responsive(),
1006 1 => TimeSyncConfig::smooth(),
1007 2 => TimeSyncConfig::lan(),
1008 3 => TimeSyncConfig::competitive(),
1009 _ => TimeSyncConfig::mobile(),
1010 };
1011
1012 let ts = TimeSync::with_config(config);
1013
1014 kani::assert(
1016 ts.window_size >= 1,
1017 "All presets must have window_size >= 1",
1018 );
1019 kani::assert(
1020 ts.local.len() == ts.window_size,
1021 "local vec length must match window_size",
1022 );
1023 kani::assert(
1024 ts.remote.len() == ts.window_size,
1025 "remote vec length must match window_size",
1026 );
1027 }
1028
1029 #[kani::proof]
1037 fn proof_zero_window_size_corrected() {
1038 let config = TimeSyncConfig { window_size: 0 };
1039 let ts = TimeSync::with_config(config);
1040
1041 kani::assert(ts.window_size == 1, "window_size 0 must be corrected to 1");
1042 kani::assert(ts.local.len() == 1, "local vec must have exactly 1 element");
1043 kani::assert(
1044 ts.remote.len() == 1,
1045 "remote vec must have exactly 1 element",
1046 );
1047 }
1048
1049 #[kani::proof]
1062 fn proof_index_wrapping_consistent() {
1063 let frame_val: i32 = kani::any();
1064 kani::assume(frame_val >= 0 && frame_val <= 10_000);
1066
1067 let window_size: usize = kani::any();
1068 kani::assume(window_size >= 1 && window_size <= 100);
1069
1070 let index1 = (frame_val as usize) % window_size;
1072
1073 let index2 = (frame_val as usize) % window_size;
1075
1076 kani::assert(index1 == index2, "Index calculation must be deterministic");
1077 kani::assert(
1078 index1 < window_size,
1079 "Index must be strictly less than window_size",
1080 );
1081 }
1082
1083 #[kani::proof]
1093 fn proof_negative_frame_safe() {
1094 let frame_val: i32 = kani::any();
1095 kani::assume(frame_val < 0);
1096
1097 let local_adv: i32 = kani::any();
1098 let remote_adv: i32 = kani::any();
1099 kani::assume(local_adv >= -1000 && local_adv <= 1000);
1100 kani::assume(remote_adv >= -1000 && remote_adv <= 1000);
1101
1102 let config = TimeSyncConfig { window_size: 10 };
1103 let mut ts = TimeSync::with_config(config);
1104
1105 ts.advance_frame(Frame::new(frame_val), local_adv, remote_adv);
1107
1108 kani::assert(
1110 ts.local[0] == 0,
1111 "Negative frame should not modify local values",
1112 );
1113 kani::assert(
1114 ts.remote[0] == 0,
1115 "Negative frame should not modify remote values",
1116 );
1117 }
1118}