1use std::collections::VecDeque;
14
15#[derive(Debug, Clone, Copy, PartialEq, Default)]
20pub struct Vec2 {
21 pub x: f32,
22 pub y: f32,
23}
24
25impl Vec2 {
26 pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
27
28 pub fn new(x: f32, y: f32) -> Self { Self { x, y } }
29
30 pub fn length(self) -> f32 {
31 (self.x * self.x + self.y * self.y).sqrt()
32 }
33
34 pub fn normalize(self) -> Self {
35 let len = self.length();
36 if len < f32::EPSILON { Self::ZERO } else { Self { x: self.x / len, y: self.y / len } }
37 }
38
39 pub fn lerp(self, other: Self, t: f32) -> Self {
40 Self {
41 x: self.x + (other.x - self.x) * t,
42 y: self.y + (other.y - self.y) * t,
43 }
44 }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Default)]
49pub struct Vec3 {
50 pub x: f32,
51 pub y: f32,
52 pub z: f32,
53}
54
55impl Vec3 {
56 pub const ZERO: Self = Self { x: 0.0, y: 0.0, z: 0.0 };
57
58 pub fn new(x: f32, y: f32, z: f32) -> Self { Self { x, y, z } }
59
60 pub fn length(self) -> f32 {
61 (self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
62 }
63
64 pub fn distance(self, other: Self) -> f32 {
65 (self - other).length()
66 }
67
68 pub fn lerp(self, other: Self, t: f32) -> Self {
69 Self {
70 x: self.x + (other.x - self.x) * t,
71 y: self.y + (other.y - self.y) * t,
72 z: self.z + (other.z - self.z) * t,
73 }
74 }
75
76 pub fn add(self, other: Self) -> Self {
77 Self { x: self.x + other.x, y: self.y + other.y, z: self.z + other.z }
78 }
79
80 pub fn scale(self, s: f32) -> Self {
81 Self { x: self.x * s, y: self.y * s, z: self.z * s }
82 }
83}
84
85impl std::ops::Sub for Vec3 {
86 type Output = Self;
87 fn sub(self, rhs: Self) -> Self {
88 Self { x: self.x - rhs.x, y: self.y - rhs.y, z: self.z - rhs.z }
89 }
90}
91
92impl std::ops::Add for Vec3 {
93 type Output = Self;
94 fn add(self, rhs: Self) -> Self {
95 Self { x: self.x + rhs.x, y: self.y + rhs.y, z: self.z + rhs.z }
96 }
97}
98
99impl std::ops::Mul<f32> for Vec3 {
100 type Output = Self;
101 fn mul(self, rhs: f32) -> Self {
102 Self { x: self.x * rhs, y: self.y * rhs, z: self.z * rhs }
103 }
104}
105
106#[derive(Debug, Clone, PartialEq)]
110pub struct EntitySnapshot {
111 pub id: u64,
113 pub position: Vec3,
114 pub velocity: Vec3,
115 pub rotation: Vec3,
117 pub health: f32,
118 pub state_flags: u32,
120 pub custom: Vec<u8>,
122}
123
124impl EntitySnapshot {
125 pub fn new(id: u64) -> Self {
126 Self {
127 id, position: Vec3::ZERO, velocity: Vec3::ZERO,
128 rotation: Vec3::ZERO, health: 100.0, state_flags: 0, custom: Vec::new(),
129 }
130 }
131
132 pub fn is_alive(&self) -> bool { self.state_flags & 1 != 0 }
133 pub fn is_grounded(&self) -> bool { self.state_flags & 2 != 0 }
134 pub fn is_crouching(&self) -> bool { self.state_flags & 4 != 0 }
135}
136
137#[derive(Debug, Clone)]
141pub struct GameStateSnapshot {
142 pub tick: u64,
144 pub timestamp: f64,
146 pub entities: Vec<EntitySnapshot>,
147}
148
149impl GameStateSnapshot {
150 pub fn new(tick: u64, timestamp: f64) -> Self {
151 Self { tick, timestamp, entities: Vec::new() }
152 }
153
154 pub fn with_entities(mut self, entities: Vec<EntitySnapshot>) -> Self {
155 self.entities = entities;
156 self
157 }
158
159 pub fn entity(&self, id: u64) -> Option<&EntitySnapshot> {
161 self.entities.iter().find(|e| e.id == id)
162 }
163
164 pub fn entity_mut(&mut self, id: u64) -> Option<&mut EntitySnapshot> {
166 self.entities.iter_mut().find(|e| e.id == id)
167 }
168}
169
170#[derive(Debug, Clone, PartialEq)]
174pub struct EntityDelta {
175 pub id: u64,
176 pub position_delta: Option<Vec3>,
178 pub velocity_delta: Option<Vec3>,
179 pub rotation_delta: Option<Vec3>,
180 pub health_delta: Option<f32>,
181 pub state_flags: Option<u32>,
182 pub custom: Option<Vec<u8>>,
183 pub spawned: bool,
185 pub despawned: bool,
187}
188
189impl EntityDelta {
190 pub fn new(id: u64) -> Self {
191 Self {
192 id, position_delta: None, velocity_delta: None, rotation_delta: None,
193 health_delta: None, state_flags: None, custom: None,
194 spawned: false, despawned: false,
195 }
196 }
197
198 pub fn is_empty(&self) -> bool {
199 self.position_delta.is_none()
200 && self.velocity_delta.is_none()
201 && self.rotation_delta.is_none()
202 && self.health_delta.is_none()
203 && self.state_flags.is_none()
204 && self.custom.is_none()
205 && !self.spawned && !self.despawned
206 }
207}
208
209#[derive(Debug, Clone)]
216pub struct DeltaSnapshot {
217 pub tick: u64,
219 pub timestamp: f64,
220 pub base_tick: u64,
222 pub changed: Vec<EntityDelta>,
223}
224
225impl DeltaSnapshot {
226 pub fn new(tick: u64, timestamp: f64, base_tick: u64) -> Self {
227 Self { tick, timestamp, base_tick, changed: Vec::new() }
228 }
229
230 pub fn build(base: &GameStateSnapshot, current: &GameStateSnapshot) -> Self {
232 let mut delta = DeltaSnapshot::new(current.tick, current.timestamp, base.tick);
233
234 for cur in ¤t.entities {
236 match base.entity(cur.id) {
237 None => {
238 let mut d = EntityDelta::new(cur.id);
240 d.spawned = true;
241 d.position_delta = Some(cur.position);
242 d.velocity_delta = Some(cur.velocity);
243 d.rotation_delta = Some(cur.rotation);
244 d.health_delta = Some(cur.health);
245 d.state_flags = Some(cur.state_flags);
246 d.custom = Some(cur.custom.clone());
247 delta.changed.push(d);
248 }
249 Some(base_ent) => {
250 let mut d = EntityDelta::new(cur.id);
251 let thresh = 0.001f32;
252 if cur.position.distance(base_ent.position) > thresh {
253 d.position_delta = Some(cur.position - base_ent.position);
254 }
255 if cur.velocity.distance(base_ent.velocity) > thresh {
256 d.velocity_delta = Some(cur.velocity - base_ent.velocity);
257 }
258 if cur.rotation.distance(base_ent.rotation) > thresh {
259 d.rotation_delta = Some(cur.rotation - base_ent.rotation);
260 }
261 if (cur.health - base_ent.health).abs() > 0.01 {
262 d.health_delta = Some(cur.health - base_ent.health);
263 }
264 if cur.state_flags != base_ent.state_flags {
265 d.state_flags = Some(cur.state_flags);
266 }
267 if cur.custom != base_ent.custom {
268 d.custom = Some(cur.custom.clone());
269 }
270 if !d.is_empty() {
271 delta.changed.push(d);
272 }
273 }
274 }
275 }
276
277 for base_ent in &base.entities {
279 if current.entity(base_ent.id).is_none() {
280 let mut d = EntityDelta::new(base_ent.id);
281 d.despawned = true;
282 delta.changed.push(d);
283 }
284 }
285
286 delta
287 }
288
289 pub fn apply(&self, base: &GameStateSnapshot) -> GameStateSnapshot {
291 let mut result = base.clone();
292 result.tick = self.tick;
293 result.timestamp = self.timestamp;
294
295 for d in &self.changed {
296 if d.despawned {
297 result.entities.retain(|e| e.id != d.id);
298 continue;
299 }
300 if d.spawned {
301 let mut ent = EntitySnapshot::new(d.id);
302 if let Some(p) = d.position_delta { ent.position = p; }
303 if let Some(v) = d.velocity_delta { ent.velocity = v; }
304 if let Some(r) = d.rotation_delta { ent.rotation = r; }
305 if let Some(h) = d.health_delta { ent.health = h; }
306 if let Some(f) = d.state_flags { ent.state_flags = f; }
307 if let Some(ref c) = d.custom { ent.custom = c.clone(); }
308 result.entities.push(ent);
309 continue;
310 }
311 if let Some(ent) = result.entity_mut(d.id) {
312 if let Some(dp) = d.position_delta { ent.position = ent.position + dp; }
313 if let Some(dv) = d.velocity_delta { ent.velocity = ent.velocity + dv; }
314 if let Some(dr) = d.rotation_delta { ent.rotation = ent.rotation + dr; }
315 if let Some(dh) = d.health_delta { ent.health += dh; }
316 if let Some(f) = d.state_flags { ent.state_flags = f; }
317 if let Some(ref c) = d.custom { ent.custom = c.clone(); }
318 }
319 }
320 result
321 }
322
323 pub fn change_count(&self) -> usize { self.changed.len() }
325}
326
327pub struct SnapshotBuffer {
333 snapshots: VecDeque<GameStateSnapshot>,
334 max_len: usize,
335}
336
337impl SnapshotBuffer {
338 pub const DEFAULT_MAX_LEN: usize = 64;
339
340 pub fn new(max_len: usize) -> Self {
341 Self { snapshots: VecDeque::with_capacity(max_len), max_len }
342 }
343
344 pub fn default() -> Self { Self::new(Self::DEFAULT_MAX_LEN) }
345
346 pub fn push(&mut self, snap: GameStateSnapshot) {
348 if self.snapshots.len() >= self.max_len {
349 self.snapshots.pop_front();
350 }
351 self.snapshots.push_back(snap);
352 }
353
354 pub fn latest(&self) -> Option<&GameStateSnapshot> {
356 self.snapshots.back()
357 }
358
359 pub fn oldest(&self) -> Option<&GameStateSnapshot> {
361 self.snapshots.front()
362 }
363
364 pub fn at_tick(&self, tick: u64) -> Option<&GameStateSnapshot> {
366 let mut best: Option<&GameStateSnapshot> = None;
368 for s in &self.snapshots {
369 if s.tick <= tick {
370 best = Some(s);
371 } else {
372 break;
373 }
374 }
375 best
376 }
377
378 pub fn bracket(&self, tick: u64) -> Option<(&GameStateSnapshot, &GameStateSnapshot)> {
381 let snaps: Vec<&GameStateSnapshot> = self.snapshots.iter().collect();
382 for i in 0..snaps.len().saturating_sub(1) {
383 let a = snaps[i];
384 let b = snaps[i + 1];
385 if a.tick <= tick && b.tick >= tick {
386 return Some((a, b));
387 }
388 }
389 None
390 }
391
392 pub fn len(&self) -> usize { self.snapshots.len() }
393 pub fn is_empty(&self) -> bool { self.snapshots.is_empty() }
394 pub fn clear(&mut self) { self.snapshots.clear(); }
395}
396
397pub struct StateInterpolator {
401 buffer: SnapshotBuffer,
402 interp_delay_ticks: u64,
404}
405
406impl StateInterpolator {
407 pub fn new(interp_delay_ticks: u64) -> Self {
408 Self {
409 buffer: SnapshotBuffer::new(64),
410 interp_delay_ticks,
411 }
412 }
413
414 pub fn push_snapshot(&mut self, snap: GameStateSnapshot) {
415 self.buffer.push(snap);
416 }
417
418 pub fn interpolate(&self, render_tick: u64, t: f32) -> Option<GameStateSnapshot> {
423 let display_tick = render_tick.saturating_sub(self.interp_delay_ticks);
424
425 match self.buffer.bracket(display_tick) {
426 Some((a, b)) => {
427 let tick_range = (b.tick - a.tick) as f32;
428 let local_t = if tick_range > 0.0 {
429 ((display_tick - a.tick) as f32 + t) / tick_range
430 } else {
431 0.0
432 };
433 let local_t = local_t.clamp(0.0, 1.0);
434
435 let mut result = GameStateSnapshot::new(display_tick, a.timestamp);
436 for a_ent in &a.entities {
437 let pos = if let Some(b_ent) = b.entity(a_ent.id) {
438 a_ent.position.lerp(b_ent.position, local_t)
439 } else {
440 a_ent.position
441 };
442 let vel = if let Some(b_ent) = b.entity(a_ent.id) {
443 a_ent.velocity.lerp(b_ent.velocity, local_t)
444 } else {
445 a_ent.velocity
446 };
447 let rot = if let Some(b_ent) = b.entity(a_ent.id) {
448 a_ent.rotation.lerp(b_ent.rotation, local_t)
449 } else {
450 a_ent.rotation
451 };
452 let health = if let Some(b_ent) = b.entity(a_ent.id) {
453 a_ent.health + (b_ent.health - a_ent.health) * local_t
454 } else {
455 a_ent.health
456 };
457 let flags = a_ent.state_flags; result.entities.push(EntitySnapshot {
460 id: a_ent.id,
461 position: pos,
462 velocity: vel,
463 rotation: rot,
464 health,
465 state_flags: flags,
466 custom: a_ent.custom.clone(),
467 });
468 }
469 Some(result)
470 }
471 None => {
472 self.dead_reckon(render_tick, t)
474 }
475 }
476 }
477
478 pub fn dead_reckon(&self, render_tick: u64, t: f32) -> Option<GameStateSnapshot> {
481 let latest = self.buffer.latest()?;
482 let dt = ((render_tick.saturating_sub(latest.tick)) as f32 + t) / 60.0; let mut result = GameStateSnapshot::new(render_tick, latest.timestamp + dt as f64);
485 for ent in &latest.entities {
486 let predicted_pos = ent.position + ent.velocity * dt;
487 result.entities.push(EntitySnapshot {
488 id: ent.id,
489 position: predicted_pos,
490 velocity: ent.velocity,
491 rotation: ent.rotation,
492 health: ent.health,
493 state_flags: ent.state_flags,
494 custom: ent.custom.clone(),
495 });
496 }
497 Some(result)
498 }
499
500 pub fn buffer(&self) -> &SnapshotBuffer { &self.buffer }
501 pub fn latest_tick(&self) -> Option<u64> { self.buffer.latest().map(|s| s.tick) }
502}
503
504#[derive(Debug, Clone, PartialEq)]
508pub struct PlayerInput {
509 pub tick: u64,
511 pub move_dir: Vec2,
513 pub jump: bool,
514 pub actions: u32,
516 pub facing: f32,
518}
519
520impl PlayerInput {
521 pub fn new(tick: u64) -> Self {
522 Self { tick, move_dir: Vec2::ZERO, jump: false, actions: 0, facing: 0.0 }
523 }
524
525 pub fn serialize(&self) -> Vec<u8> {
526 let mut out = Vec::with_capacity(32);
527 out.extend_from_slice(&self.tick.to_be_bytes());
528 out.extend_from_slice(&self.move_dir.x.to_bits().to_be_bytes());
529 out.extend_from_slice(&self.move_dir.y.to_bits().to_be_bytes());
530 out.push(self.jump as u8);
531 out.extend_from_slice(&self.actions.to_be_bytes());
532 out.extend_from_slice(&self.facing.to_bits().to_be_bytes());
533 out
534 }
535
536 pub fn deserialize(b: &[u8]) -> Option<Self> {
537 if b.len() < 25 { return None; }
538 let tick = u64::from_be_bytes(b[0..8].try_into().ok()?);
539 let mx = f32::from_bits(u32::from_be_bytes(b[8..12].try_into().ok()?));
540 let my = f32::from_bits(u32::from_be_bytes(b[12..16].try_into().ok()?));
541 let jump = b[16] != 0;
542 let actions = u32::from_be_bytes(b[17..21].try_into().ok()?);
543 let facing = f32::from_bits(u32::from_be_bytes(b[21..25].try_into().ok()?));
544 Some(Self { tick, move_dir: Vec2::new(mx, my), jump, actions, facing })
545 }
546}
547
548pub struct InputBuffer {
552 inputs: VecDeque<PlayerInput>,
553 max_len: usize,
554}
555
556impl InputBuffer {
557 pub const DEFAULT_MAX_LEN: usize = 128;
558
559 pub fn new(max_len: usize) -> Self {
560 Self { inputs: VecDeque::with_capacity(max_len), max_len }
561 }
562
563 pub fn push(&mut self, input: PlayerInput) {
564 if self.inputs.len() >= self.max_len {
565 self.inputs.pop_front();
566 }
567 self.inputs.push_back(input);
568 }
569
570 pub fn ack_up_to(&mut self, acked_tick: u64) {
572 while let Some(front) = self.inputs.front() {
573 if front.tick <= acked_tick {
574 self.inputs.pop_front();
575 } else {
576 break;
577 }
578 }
579 }
580
581 pub fn unacked(&self) -> impl Iterator<Item = &PlayerInput> {
583 self.inputs.iter()
584 }
585
586 pub fn len(&self) -> usize { self.inputs.len() }
587 pub fn is_empty(&self) -> bool { self.inputs.is_empty() }
588 pub fn clear(&mut self) { self.inputs.clear(); }
589}
590
591pub struct ClientPrediction {
599 pub input_buffer: InputBuffer,
600 pub predicted_pos: Vec3,
602 pub predicted_vel: Vec3,
604 pub last_acked_tick: u64,
606 pub correction_blend: f32,
608 correction_offset: Vec3,
610 correcting: bool,
612 snap_threshold: f32,
614}
615
616impl ClientPrediction {
617 pub fn new() -> Self {
618 Self {
619 input_buffer: InputBuffer::new(128),
620 predicted_pos: Vec3::ZERO,
621 predicted_vel: Vec3::ZERO,
622 last_acked_tick: 0,
623 correction_blend: 0.2,
624 correction_offset: Vec3::ZERO,
625 correcting: false,
626 snap_threshold: 5.0,
627 }
628 }
629
630 pub fn apply_input<F>(&mut self, input: PlayerInput, dt: f32, simulate: F)
633 where F: Fn(Vec3, Vec3, &PlayerInput, f32) -> (Vec3, Vec3) {
634 let (np, nv) = simulate(self.predicted_pos, self.predicted_vel, &input, dt);
635 self.predicted_pos = np;
636 self.predicted_vel = nv;
637 self.input_buffer.push(input);
638 }
639
640 pub fn reconcile<F>(
643 &mut self,
644 server_pos: Vec3,
645 server_vel: Vec3,
646 server_tick: u64,
647 dt: f32,
648 simulate: F,
649 ) where F: Fn(Vec3, Vec3, &PlayerInput, f32) -> (Vec3, Vec3) {
650 self.last_acked_tick = server_tick;
651 self.input_buffer.ack_up_to(server_tick);
652
653 let mut pos = server_pos;
655 let mut vel = server_vel;
656 let unacked: Vec<PlayerInput> = self.input_buffer.unacked().cloned().collect();
657 for inp in &unacked {
658 let (np, nv) = simulate(pos, vel, inp, dt);
659 pos = np;
660 vel = nv;
661 }
662
663 let error = pos - self.predicted_pos;
665 let error_dist = error.length();
666
667 if error_dist > self.snap_threshold {
668 self.predicted_pos = pos;
670 self.predicted_vel = vel;
671 self.correcting = false;
672 self.correction_offset = Vec3::ZERO;
673 } else if error_dist > 0.001 {
674 self.correction_offset = error;
676 self.correcting = true;
677 self.predicted_pos = pos;
678 self.predicted_vel = vel;
679 } else {
680 self.predicted_pos = pos;
681 self.predicted_vel = vel;
682 }
683 }
684
685 pub fn tick_correction(&mut self) -> Vec3 {
687 if self.correcting {
688 let step = self.correction_offset.scale(self.correction_blend);
689 self.correction_offset = self.correction_offset - step;
690 if self.correction_offset.length() < 0.001 {
691 self.correcting = false;
692 self.correction_offset = Vec3::ZERO;
693 }
694 self.predicted_pos - self.correction_offset
695 } else {
696 self.predicted_pos
697 }
698 }
699
700 pub fn is_correcting(&self) -> bool { self.correcting }
701}
702
703impl Default for ClientPrediction {
704 fn default() -> Self { Self::new() }
705}
706
707pub struct LagCompensation {
712 history: SnapshotBuffer,
713 max_history_ms: f64,
715 tick_rate_hz: f64,
716}
717
718impl LagCompensation {
719 pub fn new(max_history_ms: f64, tick_rate_hz: f64) -> Self {
721 let max_ticks = ((max_history_ms / 1000.0) * tick_rate_hz).ceil() as usize + 4;
723 Self {
724 history: SnapshotBuffer::new(max_ticks),
725 max_history_ms,
726 tick_rate_hz,
727 }
728 }
729
730 pub fn default_one_second() -> Self {
731 Self::new(1000.0, 60.0)
732 }
733
734 pub fn record(&mut self, snap: GameStateSnapshot) {
736 self.history.push(snap);
737 }
738
739 pub fn rewind_to_tick(&self, target_tick: u64) -> Option<&GameStateSnapshot> {
742 self.history.at_tick(target_tick)
743 }
744
745 pub fn rewind_for_client(&self, client_tick: u64, rtt_ms: f64) -> Option<&GameStateSnapshot> {
748 let ticks_back = (rtt_ms / 1000.0 * self.tick_rate_hz / 2.0).round() as u64;
749 let target_tick = client_tick.saturating_sub(ticks_back);
750 self.rewind_to_tick(target_tick)
751 }
752
753 pub fn entity_at_tick(&self, entity_id: u64, tick: u64) -> Option<&EntitySnapshot> {
755 self.rewind_to_tick(tick)?.entity(entity_id)
756 }
757
758 pub fn oldest_tick(&self) -> Option<u64> {
759 self.history.oldest().map(|s| s.tick)
760 }
761
762 pub fn latest_tick(&self) -> Option<u64> {
763 self.history.latest().map(|s| s.tick)
764 }
765
766 pub fn history_len(&self) -> usize {
767 self.history.len()
768 }
769}
770
771pub struct NetworkClock {
778 time_offset: f64,
780 rtt_s: f64,
782 samples: u64,
784 alpha: f64,
786 correction_rate: f64,
788 correction_remaining: f64,
789}
790
791impl NetworkClock {
792 pub fn new() -> Self {
793 Self {
794 time_offset: 0.0,
795 rtt_s: 0.05,
796 samples: 0,
797 alpha: 0.1,
798 correction_rate: 0.001,
799 correction_remaining: 0.0,
800 }
801 }
802
803 pub fn record_ping_pong(
809 &mut self,
810 send_time_s: f64,
811 recv_time_s: f64,
812 server_send_time_s: f64,
813 ) {
814 let rtt = recv_time_s - send_time_s;
815 if rtt <= 0.0 { return; }
816
817 if self.samples == 0 {
819 self.rtt_s = rtt;
820 } else {
821 self.rtt_s = self.rtt_s * (1.0 - self.alpha) + rtt * self.alpha;
822 }
823
824 let estimated_server_recv = server_send_time_s + rtt / 2.0;
826 let new_offset = estimated_server_recv - recv_time_s;
827
828 if self.samples == 0 {
830 self.time_offset = new_offset;
831 } else {
832 let error = new_offset - self.time_offset;
833 self.correction_remaining += error;
835 }
836 self.samples += 1;
837 }
838
839 pub fn tick(&mut self, dt: f64) {
842 if self.correction_remaining.abs() > f64::EPSILON {
843 let step = self.correction_rate * dt * self.correction_remaining.signum();
844 let step = if step.abs() > self.correction_remaining.abs() {
845 self.correction_remaining
846 } else {
847 step
848 };
849 self.time_offset += step;
850 self.correction_remaining -= step;
851 }
852 }
853
854 pub fn server_time(&self, local_time_s: f64) -> f64 {
856 local_time_s + self.time_offset
857 }
858
859 pub fn to_server_tick(&self, local_time_s: f64, tick_rate_hz: f64) -> u64 {
861 (self.server_time(local_time_s) * tick_rate_hz) as u64
862 }
863
864 pub fn rtt_ms(&self) -> f64 { self.rtt_s * 1000.0 }
865 pub fn offset_s(&self) -> f64 { self.time_offset }
866 pub fn sample_count(&self) -> u64 { self.samples }
867}
868
869impl Default for NetworkClock {
870 fn default() -> Self { Self::new() }
871}
872
873#[derive(Debug, Clone, Copy, PartialEq, Eq)]
877pub enum AuthorityModel {
878 ServerAuthority,
880 ClientAuthority,
882 SharedAuthority,
884}
885
886#[cfg(test)]
889mod tests {
890 use super::*;
891
892 fn snap(tick: u64, entities: Vec<EntitySnapshot>) -> GameStateSnapshot {
893 GameStateSnapshot { tick, timestamp: tick as f64 / 60.0, entities }
894 }
895
896 fn ent(id: u64, pos: Vec3) -> EntitySnapshot {
897 EntitySnapshot {
898 id, position: pos, velocity: Vec3::ZERO, rotation: Vec3::ZERO,
899 health: 100.0, state_flags: 1, custom: vec![],
900 }
901 }
902
903 #[test]
906 fn test_delta_snapshot_no_changes() {
907 let e = ent(1, Vec3::new(0.0, 0.0, 0.0));
908 let base = snap(10, vec![e.clone()]);
909 let current = snap(11, vec![e]);
910 let delta = DeltaSnapshot::build(&base, ¤t);
911 assert_eq!(delta.change_count(), 0, "no changes expected");
912 }
913
914 #[test]
915 fn test_delta_snapshot_position_change() {
916 let base = snap(10, vec![ent(1, Vec3::new(0.0, 0.0, 0.0))]);
917 let current = snap(11, vec![ent(1, Vec3::new(1.0, 0.0, 0.0))]);
918 let delta = DeltaSnapshot::build(&base, ¤t);
919 assert_eq!(delta.change_count(), 1);
920 let d = &delta.changed[0];
921 assert!(d.position_delta.is_some());
922 }
923
924 #[test]
925 fn test_delta_snapshot_spawn_despawn() {
926 let base = snap(10, vec![ent(1, Vec3::ZERO)]);
927 let current = snap(11, vec![ent(1, Vec3::ZERO), ent(2, Vec3::new(5.0, 0.0, 0.0))]);
928 let delta = DeltaSnapshot::build(&base, ¤t);
929 assert!(delta.changed.iter().any(|d| d.id == 2 && d.spawned));
930
931 let base2 = snap(11, vec![ent(1, Vec3::ZERO), ent(2, Vec3::ZERO)]);
933 let current2 = snap(12, vec![ent(2, Vec3::ZERO)]);
934 let delta2 = DeltaSnapshot::build(&base2, ¤t2);
935 assert!(delta2.changed.iter().any(|d| d.id == 1 && d.despawned));
936 }
937
938 #[test]
939 fn test_delta_apply_roundtrip() {
940 let base = snap(10, vec![ent(1, Vec3::new(0.0, 0.0, 0.0))]);
941 let target = snap(11, vec![ent(1, Vec3::new(3.0, 1.0, 2.0))]);
942 let delta = DeltaSnapshot::build(&base, &target);
943 let applied = delta.apply(&base);
944 let ent_r = applied.entity(1).unwrap();
945 assert!((ent_r.position.x - 3.0).abs() < 0.001);
946 assert!((ent_r.position.y - 1.0).abs() < 0.001);
947 assert!((ent_r.position.z - 2.0).abs() < 0.001);
948 }
949
950 #[test]
953 fn test_snapshot_buffer_capacity() {
954 let mut buf = SnapshotBuffer::new(4);
955 for i in 0..6u64 {
956 buf.push(snap(i, vec![]));
957 }
958 assert_eq!(buf.len(), 4);
959 assert_eq!(buf.oldest().unwrap().tick, 2);
960 assert_eq!(buf.latest().unwrap().tick, 5);
961 }
962
963 #[test]
964 fn test_snapshot_buffer_at_tick() {
965 let mut buf = SnapshotBuffer::new(64);
966 for i in [10u64, 20, 30, 40] {
967 buf.push(snap(i, vec![]));
968 }
969 assert_eq!(buf.at_tick(25).unwrap().tick, 20);
970 assert_eq!(buf.at_tick(40).unwrap().tick, 40);
971 assert!(buf.at_tick(5).is_none());
972 }
973
974 #[test]
977 fn test_interpolator_between_snapshots() {
978 let mut interp = StateInterpolator::new(0);
979 interp.push_snapshot(snap(10, vec![ent(1, Vec3::new(0.0, 0.0, 0.0))]));
980 interp.push_snapshot(snap(20, vec![ent(1, Vec3::new(10.0, 0.0, 0.0))]));
981
982 let result = interp.interpolate(15, 0.0).unwrap();
983 let e = result.entity(1).unwrap();
984 assert!((e.position.x - 5.0).abs() < 0.1, "expected ~5.0, got {}", e.position.x);
985 }
986
987 #[test]
990 fn test_player_input_roundtrip() {
991 let inp = PlayerInput {
992 tick: 42,
993 move_dir: Vec2::new(0.5, -0.5),
994 jump: true,
995 actions: 0b1010,
996 facing: 1.57,
997 };
998 let bytes = inp.serialize();
999 let decoded = PlayerInput::deserialize(&bytes).unwrap();
1000 assert_eq!(decoded.tick, inp.tick);
1001 assert!((decoded.move_dir.x - inp.move_dir.x).abs() < 0.0001);
1002 assert_eq!(decoded.jump, inp.jump);
1003 assert_eq!(decoded.actions, inp.actions);
1004 }
1005
1006 #[test]
1009 fn test_client_prediction_reconcile_snaps_large_error() {
1010 let mut pred = ClientPrediction::new();
1011 pred.predicted_pos = Vec3::new(100.0, 0.0, 0.0);
1012 pred.predicted_vel = Vec3::ZERO;
1013
1014 let sim = |_pos: Vec3, vel: Vec3, _inp: &PlayerInput, _dt: f32| (Vec3::new(1.0, 0.0, 0.0), vel);
1015 pred.reconcile(Vec3::new(0.0, 0.0, 0.0), Vec3::ZERO, 5, 0.016, sim);
1016
1017 assert!(!pred.is_correcting());
1019 }
1020
1021 #[test]
1022 fn test_client_prediction_reconcile_blends_small_error() {
1023 let mut pred = ClientPrediction::new();
1024 pred.predicted_pos = Vec3::new(1.0, 0.0, 0.0);
1025 let sim = |_pos: Vec3, vel: Vec3, _inp: &PlayerInput, _dt: f32| (Vec3::new(1.0, 0.0, 0.0), vel);
1026 pred.reconcile(Vec3::new(1.05, 0.0, 0.0), Vec3::ZERO, 0, 0.016, sim);
1028 assert!(pred.is_correcting());
1030 }
1031
1032 #[test]
1035 fn test_lag_compensation_rewind() {
1036 let mut lc = LagCompensation::default_one_second();
1037 for tick in 0..65u64 {
1038 lc.record(snap(tick, vec![ent(1, Vec3::new(tick as f32, 0.0, 0.0))]));
1039 }
1040 let rewound = lc.rewind_to_tick(10).unwrap();
1041 assert!(rewound.tick <= 10);
1042
1043 let ent_at_10 = lc.entity_at_tick(1, 10).unwrap();
1044 assert!((ent_at_10.position.x - 10.0).abs() < 0.001);
1045 }
1046
1047 #[test]
1050 fn test_network_clock_basic_sync() {
1051 let mut clock = NetworkClock::new();
1052 let send_t = 1.0f64;
1054 let recv_t = 1.1f64;
1055 let srv_t = 6.05f64; clock.record_ping_pong(send_t, recv_t, srv_t);
1057 let est = clock.server_time(recv_t);
1059 assert!((est - 6.1).abs() < 0.2, "est={est}");
1060 }
1061
1062 #[test]
1063 fn test_network_clock_tick_applies_correction() {
1064 let mut clock = NetworkClock::new();
1065 clock.record_ping_pong(0.0, 0.1, 5.05);
1066 clock.record_ping_pong(1.0, 1.1, 6.05); for _ in 0..100 {
1069 clock.tick(0.016);
1070 }
1071 assert!(clock.sample_count() >= 2);
1072 }
1073}