1use std::collections::{HashMap, VecDeque};
4
5#[derive(Debug, Clone)]
7pub struct InterpolationSample {
8 pub timestamp_ms: u64,
9 pub position: [f32; 3],
10 pub rotation: [f32; 4],
11 pub velocity: [f32; 3],
12 pub extra: Vec<(u16, Vec<u8>)>,
13}
14
15impl InterpolationSample {
16 pub fn new(timestamp_ms: u64, position: [f32; 3], rotation: [f32; 4]) -> Self {
17 Self {
18 timestamp_ms,
19 position,
20 rotation,
21 velocity: [0.0; 3],
22 extra: Vec::new(),
23 }
24 }
25
26 pub fn with_velocity(mut self, velocity: [f32; 3]) -> Self {
27 self.velocity = velocity;
28 self
29 }
30
31 pub fn with_extra(mut self, component_type: u16, data: Vec<u8>) -> Self {
32 self.extra.push((component_type, data));
33 self
34 }
35
36 pub fn lerp(&self, other: &InterpolationSample, t: f32) -> InterpolationSample {
38 let t = t.clamp(0.0, 1.0);
39 let inv = 1.0 - t;
40
41 let position = [
42 self.position[0] * inv + other.position[0] * t,
43 self.position[1] * inv + other.position[1] * t,
44 self.position[2] * inv + other.position[2] * t,
45 ];
46
47 let velocity = [
48 self.velocity[0] * inv + other.velocity[0] * t,
49 self.velocity[1] * inv + other.velocity[1] * t,
50 self.velocity[2] * inv + other.velocity[2] * t,
51 ];
52
53 let rotation = quat_slerp(self.rotation, other.rotation, t);
55
56 InterpolationSample {
57 timestamp_ms: self.timestamp_ms + ((other.timestamp_ms as f64 - self.timestamp_ms as f64) * t as f64) as u64,
58 position,
59 rotation,
60 velocity,
61 extra: if t < 0.5 { self.extra.clone() } else { other.extra.clone() },
62 }
63 }
64
65 pub fn extrapolate(&self, dt_ms: u64) -> InterpolationSample {
67 let dt_sec = dt_ms as f32 / 1000.0;
68 InterpolationSample {
69 timestamp_ms: self.timestamp_ms + dt_ms,
70 position: [
71 self.position[0] + self.velocity[0] * dt_sec,
72 self.position[1] + self.velocity[1] * dt_sec,
73 self.position[2] + self.velocity[2] * dt_sec,
74 ],
75 rotation: self.rotation,
76 velocity: self.velocity,
77 extra: self.extra.clone(),
78 }
79 }
80}
81
82fn quat_slerp(a: [f32; 4], b: [f32; 4], t: f32) -> [f32; 4] {
84 let mut dot = a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
85 let mut b = b;
86
87 if dot < 0.0 {
89 b = [-b[0], -b[1], -b[2], -b[3]];
90 dot = -dot;
91 }
92
93 if dot > 0.9999 {
95 let result = [
96 a[0] + t * (b[0] - a[0]),
97 a[1] + t * (b[1] - a[1]),
98 a[2] + t * (b[2] - a[2]),
99 a[3] + t * (b[3] - a[3]),
100 ];
101 return quat_normalize(result);
102 }
103
104 let theta = dot.clamp(-1.0, 1.0).acos();
105 let sin_theta = theta.sin();
106
107 if sin_theta.abs() < 1e-6 {
108 return a;
109 }
110
111 let s0 = ((1.0 - t) * theta).sin() / sin_theta;
112 let s1 = (t * theta).sin() / sin_theta;
113
114 [
115 a[0] * s0 + b[0] * s1,
116 a[1] * s0 + b[1] * s1,
117 a[2] * s0 + b[2] * s1,
118 a[3] * s0 + b[3] * s1,
119 ]
120}
121
122fn quat_normalize(q: [f32; 4]) -> [f32; 4] {
123 let len = (q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]).sqrt();
124 if len < 1e-10 {
125 return [0.0, 0.0, 0.0, 1.0];
126 }
127 [q[0] / len, q[1] / len, q[2] / len, q[3] / len]
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
132pub struct SyncEntityId(pub u32);
133
134pub struct InterpolationBuffer {
137 entity_id: SyncEntityId,
138 samples: VecDeque<InterpolationSample>,
139 max_samples: usize,
140 interp_delay_ms: u64,
141 max_extrapolation_ms: u64,
142 last_interpolated: Option<InterpolationSample>,
143 hermite_enabled: bool,
144}
145
146impl InterpolationBuffer {
147 pub fn new(entity_id: SyncEntityId, interp_delay_ms: u64) -> Self {
148 Self {
149 entity_id,
150 samples: VecDeque::new(),
151 max_samples: 32,
152 interp_delay_ms,
153 max_extrapolation_ms: 250,
154 last_interpolated: None,
155 hermite_enabled: false,
156 }
157 }
158
159 pub fn entity_id(&self) -> SyncEntityId {
160 self.entity_id
161 }
162
163 pub fn set_interp_delay(&mut self, delay_ms: u64) {
164 self.interp_delay_ms = delay_ms;
165 }
166
167 pub fn set_max_extrapolation(&mut self, max_ms: u64) {
168 self.max_extrapolation_ms = max_ms;
169 }
170
171 pub fn set_hermite(&mut self, enabled: bool) {
172 self.hermite_enabled = enabled;
173 }
174
175 pub fn sample_count(&self) -> usize {
176 self.samples.len()
177 }
178
179 pub fn push(&mut self, sample: InterpolationSample) {
181 let ts = sample.timestamp_ms;
183 let pos = self.samples.iter().position(|s| s.timestamp_ms > ts);
184 match pos {
185 Some(idx) => self.samples.insert(idx, sample),
186 None => self.samples.push_back(sample),
187 }
188
189 while self.samples.len() > self.max_samples {
191 self.samples.pop_front();
192 }
193 }
194
195 pub fn sample_at(&mut self, render_time_ms: u64) -> Option<InterpolationSample> {
197 if self.samples.is_empty() {
198 return self.last_interpolated.clone();
199 }
200
201 let target_time = render_time_ms.saturating_sub(self.interp_delay_ms);
202
203 let mut before_idx = None;
205 let mut after_idx = None;
206
207 for (i, sample) in self.samples.iter().enumerate() {
208 if sample.timestamp_ms <= target_time {
209 before_idx = Some(i);
210 } else {
211 after_idx = Some(i);
212 break;
213 }
214 }
215
216 let result = match (before_idx, after_idx) {
217 (Some(bi), Some(ai)) => {
218 let before = &self.samples[bi];
219 let after = &self.samples[ai];
220 let range = after.timestamp_ms.saturating_sub(before.timestamp_ms);
221 if range == 0 {
222 before.clone()
223 } else {
224 let t = (target_time.saturating_sub(before.timestamp_ms)) as f32 / range as f32;
225 if self.hermite_enabled {
226 self.hermite_interpolate(bi, ai, t)
227 } else {
228 before.lerp(after, t)
229 }
230 }
231 }
232 (Some(bi), None) => {
233 let latest = &self.samples[bi];
235 let dt = target_time.saturating_sub(latest.timestamp_ms);
236 if dt <= self.max_extrapolation_ms {
237 latest.extrapolate(dt)
238 } else {
239 latest.extrapolate(self.max_extrapolation_ms)
240 }
241 }
242 (None, Some(ai)) => {
243 self.samples[ai].clone()
245 }
246 (None, None) => {
247 return self.last_interpolated.clone();
248 }
249 };
250
251 self.last_interpolated = Some(result.clone());
252 Some(result)
253 }
254
255 fn hermite_interpolate(&self, idx_a: usize, idx_b: usize, t: f32) -> InterpolationSample {
257 let a = &self.samples[idx_a];
258 let b = &self.samples[idx_b];
259 let dt_sec = (b.timestamp_ms.saturating_sub(a.timestamp_ms)) as f32 / 1000.0;
260
261 let t2 = t * t;
262 let t3 = t2 * t;
263
264 let h00 = 2.0 * t3 - 3.0 * t2 + 1.0;
266 let h10 = t3 - 2.0 * t2 + t;
267 let h01 = -2.0 * t3 + 3.0 * t2;
268 let h11 = t3 - t2;
269
270 let mut position = [0.0f32; 3];
271 for i in 0..3 {
272 position[i] = h00 * a.position[i]
273 + h10 * a.velocity[i] * dt_sec
274 + h01 * b.position[i]
275 + h11 * b.velocity[i] * dt_sec;
276 }
277
278 let dh00 = 6.0 * t2 - 6.0 * t;
280 let dh10 = 3.0 * t2 - 4.0 * t + 1.0;
281 let dh01 = -6.0 * t2 + 6.0 * t;
282 let dh11 = 3.0 * t2 - 2.0 * t;
283
284 let mut velocity = [0.0f32; 3];
285 if dt_sec > 0.0 {
286 for i in 0..3 {
287 velocity[i] = (dh00 * a.position[i]
288 + dh10 * a.velocity[i] * dt_sec
289 + dh01 * b.position[i]
290 + dh11 * b.velocity[i] * dt_sec) / dt_sec;
291 }
292 }
293
294 let rotation = quat_slerp(a.rotation, b.rotation, t);
295
296 let timestamp_ms = a.timestamp_ms + (dt_sec * t * 1000.0) as u64;
297
298 InterpolationSample {
299 timestamp_ms,
300 position,
301 rotation,
302 velocity,
303 extra: if t < 0.5 { a.extra.clone() } else { b.extra.clone() },
304 }
305 }
306
307 pub fn clear(&mut self) {
309 self.samples.clear();
310 self.last_interpolated = None;
311 }
312
313 pub fn prune_before(&mut self, timestamp_ms: u64) {
315 while self.samples.len() > 2 {
317 if let Some(front) = self.samples.front() {
318 if front.timestamp_ms < timestamp_ms {
319 self.samples.pop_front();
320 } else {
321 break;
322 }
323 } else {
324 break;
325 }
326 }
327 }
328
329 pub fn buffered_time_ms(&self) -> u64 {
331 if self.samples.len() < 2 {
332 return 0;
333 }
334 let first = self.samples.front().unwrap().timestamp_ms;
335 let last = self.samples.back().unwrap().timestamp_ms;
336 last.saturating_sub(first)
337 }
338}
339
340#[derive(Debug, Clone)]
342pub struct PredictionEntry {
343 pub sequence: u32,
344 pub timestamp_ms: u64,
345 pub input_data: Vec<u8>,
346 pub predicted_position: [f32; 3],
347 pub predicted_rotation: [f32; 4],
348 pub predicted_velocity: [f32; 3],
349 pub acknowledged: bool,
350}
351
352impl PredictionEntry {
353 pub fn new(sequence: u32, timestamp_ms: u64, input_data: Vec<u8>) -> Self {
354 Self {
355 sequence,
356 timestamp_ms,
357 input_data,
358 predicted_position: [0.0; 3],
359 predicted_rotation: [0.0, 0.0, 0.0, 1.0],
360 predicted_velocity: [0.0; 3],
361 acknowledged: false,
362 }
363 }
364}
365
366pub struct ClientPrediction {
368 entity_id: SyncEntityId,
369 pending_inputs: VecDeque<PredictionEntry>,
370 next_sequence: u32,
371 max_pending: usize,
372 last_server_position: [f32; 3],
373 last_server_rotation: [f32; 4],
374 last_server_velocity: [f32; 3],
375 last_server_sequence: u32,
376 correction_threshold: f32,
377 correction_smoothing: f32,
378 correction_offset: [f32; 3],
379 correction_timer: f32,
380 correction_duration: f32,
381 misprediction_count: u64,
382 total_predictions: u64,
383}
384
385impl ClientPrediction {
386 pub fn new(entity_id: SyncEntityId) -> Self {
387 Self {
388 entity_id,
389 pending_inputs: VecDeque::new(),
390 next_sequence: 0,
391 max_pending: 128,
392 last_server_position: [0.0; 3],
393 last_server_rotation: [0.0, 0.0, 0.0, 1.0],
394 last_server_velocity: [0.0; 3],
395 last_server_sequence: 0,
396 correction_threshold: 0.1,
397 correction_smoothing: 0.1,
398 correction_offset: [0.0; 3],
399 correction_timer: 0.0,
400 correction_duration: 0.2,
401 misprediction_count: 0,
402 total_predictions: 0,
403 }
404 }
405
406 pub fn entity_id(&self) -> SyncEntityId {
407 self.entity_id
408 }
409
410 pub fn set_correction_threshold(&mut self, threshold: f32) {
411 self.correction_threshold = threshold;
412 }
413
414 pub fn set_correction_duration(&mut self, duration: f32) {
415 self.correction_duration = duration;
416 }
417
418 pub fn misprediction_rate(&self) -> f64 {
419 if self.total_predictions == 0 {
420 return 0.0;
421 }
422 self.misprediction_count as f64 / self.total_predictions as f64
423 }
424
425 pub fn pending_count(&self) -> usize {
426 self.pending_inputs.len()
427 }
428
429 pub fn record_input(
431 &mut self,
432 timestamp_ms: u64,
433 input_data: Vec<u8>,
434 predicted_position: [f32; 3],
435 predicted_rotation: [f32; 4],
436 predicted_velocity: [f32; 3],
437 ) -> u32 {
438 let seq = self.next_sequence;
439 self.next_sequence = self.next_sequence.wrapping_add(1);
440
441 let mut entry = PredictionEntry::new(seq, timestamp_ms, input_data);
442 entry.predicted_position = predicted_position;
443 entry.predicted_rotation = predicted_rotation;
444 entry.predicted_velocity = predicted_velocity;
445
446 self.pending_inputs.push_back(entry);
447 self.total_predictions += 1;
448
449 while self.pending_inputs.len() > self.max_pending {
451 self.pending_inputs.pop_front();
452 }
453
454 seq
455 }
456
457 pub fn reconcile(
460 &mut self,
461 server_sequence: u32,
462 server_position: [f32; 3],
463 server_rotation: [f32; 4],
464 server_velocity: [f32; 3],
465 apply_input: &dyn Fn(&[u8], [f32; 3], [f32; 4], [f32; 3]) -> ([f32; 3], [f32; 4], [f32; 3]),
466 ) -> [f32; 3] {
467 self.last_server_position = server_position;
468 self.last_server_rotation = server_rotation;
469 self.last_server_velocity = server_velocity;
470 self.last_server_sequence = server_sequence;
471
472 while let Some(front) = self.pending_inputs.front() {
474 if front.sequence <= server_sequence {
475 self.pending_inputs.pop_front();
476 } else {
477 break;
478 }
479 }
480
481 let mut pos = server_position;
483 let mut rot = server_rotation;
484 let mut vel = server_velocity;
485
486 for entry in self.pending_inputs.iter_mut() {
487 let (new_pos, new_rot, new_vel) = apply_input(&entry.input_data, pos, rot, vel);
488
489 let dx = new_pos[0] - entry.predicted_position[0];
491 let dy = new_pos[1] - entry.predicted_position[1];
492 let dz = new_pos[2] - entry.predicted_position[2];
493 let error = (dx * dx + dy * dy + dz * dz).sqrt();
494
495 if error > self.correction_threshold {
496 self.misprediction_count += 1;
497 self.correction_offset = [
499 entry.predicted_position[0] - new_pos[0],
500 entry.predicted_position[1] - new_pos[1],
501 entry.predicted_position[2] - new_pos[2],
502 ];
503 self.correction_timer = self.correction_duration;
504 }
505
506 entry.predicted_position = new_pos;
507 entry.predicted_rotation = new_rot;
508 entry.predicted_velocity = new_vel;
509
510 pos = new_pos;
511 rot = new_rot;
512 vel = new_vel;
513 }
514
515 pos
516 }
517
518 pub fn visual_position(&self, predicted_position: [f32; 3], dt: f32) -> [f32; 3] {
520 if self.correction_timer <= 0.0 {
521 return predicted_position;
522 }
523 let t = (self.correction_timer / self.correction_duration).clamp(0.0, 1.0);
524 let smooth_t = t * t * (3.0 - 2.0 * t); [
527 predicted_position[0] + self.correction_offset[0] * smooth_t,
528 predicted_position[1] + self.correction_offset[1] * smooth_t,
529 predicted_position[2] + self.correction_offset[2] * smooth_t,
530 ]
531 }
532
533 pub fn update_correction(&mut self, dt: f32) {
535 if self.correction_timer > 0.0 {
536 self.correction_timer = (self.correction_timer - dt).max(0.0);
537 }
538 }
539
540 pub fn unacknowledged_inputs(&self) -> Vec<&PredictionEntry> {
542 self.pending_inputs.iter().filter(|e| !e.acknowledged).collect()
543 }
544
545 pub fn acknowledge_up_to(&mut self, sequence: u32) {
547 for entry in self.pending_inputs.iter_mut() {
548 if entry.sequence <= sequence {
549 entry.acknowledged = true;
550 }
551 }
552 }
553
554 pub fn clear(&mut self) {
555 self.pending_inputs.clear();
556 self.correction_offset = [0.0; 3];
557 self.correction_timer = 0.0;
558 }
559
560 pub fn server_position(&self) -> [f32; 3] {
561 self.last_server_position
562 }
563
564 pub fn server_rotation(&self) -> [f32; 4] {
565 self.last_server_rotation
566 }
567}
568
569#[derive(Debug, Clone, Copy, PartialEq, Eq)]
571pub enum AuthorityMode {
572 ServerAuth,
574 ClientAuth,
576 SharedAuth,
578}
579
580pub struct AuthorityModel {
582 authorities: HashMap<SyncEntityId, AuthorityEntry>,
583 default_mode: AuthorityMode,
584}
585
586#[derive(Debug, Clone)]
587struct AuthorityEntry {
588 mode: AuthorityMode,
589 owner_client: u32,
590 transfer_pending: bool,
591 transfer_target: Option<u32>,
592 lock_timestamp_ms: u64,
593}
594
595impl AuthorityModel {
596 pub fn new(default_mode: AuthorityMode) -> Self {
597 Self {
598 authorities: HashMap::new(),
599 default_mode,
600 }
601 }
602
603 pub fn set_default_mode(&mut self, mode: AuthorityMode) {
604 self.default_mode = mode;
605 }
606
607 pub fn register(&mut self, entity_id: SyncEntityId, mode: AuthorityMode, owner: u32) {
608 self.authorities.insert(entity_id, AuthorityEntry {
609 mode,
610 owner_client: owner,
611 transfer_pending: false,
612 transfer_target: None,
613 lock_timestamp_ms: 0,
614 });
615 }
616
617 pub fn unregister(&mut self, entity_id: SyncEntityId) {
618 self.authorities.remove(&entity_id);
619 }
620
621 pub fn get_mode(&self, entity_id: SyncEntityId) -> AuthorityMode {
622 self.authorities.get(&entity_id).map(|e| e.mode).unwrap_or(self.default_mode)
623 }
624
625 pub fn get_owner(&self, entity_id: SyncEntityId) -> Option<u32> {
626 self.authorities.get(&entity_id).map(|e| e.owner_client)
627 }
628
629 pub fn set_mode(&mut self, entity_id: SyncEntityId, mode: AuthorityMode) {
630 if let Some(entry) = self.authorities.get_mut(&entity_id) {
631 entry.mode = mode;
632 }
633 }
634
635 pub fn set_owner(&mut self, entity_id: SyncEntityId, owner: u32) {
636 if let Some(entry) = self.authorities.get_mut(&entity_id) {
637 entry.owner_client = owner;
638 }
639 }
640
641 pub fn request_transfer(&mut self, entity_id: SyncEntityId, target_client: u32, timestamp_ms: u64) -> bool {
643 if let Some(entry) = self.authorities.get_mut(&entity_id) {
644 if entry.transfer_pending {
645 return false;
646 }
647 entry.transfer_pending = true;
648 entry.transfer_target = Some(target_client);
649 entry.lock_timestamp_ms = timestamp_ms;
650 true
651 } else {
652 false
653 }
654 }
655
656 pub fn complete_transfer(&mut self, entity_id: SyncEntityId) -> bool {
658 if let Some(entry) = self.authorities.get_mut(&entity_id) {
659 if entry.transfer_pending {
660 if let Some(target) = entry.transfer_target.take() {
661 entry.owner_client = target;
662 entry.transfer_pending = false;
663 return true;
664 }
665 }
666 }
667 false
668 }
669
670 pub fn cancel_transfer(&mut self, entity_id: SyncEntityId) {
672 if let Some(entry) = self.authorities.get_mut(&entity_id) {
673 entry.transfer_pending = false;
674 entry.transfer_target = None;
675 }
676 }
677
678 pub fn has_authority(&self, entity_id: SyncEntityId, client_id: u32) -> bool {
680 match self.authorities.get(&entity_id) {
681 Some(entry) => {
682 match entry.mode {
683 AuthorityMode::ServerAuth => client_id == 0, AuthorityMode::ClientAuth => entry.owner_client == client_id,
685 AuthorityMode::SharedAuth => true,
686 }
687 }
688 None => {
689 match self.default_mode {
690 AuthorityMode::ServerAuth => client_id == 0,
691 _ => true,
692 }
693 }
694 }
695 }
696
697 pub fn is_transfer_pending(&self, entity_id: SyncEntityId) -> bool {
699 self.authorities.get(&entity_id).map(|e| e.transfer_pending).unwrap_or(false)
700 }
701
702 pub fn entity_count(&self) -> usize {
703 self.authorities.len()
704 }
705}
706
707#[derive(Debug, Clone, Copy)]
709pub struct PropertyFlags(pub u64);
710
711impl PropertyFlags {
712 pub fn empty() -> Self {
713 PropertyFlags(0)
714 }
715
716 pub fn all() -> Self {
717 PropertyFlags(u64::MAX)
718 }
719
720 pub fn set(&mut self, bit: u32) {
721 self.0 |= 1u64 << bit;
722 }
723
724 pub fn clear(&mut self, bit: u32) {
725 self.0 &= !(1u64 << bit);
726 }
727
728 pub fn is_set(&self, bit: u32) -> bool {
729 (self.0 & (1u64 << bit)) != 0
730 }
731
732 pub fn any_set(&self) -> bool {
733 self.0 != 0
734 }
735
736 pub fn clear_all(&mut self) {
737 self.0 = 0;
738 }
739
740 pub fn count_set(&self) -> u32 {
741 self.0.count_ones()
742 }
743
744 pub fn union(&self, other: PropertyFlags) -> PropertyFlags {
745 PropertyFlags(self.0 | other.0)
746 }
747
748 pub fn intersection(&self, other: PropertyFlags) -> PropertyFlags {
749 PropertyFlags(self.0 & other.0)
750 }
751}
752
753#[derive(Debug, Clone)]
755pub struct ReplicatedProperty {
756 pub name: String,
757 pub property_index: u32,
758 pub data: Vec<u8>,
759 pub previous_data: Vec<u8>,
760 pub reliable: bool,
761 pub interpolate: bool,
762 pub priority: f32,
763 generation: u32,
764}
765
766impl ReplicatedProperty {
767 pub fn new(name: String, property_index: u32, initial_data: Vec<u8>) -> Self {
768 let prev = initial_data.clone();
769 Self {
770 name,
771 property_index,
772 data: initial_data,
773 previous_data: prev,
774 reliable: false,
775 interpolate: true,
776 priority: 1.0,
777 generation: 0,
778 }
779 }
780
781 pub fn set_reliable(mut self, reliable: bool) -> Self {
782 self.reliable = reliable;
783 self
784 }
785
786 pub fn set_interpolate(mut self, interpolate: bool) -> Self {
787 self.interpolate = interpolate;
788 self
789 }
790
791 pub fn set_priority(mut self, priority: f32) -> Self {
792 self.priority = priority;
793 self
794 }
795
796 pub fn update(&mut self, new_data: Vec<u8>) {
797 if self.data != new_data {
798 self.previous_data = std::mem::replace(&mut self.data, new_data);
799 self.generation = self.generation.wrapping_add(1);
800 }
801 }
802
803 pub fn is_dirty(&self) -> bool {
804 self.data != self.previous_data
805 }
806
807 pub fn clear_dirty(&mut self) {
808 self.previous_data = self.data.clone();
809 }
810
811 pub fn generation(&self) -> u32 {
812 self.generation
813 }
814
815 pub fn size(&self) -> usize {
816 self.data.len()
817 }
818}
819
820pub struct DirtyTracker {
822 entity_id: SyncEntityId,
823 properties: Vec<ReplicatedProperty>,
824 dirty_flags: PropertyFlags,
825 force_full_update: bool,
826 last_replicated_generation: HashMap<u32, u32>,
827}
828
829impl DirtyTracker {
830 pub fn new(entity_id: SyncEntityId) -> Self {
831 Self {
832 entity_id,
833 properties: Vec::new(),
834 dirty_flags: PropertyFlags::empty(),
835 force_full_update: false,
836 last_replicated_generation: HashMap::new(),
837 }
838 }
839
840 pub fn entity_id(&self) -> SyncEntityId {
841 self.entity_id
842 }
843
844 pub fn add_property(&mut self, property: ReplicatedProperty) {
845 let idx = property.property_index;
846 self.properties.push(property);
847 self.last_replicated_generation.insert(idx, 0);
848 }
849
850 pub fn update_property(&mut self, index: u32, data: Vec<u8>) {
851 if let Some(prop) = self.properties.iter_mut().find(|p| p.property_index == index) {
852 prop.update(data);
853 if prop.is_dirty() {
854 self.dirty_flags.set(index);
855 }
856 }
857 }
858
859 pub fn get_property(&self, index: u32) -> Option<&ReplicatedProperty> {
860 self.properties.iter().find(|p| p.property_index == index)
861 }
862
863 pub fn dirty_flags(&self) -> PropertyFlags {
864 self.dirty_flags
865 }
866
867 pub fn has_dirty(&self) -> bool {
868 self.dirty_flags.any_set() || self.force_full_update
869 }
870
871 pub fn force_full_update(&mut self) {
872 self.force_full_update = true;
873 }
874
875 pub fn collect_dirty(&self) -> Vec<(u32, Vec<u8>)> {
877 let mut result = Vec::new();
878 for prop in &self.properties {
879 if self.force_full_update || self.dirty_flags.is_set(prop.property_index) {
880 result.push((prop.property_index, prop.data.clone()));
881 }
882 }
883 result
884 }
885
886 pub fn collect_reliable_dirty(&self) -> Vec<(u32, Vec<u8>)> {
888 let mut result = Vec::new();
889 for prop in &self.properties {
890 if prop.reliable && (self.force_full_update || self.dirty_flags.is_set(prop.property_index)) {
891 result.push((prop.property_index, prop.data.clone()));
892 }
893 }
894 result
895 }
896
897 pub fn collect_unreliable_dirty(&self) -> Vec<(u32, Vec<u8>)> {
899 let mut result = Vec::new();
900 for prop in &self.properties {
901 if !prop.reliable && (self.force_full_update || self.dirty_flags.is_set(prop.property_index)) {
902 result.push((prop.property_index, prop.data.clone()));
903 }
904 }
905 result
906 }
907
908 pub fn clear_dirty(&mut self) {
910 for prop in &mut self.properties {
911 if self.dirty_flags.is_set(prop.property_index) {
912 self.last_replicated_generation.insert(prop.property_index, prop.generation());
913 prop.clear_dirty();
914 }
915 }
916 self.dirty_flags.clear_all();
917 self.force_full_update = false;
918 }
919
920 pub fn property_count(&self) -> usize {
921 self.properties.len()
922 }
923
924 pub fn dirty_size(&self) -> usize {
926 let mut size = 0;
927 for prop in &self.properties {
928 if self.force_full_update || self.dirty_flags.is_set(prop.property_index) {
929 size += 4 + prop.size(); }
931 }
932 size
933 }
934}
935
936#[derive(Debug, Clone)]
938pub struct SpawnEvent {
939 pub entity_id: SyncEntityId,
940 pub entity_type: u16,
941 pub owner_client: u32,
942 pub position: [f32; 3],
943 pub rotation: [f32; 4],
944 pub initial_properties: Vec<(u32, Vec<u8>)>,
945 pub timestamp_ms: u64,
946 pub authority_mode: AuthorityMode,
947}
948
949impl SpawnEvent {
950 pub fn new(entity_id: SyncEntityId, entity_type: u16, position: [f32; 3]) -> Self {
951 Self {
952 entity_id,
953 entity_type,
954 owner_client: 0,
955 position,
956 rotation: [0.0, 0.0, 0.0, 1.0],
957 initial_properties: Vec::new(),
958 timestamp_ms: 0,
959 authority_mode: AuthorityMode::ServerAuth,
960 }
961 }
962
963 pub fn with_owner(mut self, owner: u32) -> Self {
964 self.owner_client = owner;
965 self
966 }
967
968 pub fn with_rotation(mut self, rotation: [f32; 4]) -> Self {
969 self.rotation = rotation;
970 self
971 }
972
973 pub fn with_property(mut self, index: u32, data: Vec<u8>) -> Self {
974 self.initial_properties.push((index, data));
975 self
976 }
977
978 pub fn with_timestamp(mut self, ts: u64) -> Self {
979 self.timestamp_ms = ts;
980 self
981 }
982
983 pub fn with_authority(mut self, mode: AuthorityMode) -> Self {
984 self.authority_mode = mode;
985 self
986 }
987
988 pub fn estimated_size(&self) -> usize {
989 let base = 4 + 2 + 4 + 12 + 16 + 8 + 1;
990 let props: usize = self.initial_properties.iter().map(|(_, d)| 4 + d.len()).sum();
991 base + props
992 }
993}
994
995#[derive(Debug, Clone)]
997pub struct DespawnEvent {
998 pub entity_id: SyncEntityId,
999 pub reason: DespawnReason,
1000 pub timestamp_ms: u64,
1001}
1002
1003#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1004pub enum DespawnReason {
1005 Destroyed,
1006 OutOfRelevancy,
1007 OwnerDisconnected,
1008 ServerCommand,
1009}
1010
1011impl DespawnEvent {
1012 pub fn new(entity_id: SyncEntityId, reason: DespawnReason) -> Self {
1013 Self {
1014 entity_id,
1015 reason,
1016 timestamp_ms: 0,
1017 }
1018 }
1019
1020 pub fn with_timestamp(mut self, ts: u64) -> Self {
1021 self.timestamp_ms = ts;
1022 self
1023 }
1024}
1025
1026pub struct SpawnDespawnReplicator {
1028 pending_spawns: VecDeque<SpawnEvent>,
1029 pending_despawns: VecDeque<DespawnEvent>,
1030 spawned_entities: HashMap<SyncEntityId, SpawnEvent>,
1031 acknowledged_spawns: HashMap<SyncEntityId, Vec<u32>>,
1032 max_pending: usize,
1033}
1034
1035impl SpawnDespawnReplicator {
1036 pub fn new() -> Self {
1037 Self {
1038 pending_spawns: VecDeque::new(),
1039 pending_despawns: VecDeque::new(),
1040 spawned_entities: HashMap::new(),
1041 acknowledged_spawns: HashMap::new(),
1042 max_pending: 256,
1043 }
1044 }
1045
1046 pub fn queue_spawn(&mut self, event: SpawnEvent) {
1048 let id = event.entity_id;
1049 self.spawned_entities.insert(id, event.clone());
1050 self.pending_spawns.push_back(event);
1051
1052 while self.pending_spawns.len() > self.max_pending {
1053 self.pending_spawns.pop_front();
1054 }
1055 }
1056
1057 pub fn queue_despawn(&mut self, event: DespawnEvent) {
1059 let id = event.entity_id;
1060 self.spawned_entities.remove(&id);
1061 self.acknowledged_spawns.remove(&id);
1062 self.pending_despawns.push_back(event);
1063
1064 while self.pending_despawns.len() > self.max_pending {
1065 self.pending_despawns.pop_front();
1066 }
1067 }
1068
1069 pub fn acknowledge_spawn(&mut self, entity_id: SyncEntityId, client_id: u32) {
1071 let clients = self.acknowledged_spawns.entry(entity_id).or_insert_with(Vec::new);
1072 if !clients.contains(&client_id) {
1073 clients.push(client_id);
1074 }
1075 }
1076
1077 pub fn client_knows_entity(&self, entity_id: SyncEntityId, client_id: u32) -> bool {
1079 self.acknowledged_spawns.get(&entity_id)
1080 .map(|clients| clients.contains(&client_id))
1081 .unwrap_or(false)
1082 }
1083
1084 pub fn pending_spawns_for_client(&self, client_id: u32) -> Vec<&SpawnEvent> {
1086 self.spawned_entities.values()
1087 .filter(|e| !self.client_knows_entity(e.entity_id, client_id))
1088 .collect()
1089 }
1090
1091 pub fn drain_spawns(&mut self) -> Vec<SpawnEvent> {
1093 self.pending_spawns.drain(..).collect()
1094 }
1095
1096 pub fn drain_despawns(&mut self) -> Vec<DespawnEvent> {
1098 self.pending_despawns.drain(..).collect()
1099 }
1100
1101 pub fn spawned_count(&self) -> usize {
1102 self.spawned_entities.len()
1103 }
1104
1105 pub fn pending_spawn_count(&self) -> usize {
1106 self.pending_spawns.len()
1107 }
1108
1109 pub fn pending_despawn_count(&self) -> usize {
1110 self.pending_despawns.len()
1111 }
1112
1113 pub fn clear(&mut self) {
1114 self.pending_spawns.clear();
1115 self.pending_despawns.clear();
1116 self.spawned_entities.clear();
1117 self.acknowledged_spawns.clear();
1118 }
1119
1120 pub fn get_spawn_event(&self, entity_id: SyncEntityId) -> Option<&SpawnEvent> {
1122 self.spawned_entities.get(&entity_id)
1123 }
1124
1125 pub fn remove_client(&mut self, client_id: u32) {
1127 for clients in self.acknowledged_spawns.values_mut() {
1128 clients.retain(|&c| c != client_id);
1129 }
1130 }
1131}
1132
1133#[derive(Debug, Clone, Copy)]
1135pub struct ClockSyncSample {
1136 pub local_send_time_ms: u64,
1137 pub remote_time_ms: u64,
1138 pub local_recv_time_ms: u64,
1139 pub rtt_ms: u64,
1140 pub offset_ms: i64,
1141}
1142
1143impl ClockSyncSample {
1144 pub fn new(
1145 local_send_time_ms: u64,
1146 remote_time_ms: u64,
1147 local_recv_time_ms: u64,
1148 ) -> Self {
1149 let rtt_ms = local_recv_time_ms.saturating_sub(local_send_time_ms);
1150 let half_rtt = (rtt_ms / 2) as i64;
1151 let offset_ms = remote_time_ms as i64 - local_send_time_ms as i64 - half_rtt;
1152 Self {
1153 local_send_time_ms,
1154 remote_time_ms,
1155 local_recv_time_ms,
1156 rtt_ms,
1157 offset_ms,
1158 }
1159 }
1160}
1161
1162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1164pub enum SyncState {
1165 Unsynchronized,
1166 Synchronizing,
1167 Synchronized,
1168}
1169
1170pub struct ClockSync {
1172 samples: VecDeque<ClockSyncSample>,
1173 max_samples: usize,
1174 estimated_offset_ms: i64,
1175 estimated_rtt_ms: f64,
1176 rtt_variance_ms: f64,
1177 state: SyncState,
1178 min_samples_for_sync: usize,
1179 outlier_threshold: f64,
1180 last_sync_time_ms: u64,
1181 sync_interval_ms: u64,
1182 drift_rate: f64,
1183 last_drift_update_ms: u64,
1184 accumulated_drift_ms: f64,
1185 convergence_rate: f64,
1186}
1187
1188impl ClockSync {
1189 pub fn new() -> Self {
1190 Self {
1191 samples: VecDeque::new(),
1192 max_samples: 16,
1193 estimated_offset_ms: 0,
1194 estimated_rtt_ms: 0.0,
1195 rtt_variance_ms: 0.0,
1196 state: SyncState::Unsynchronized,
1197 min_samples_for_sync: 5,
1198 outlier_threshold: 2.0,
1199 last_sync_time_ms: 0,
1200 sync_interval_ms: 2000,
1201 drift_rate: 0.0,
1202 last_drift_update_ms: 0,
1203 accumulated_drift_ms: 0.0,
1204 convergence_rate: 0.1,
1205 }
1206 }
1207
1208 pub fn state(&self) -> SyncState {
1209 self.state
1210 }
1211
1212 pub fn estimated_offset_ms(&self) -> i64 {
1213 self.estimated_offset_ms
1214 }
1215
1216 pub fn estimated_rtt_ms(&self) -> f64 {
1217 self.estimated_rtt_ms
1218 }
1219
1220 pub fn is_synchronized(&self) -> bool {
1221 self.state == SyncState::Synchronized
1222 }
1223
1224 pub fn set_sync_interval(&mut self, interval_ms: u64) {
1225 self.sync_interval_ms = interval_ms;
1226 }
1227
1228 pub fn set_convergence_rate(&mut self, rate: f64) {
1229 self.convergence_rate = rate.clamp(0.01, 1.0);
1230 }
1231
1232 pub fn add_sample(&mut self, sample: ClockSyncSample) {
1234 if self.state == SyncState::Unsynchronized {
1235 self.state = SyncState::Synchronizing;
1236 }
1237
1238 self.samples.push_back(sample);
1239 while self.samples.len() > self.max_samples {
1240 self.samples.pop_front();
1241 }
1242
1243 self.recompute_estimate();
1244
1245 if self.samples.len() >= self.min_samples_for_sync {
1246 self.state = SyncState::Synchronized;
1247 }
1248 }
1249
1250 fn recompute_estimate(&mut self) {
1252 if self.samples.is_empty() {
1253 return;
1254 }
1255
1256 let mut rtts: Vec<u64> = self.samples.iter().map(|s| s.rtt_ms).collect();
1258 rtts.sort();
1259 let median_rtt = rtts[rtts.len() / 2] as f64;
1260
1261 let mean_rtt: f64 = rtts.iter().map(|&r| r as f64).sum::<f64>() / rtts.len() as f64;
1263 let variance: f64 = rtts.iter()
1264 .map(|&r| { let d = r as f64 - mean_rtt; d * d })
1265 .sum::<f64>() / rtts.len() as f64;
1266 let std_dev = variance.sqrt();
1267 self.rtt_variance_ms = std_dev;
1268
1269 let threshold = self.outlier_threshold;
1271 let valid_samples: Vec<&ClockSyncSample> = self.samples.iter()
1272 .filter(|s| {
1273 let diff = (s.rtt_ms as f64 - median_rtt).abs();
1274 diff <= threshold * std_dev.max(1.0)
1275 })
1276 .collect();
1277
1278 if valid_samples.is_empty() {
1279 let sum: i64 = self.samples.iter().map(|s| s.offset_ms).sum();
1281 let new_offset = sum / self.samples.len() as i64;
1282 self.estimated_offset_ms = self.smooth_offset(new_offset);
1283 self.estimated_rtt_ms = mean_rtt;
1284 return;
1285 }
1286
1287 let mut weighted_offset: f64 = 0.0;
1289 let mut total_weight: f64 = 0.0;
1290 let mut rtt_sum: f64 = 0.0;
1291
1292 for sample in &valid_samples {
1293 let weight = 1.0 / (sample.rtt_ms as f64 + 1.0);
1294 weighted_offset += sample.offset_ms as f64 * weight;
1295 total_weight += weight;
1296 rtt_sum += sample.rtt_ms as f64;
1297 }
1298
1299 if total_weight > 0.0 {
1300 let new_offset = (weighted_offset / total_weight) as i64;
1301 self.estimated_offset_ms = self.smooth_offset(new_offset);
1302 self.estimated_rtt_ms = rtt_sum / valid_samples.len() as f64;
1303 }
1304 }
1305
1306 fn smooth_offset(&self, new_offset: i64) -> i64 {
1308 if self.state == SyncState::Unsynchronized || self.state == SyncState::Synchronizing {
1309 return new_offset;
1310 }
1311 let diff = new_offset - self.estimated_offset_ms;
1312 let adjustment = (diff as f64 * self.convergence_rate) as i64;
1313 self.estimated_offset_ms + adjustment
1314 }
1315
1316 pub fn local_to_remote(&self, local_ms: u64) -> u64 {
1318 let adjusted = local_ms as i64 + self.estimated_offset_ms + self.accumulated_drift_ms as i64;
1319 adjusted.max(0) as u64
1320 }
1321
1322 pub fn remote_to_local(&self, remote_ms: u64) -> u64 {
1324 let adjusted = remote_ms as i64 - self.estimated_offset_ms - self.accumulated_drift_ms as i64;
1325 adjusted.max(0) as u64
1326 }
1327
1328 pub fn needs_sync(&self, current_time_ms: u64) -> bool {
1330 if self.state == SyncState::Unsynchronized {
1331 return true;
1332 }
1333 current_time_ms.saturating_sub(self.last_sync_time_ms) >= self.sync_interval_ms
1334 }
1335
1336 pub fn on_sync_sent(&mut self, time_ms: u64) {
1338 self.last_sync_time_ms = time_ms;
1339 }
1340
1341 pub fn update_drift(&mut self, current_time_ms: u64) {
1343 if self.last_drift_update_ms == 0 {
1344 self.last_drift_update_ms = current_time_ms;
1345 return;
1346 }
1347
1348 let dt_ms = current_time_ms.saturating_sub(self.last_drift_update_ms);
1349 self.last_drift_update_ms = current_time_ms;
1350
1351 if self.samples.len() >= 4 {
1353 let recent_half = &self.samples.as_slices().0;
1354 let first_half_len = self.samples.len() / 2;
1355 if first_half_len > 0 && self.samples.len() > first_half_len {
1356 let first_avg: f64 = self.samples.iter().take(first_half_len)
1357 .map(|s| s.offset_ms as f64).sum::<f64>() / first_half_len as f64;
1358 let second_avg: f64 = self.samples.iter().skip(first_half_len)
1359 .map(|s| s.offset_ms as f64).sum::<f64>() / (self.samples.len() - first_half_len) as f64;
1360
1361 let first_time = self.samples.iter().take(first_half_len)
1362 .map(|s| s.local_recv_time_ms as f64).sum::<f64>() / first_half_len as f64;
1363 let second_time = self.samples.iter().skip(first_half_len)
1364 .map(|s| s.local_recv_time_ms as f64).sum::<f64>() / (self.samples.len() - first_half_len) as f64;
1365
1366 let time_diff = second_time - first_time;
1367 if time_diff > 100.0 {
1368 self.drift_rate = (second_avg - first_avg) / time_diff;
1369 }
1370
1371 let _ = recent_half; }
1373 }
1374
1375 self.accumulated_drift_ms += self.drift_rate * dt_ms as f64;
1376 self.accumulated_drift_ms = self.accumulated_drift_ms.clamp(-5000.0, 5000.0);
1378 }
1379
1380 pub fn drift_rate(&self) -> f64 {
1381 self.drift_rate
1382 }
1383
1384 pub fn sample_count(&self) -> usize {
1385 self.samples.len()
1386 }
1387
1388 pub fn reset(&mut self) {
1389 self.samples.clear();
1390 self.estimated_offset_ms = 0;
1391 self.estimated_rtt_ms = 0.0;
1392 self.rtt_variance_ms = 0.0;
1393 self.state = SyncState::Unsynchronized;
1394 self.drift_rate = 0.0;
1395 self.accumulated_drift_ms = 0.0;
1396 self.last_drift_update_ms = 0;
1397 }
1398
1399 pub fn confidence(&self) -> f64 {
1401 if self.samples.is_empty() {
1402 return 0.0;
1403 }
1404 let count_factor = (self.samples.len() as f64 / self.min_samples_for_sync as f64).min(1.0);
1405 let variance_factor = 1.0 / (1.0 + self.rtt_variance_ms / 50.0);
1406 count_factor * variance_factor
1407 }
1408}
1409
1410#[cfg(test)]
1411mod tests {
1412 use super::*;
1413
1414 #[test]
1415 fn test_interpolation_sample_lerp() {
1416 let a = InterpolationSample::new(0, [0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0]);
1417 let b = InterpolationSample::new(100, [10.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0]);
1418 let mid = a.lerp(&b, 0.5);
1419 assert!((mid.position[0] - 5.0).abs() < 0.01);
1420 assert_eq!(mid.timestamp_ms, 50);
1421 }
1422
1423 #[test]
1424 fn test_interpolation_sample_extrapolate() {
1425 let sample = InterpolationSample::new(0, [0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0])
1426 .with_velocity([10.0, 0.0, 0.0]);
1427 let ext = sample.extrapolate(1000);
1428 assert!((ext.position[0] - 10.0).abs() < 0.01);
1429 }
1430
1431 #[test]
1432 fn test_interpolation_buffer() {
1433 let mut buf = InterpolationBuffer::new(SyncEntityId(1), 50);
1434 buf.push(InterpolationSample::new(100, [0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0]));
1435 buf.push(InterpolationSample::new(200, [10.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0]));
1436
1437 let result = buf.sample_at(200).unwrap();
1439 assert!((result.position[0] - 5.0).abs() < 0.01);
1440 }
1441
1442 #[test]
1443 fn test_client_prediction() {
1444 let mut pred = ClientPrediction::new(SyncEntityId(1));
1445 let seq = pred.record_input(
1446 100, vec![1, 0, 0], [1.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0], [1.0, 0.0, 0.0],
1447 );
1448 assert_eq!(seq, 0);
1449 assert_eq!(pred.pending_count(), 1);
1450 }
1451
1452 #[test]
1453 fn test_authority_model() {
1454 let mut auth = AuthorityModel::new(AuthorityMode::ServerAuth);
1455 auth.register(SyncEntityId(1), AuthorityMode::ClientAuth, 5);
1456 assert!(auth.has_authority(SyncEntityId(1), 5));
1457 assert!(!auth.has_authority(SyncEntityId(1), 3));
1458 assert_eq!(auth.get_mode(SyncEntityId(1)), AuthorityMode::ClientAuth);
1459 }
1460
1461 #[test]
1462 fn test_authority_transfer() {
1463 let mut auth = AuthorityModel::new(AuthorityMode::ServerAuth);
1464 auth.register(SyncEntityId(1), AuthorityMode::ClientAuth, 1);
1465 assert!(auth.request_transfer(SyncEntityId(1), 2, 100));
1466 assert!(auth.is_transfer_pending(SyncEntityId(1)));
1467 assert!(auth.complete_transfer(SyncEntityId(1)));
1468 assert_eq!(auth.get_owner(SyncEntityId(1)), Some(2));
1469 }
1470
1471 #[test]
1472 fn test_property_flags() {
1473 let mut flags = PropertyFlags::empty();
1474 assert!(!flags.any_set());
1475 flags.set(0);
1476 flags.set(5);
1477 assert!(flags.is_set(0));
1478 assert!(flags.is_set(5));
1479 assert!(!flags.is_set(1));
1480 assert_eq!(flags.count_set(), 2);
1481 flags.clear(0);
1482 assert!(!flags.is_set(0));
1483 }
1484
1485 #[test]
1486 fn test_dirty_tracker() {
1487 let mut tracker = DirtyTracker::new(SyncEntityId(1));
1488 tracker.add_property(ReplicatedProperty::new("health".into(), 0, vec![100]));
1489 tracker.add_property(ReplicatedProperty::new("pos_x".into(), 1, vec![0, 0, 0, 0]));
1490
1491 assert!(!tracker.has_dirty());
1492
1493 tracker.update_property(0, vec![50]);
1494 assert!(tracker.has_dirty());
1495 assert!(tracker.dirty_flags().is_set(0));
1496
1497 let dirty = tracker.collect_dirty();
1498 assert_eq!(dirty.len(), 1);
1499 assert_eq!(dirty[0].0, 0);
1500 assert_eq!(dirty[0].1, vec![50]);
1501
1502 tracker.clear_dirty();
1503 assert!(!tracker.has_dirty());
1504 }
1505
1506 #[test]
1507 fn test_spawn_despawn_replicator() {
1508 let mut rep = SpawnDespawnReplicator::new();
1509 let spawn = SpawnEvent::new(SyncEntityId(1), 42, [1.0, 2.0, 3.0]);
1510 rep.queue_spawn(spawn);
1511 assert_eq!(rep.spawned_count(), 1);
1512
1513 let pending = rep.pending_spawns_for_client(1);
1515 assert_eq!(pending.len(), 1);
1516
1517 rep.acknowledge_spawn(SyncEntityId(1), 1);
1519 assert!(rep.client_knows_entity(SyncEntityId(1), 1));
1520 let pending2 = rep.pending_spawns_for_client(1);
1521 assert_eq!(pending2.len(), 0);
1522
1523 rep.queue_despawn(DespawnEvent::new(SyncEntityId(1), DespawnReason::Destroyed));
1525 assert_eq!(rep.spawned_count(), 0);
1526 }
1527
1528 #[test]
1529 fn test_clock_sync() {
1530 let mut clock = ClockSync::new();
1531 assert_eq!(clock.state(), SyncState::Unsynchronized);
1532
1533 for i in 0..5 {
1535 let send = 1000 + i * 2000;
1536 let remote = send + 100 + 25; let recv = send + 50;
1538 clock.add_sample(ClockSyncSample::new(send, remote, recv));
1539 }
1540
1541 assert_eq!(clock.state(), SyncState::Synchronized);
1542 assert!((clock.estimated_offset_ms() - 100).abs() < 10);
1544 }
1545
1546 #[test]
1547 fn test_clock_sync_conversion() {
1548 let mut clock = ClockSync::new();
1549 for i in 0..5 {
1550 let send = i * 1000;
1551 let remote = send + 200 + 25;
1552 let recv = send + 50;
1553 clock.add_sample(ClockSyncSample::new(send, remote, recv));
1554 }
1555
1556 let local = 5000u64;
1557 let remote = clock.local_to_remote(local);
1558 let back = clock.remote_to_local(remote);
1559 assert!((back as i64 - local as i64).abs() <= 1);
1560 }
1561
1562 #[test]
1563 fn test_quat_slerp_identity() {
1564 let q = [0.0, 0.0, 0.0, 1.0];
1565 let result = quat_slerp(q, q, 0.5);
1566 assert!((result[3] - 1.0).abs() < 0.01);
1567 }
1568
1569 #[test]
1570 fn test_replicated_property() {
1571 let mut prop = ReplicatedProperty::new("test".into(), 0, vec![1, 2, 3]);
1572 assert!(!prop.is_dirty());
1573 prop.update(vec![4, 5, 6]);
1574 assert!(prop.is_dirty());
1575 assert_eq!(prop.generation(), 1);
1576 prop.clear_dirty();
1577 assert!(!prop.is_dirty());
1578 }
1579}