Skip to main content

proof_engine/netcode/
sync.rs

1//! State synchronization: interpolation, prediction, authority, replication, and clock sync.
2
3use std::collections::{HashMap, VecDeque};
4
5/// A timestamped sample for interpolation.
6#[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    /// Linearly interpolate between two samples.
37    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        // Slerp for quaternion
54        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    /// Extrapolate forward from this sample using velocity.
66    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
82/// Quaternion spherical linear interpolation.
83fn 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    // Ensure shortest path
88    if dot < 0.0 {
89        b = [-b[0], -b[1], -b[2], -b[3]];
90        dot = -dot;
91    }
92
93    // If very close, use linear interpolation
94    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/// Entity identifier used in the sync layer.
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
132pub struct SyncEntityId(pub u32);
133
134/// Interpolation buffer for a single remote entity.
135/// Stores recent state samples and provides smooth interpolation.
136pub 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    /// Add a new state sample.
180    pub fn push(&mut self, sample: InterpolationSample) {
181        // Insert in timestamp order
182        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        // Trim old samples
190        while self.samples.len() > self.max_samples {
191            self.samples.pop_front();
192        }
193    }
194
195    /// Get the interpolated state at the given render time.
196    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        // Find the two samples surrounding target_time
204        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                // Extrapolate from the latest sample
234                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                // Before all samples, just use the earliest
244                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    /// Cubic Hermite interpolation using velocity as tangents.
256    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        // Hermite basis functions
265        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        // Velocity: derivative of Hermite
279        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    /// Clear all samples.
308    pub fn clear(&mut self) {
309        self.samples.clear();
310        self.last_interpolated = None;
311    }
312
313    /// Remove samples older than the given timestamp.
314    pub fn prune_before(&mut self, timestamp_ms: u64) {
315        // Keep at least 2 samples for interpolation
316        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    /// Get the time span covered by buffered samples.
330    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/// A prediction entry representing one input frame.
341#[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
366/// Client-side prediction with server reconciliation.
367pub 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    /// Record a new predicted input. Returns the sequence number.
430    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        // Trim old entries
450        while self.pending_inputs.len() > self.max_pending {
451            self.pending_inputs.pop_front();
452        }
453
454        seq
455    }
456
457    /// Process a server authoritative state update.
458    /// Returns the corrected position after reconciliation.
459    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        // Remove all acknowledged inputs
473        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        // Re-simulate remaining inputs from server state
482        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            // Check for misprediction
490            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                // Start smooth correction
498                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    /// Get the visual position with smoothed correction applied.
519    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        // Smooth interpolation of the error offset
525        let smooth_t = t * t * (3.0 - 2.0 * t); // smoothstep
526        [
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    /// Tick the correction timer.
534    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    /// Get inputs that haven't been acknowledged yet, for retransmission.
541    pub fn unacknowledged_inputs(&self) -> Vec<&PredictionEntry> {
542        self.pending_inputs.iter().filter(|e| !e.acknowledged).collect()
543    }
544
545    /// Mark inputs as acknowledged up to and including the given sequence.
546    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/// Authority models for networked entities.
570#[derive(Debug, Clone, Copy, PartialEq, Eq)]
571pub enum AuthorityMode {
572    /// Server has full authority. Client sends inputs, server resolves.
573    ServerAuth,
574    /// Client has authority over this entity. Server accepts client state.
575    ClientAuth,
576    /// Shared authority: client predicts, server validates and can override.
577    SharedAuth,
578}
579
580/// Authority tracking for a set of entities.
581pub 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    /// Request an authority transfer.
642    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    /// Complete a pending transfer.
657    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    /// Cancel a pending transfer.
671    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    /// Check whether a given client has authority over an entity.
679    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, // 0 = server
684                    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    /// Whether a transfer is in progress for the given entity.
698    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/// Flags for replicated property change tracking.
708#[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/// A single replicated property with dirty tracking.
754#[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
820/// Tracks dirty state for a set of replicated properties on an entity.
821pub 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    /// Collect dirty properties for replication. Returns (property_index, data) pairs.
876    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    /// Collect only reliable dirty properties.
887    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    /// Collect only unreliable dirty properties.
898    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    /// Mark all dirty properties as clean after replication.
909    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    /// Total estimated replication size for dirty properties.
925    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(); // index + data
930            }
931        }
932        size
933    }
934}
935
936/// An event representing an entity spawn on the network.
937#[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/// An event representing an entity despawn.
996#[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
1026/// Manages spawn/despawn replication across the network.
1027pub 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    /// Queue an entity spawn for replication.
1047    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    /// Queue an entity despawn for replication.
1058    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    /// Acknowledge that a client has received a spawn event.
1070    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    /// Check if a client knows about an entity.
1078    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    /// Get pending spawns for a specific client (entities it doesn't know about yet).
1085    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    /// Drain all pending spawns.
1092    pub fn drain_spawns(&mut self) -> Vec<SpawnEvent> {
1093        self.pending_spawns.drain(..).collect()
1094    }
1095
1096    /// Drain all pending despawns.
1097    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    /// Get the spawn event for a specific entity.
1121    pub fn get_spawn_event(&self, entity_id: SyncEntityId) -> Option<&SpawnEvent> {
1122        self.spawned_entities.get(&entity_id)
1123    }
1124
1125    /// Remove client tracking when they disconnect.
1126    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/// A single clock synchronization sample.
1134#[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/// State of the clock sync algorithm.
1163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1164pub enum SyncState {
1165    Unsynchronized,
1166    Synchronizing,
1167    Synchronized,
1168}
1169
1170/// NTP-like clock synchronization between client and server.
1171pub 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    /// Add a new synchronization sample.
1233    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    /// Recompute the clock offset estimate by filtering outliers and averaging.
1251    fn recompute_estimate(&mut self) {
1252        if self.samples.is_empty() {
1253            return;
1254        }
1255
1256        // Compute median RTT to detect outliers
1257        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        // Compute RTT standard deviation
1262        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        // Filter outliers: keep samples within threshold * std_dev of median
1270        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            // Fall back to all samples
1280            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        // Weight by inverse RTT (lower RTT = more accurate)
1288        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    /// Smoothly converge toward a new offset to avoid time jumps.
1307    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    /// Convert a local timestamp to estimated remote time.
1317    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    /// Convert a remote timestamp to estimated local time.
1323    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    /// Whether it's time to send another sync request.
1329    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    /// Mark that we sent a sync request.
1337    pub fn on_sync_sent(&mut self, time_ms: u64) {
1338        self.last_sync_time_ms = time_ms;
1339    }
1340
1341    /// Update drift estimation. Call periodically.
1342    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        // Estimate drift from recent offset changes
1352        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; // suppress unused warning
1372            }
1373        }
1374
1375        self.accumulated_drift_ms += self.drift_rate * dt_ms as f64;
1376        // Clamp accumulated drift
1377        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    /// Get the confidence of the current estimate (0.0 to 1.0).
1400    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        // Render time 200 with 50ms delay => target 150 => interpolate between 100 and 200
1438        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        // Client 1 doesn't know about it yet
1514        let pending = rep.pending_spawns_for_client(1);
1515        assert_eq!(pending.len(), 1);
1516
1517        // Acknowledge it
1518        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        // Despawn
1524        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        // Simulate 5 samples with ~50ms RTT and ~100ms offset
1534        for i in 0..5 {
1535            let send = 1000 + i * 2000;
1536            let remote = send + 100 + 25; // offset=100, half_rtt=25
1537            let recv = send + 50;
1538            clock.add_sample(ClockSyncSample::new(send, remote, recv));
1539        }
1540
1541        assert_eq!(clock.state(), SyncState::Synchronized);
1542        // Offset should be approximately 100
1543        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}