1use glam::DVec3;
4use steel_protocol::packets::game::{
5 CEntityPositionSync, CMoveEntityPos, CMoveEntityPosRot, CMoveEntityRot, CRotateHead,
6 CSetEntityMotion, PackedEntityDelta, PositionPath, VecDelta, calc_delta, to_angle_byte,
7};
8use steel_registry::{entity_type::EntityTypeRef, vanilla_entities};
9
10pub const POSITION_SYNC_THRESHOLD: f64 = 7.629_394_5e-6;
12pub const VELOCITY_SYNC_THRESHOLD: f64 = 1.0e-7;
14pub const FORCED_POS_UPDATE_PERIOD: i32 = 60;
17pub const FORCED_TELEPORT_PERIOD: i32 = 400;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct PackedEntityRotation {
23 yaw: i8,
24 pitch: i8,
25}
26
27impl PackedEntityRotation {
28 #[must_use]
30 pub const fn from_degrees(rotation: (f32, f32)) -> Self {
31 Self {
32 yaw: to_angle_byte(rotation.0),
33 pitch: to_angle_byte(rotation.1),
34 }
35 }
36
37 #[must_use]
39 pub const fn yaw(self) -> i8 {
40 self.yaw
41 }
42
43 #[must_use]
45 pub const fn pitch(self) -> i8 {
46 self.pitch
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub struct EntityRotationSyncState {
53 last_body_rotation: PackedEntityRotation,
54 last_head_yaw: i8,
55}
56
57impl EntityRotationSyncState {
58 #[must_use]
60 pub const fn new(body_rotation: (f32, f32), head_yaw: f32) -> Self {
61 Self {
62 last_body_rotation: PackedEntityRotation::from_degrees(body_rotation),
63 last_head_yaw: to_angle_byte(head_yaw),
64 }
65 }
66
67 pub fn record_body_rotation(&mut self, rotation: (f32, f32)) -> Option<PackedEntityRotation> {
69 let packed = PackedEntityRotation::from_degrees(rotation);
70 if packed == self.last_body_rotation {
71 return None;
72 }
73
74 self.last_body_rotation = packed;
75 Some(packed)
76 }
77
78 #[must_use]
80 pub fn body_rotation_changed(self, rotation: (f32, f32)) -> bool {
81 PackedEntityRotation::from_degrees(rotation) != self.last_body_rotation
82 }
83
84 pub const fn mark_body_rotation_sent(&mut self, rotation: (f32, f32)) {
86 self.last_body_rotation = PackedEntityRotation::from_degrees(rotation);
87 }
88
89 pub const fn record_head_yaw(&mut self, head_yaw: f32) -> Option<i8> {
91 let packed = to_angle_byte(head_yaw);
92 if packed == self.last_head_yaw {
93 return None;
94 }
95
96 self.last_head_yaw = packed;
97 Some(packed)
98 }
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum EntityPositionSyncDecision {
104 Delta {
106 dx: PackedEntityDelta,
108 dy: PackedEntityDelta,
110 dz: PackedEntityDelta,
112 },
113 Full,
115}
116
117#[derive(Clone, Debug)]
119pub enum EntityPositionSyncPacket {
120 Delta(CMoveEntityPos),
122 Full(CEntityPositionSync),
124}
125
126#[derive(Clone, Debug)]
128pub enum EntityPositionRotSyncPacket {
129 Delta(CMoveEntityPosRot),
131 Full(CEntityPositionSync),
133}
134
135#[derive(Clone, Debug)]
137pub enum EntityMovementSyncPacket {
138 Position(CMoveEntityPos),
140 PositionRotation(CMoveEntityPosRot),
142 Rotation(CMoveEntityRot),
144 HeadRotation(CRotateHead),
146 PositionSync(CEntityPositionSync),
148 Velocity(CSetEntityMotion),
150}
151
152impl From<EntityPositionSyncPacket> for EntityMovementSyncPacket {
153 fn from(packet: EntityPositionSyncPacket) -> Self {
154 match packet {
155 EntityPositionSyncPacket::Delta(packet) => Self::Position(packet),
156 EntityPositionSyncPacket::Full(packet) => Self::PositionSync(packet),
157 }
158 }
159}
160
161impl From<EntityPositionRotSyncPacket> for EntityMovementSyncPacket {
162 fn from(packet: EntityPositionRotSyncPacket) -> Self {
163 match packet {
164 EntityPositionRotSyncPacket::Delta(packet) => Self::PositionRotation(packet),
165 EntityPositionRotSyncPacket::Full(packet) => Self::PositionSync(packet),
166 }
167 }
168}
169
170impl From<CMoveEntityRot> for EntityMovementSyncPacket {
171 fn from(packet: CMoveEntityRot) -> Self {
172 Self::Rotation(packet)
173 }
174}
175
176impl From<CRotateHead> for EntityMovementSyncPacket {
177 fn from(packet: CRotateHead) -> Self {
178 Self::HeadRotation(packet)
179 }
180}
181
182impl From<CSetEntityMotion> for EntityMovementSyncPacket {
183 fn from(packet: CSetEntityMotion) -> Self {
184 Self::Velocity(packet)
185 }
186}
187
188#[derive(Clone, Debug, Default)]
191pub struct EntityMovementSyncPackets {
192 primary: Option<EntityMovementSyncPacket>,
193 head_rotation: Option<EntityMovementSyncPacket>,
194}
195
196impl EntityMovementSyncPackets {
197 fn new(primary: Option<EntityMovementSyncPacket>, head_rotation: Option<CRotateHead>) -> Self {
199 Self {
200 primary,
201 head_rotation: head_rotation.map(EntityMovementSyncPacket::from),
202 }
203 }
204
205 #[must_use]
207 pub const fn is_empty(&self) -> bool {
208 self.primary.is_none() && self.head_rotation.is_none()
209 }
210
211 pub fn for_each(self, mut send: impl FnMut(EntityMovementSyncPacket)) {
213 if let Some(packet) = self.primary {
214 send(packet);
215 }
216 if let Some(packet) = self.head_rotation {
217 send(packet);
218 }
219 }
220}
221
222#[derive(Debug, Clone, Copy, PartialEq)]
224pub struct EntityPositionSyncSnapshot {
225 entity_id: i32,
226 position: DVec3,
227 rotation: (f32, f32),
228 on_ground: bool,
229}
230
231impl EntityPositionSyncSnapshot {
232 #[must_use]
234 pub const fn new(
235 entity_id: i32,
236 position: DVec3,
237 rotation: (f32, f32),
238 on_ground: bool,
239 ) -> Self {
240 Self {
241 entity_id,
242 position,
243 rotation,
244 on_ground,
245 }
246 }
247
248 const fn full_sync_packet(self) -> CEntityPositionSync {
249 CEntityPositionSync {
250 entity_id: self.entity_id,
251 path: PositionPath::Linear(self.position),
252 y_rot: self.rotation.0,
253 x_rot: self.rotation.1,
254 on_ground: self.on_ground,
255 }
256 }
257}
258
259impl EntityPositionSyncDecision {
260 #[must_use]
262 pub const fn into_position_packet(
263 self,
264 snapshot: EntityPositionSyncSnapshot,
265 ) -> EntityPositionSyncPacket {
266 match self {
267 Self::Delta { dx, dy, dz } => EntityPositionSyncPacket::Delta(CMoveEntityPos {
268 entity_id: snapshot.entity_id,
269 delta: VecDelta::Linear { dx, dy, dz },
270 on_ground: snapshot.on_ground,
271 }),
272 Self::Full => EntityPositionSyncPacket::Full(snapshot.full_sync_packet()),
273 }
274 }
275
276 #[must_use]
278 pub const fn into_position_rot_packet(
279 self,
280 snapshot: EntityPositionSyncSnapshot,
281 ) -> EntityPositionRotSyncPacket {
282 match self {
283 Self::Delta { dx, dy, dz } => EntityPositionRotSyncPacket::Delta(CMoveEntityPosRot {
284 entity_id: snapshot.entity_id,
285 delta: VecDelta::Linear { dx, dy, dz },
286 y_rot: to_angle_byte(snapshot.rotation.0),
287 x_rot: to_angle_byte(snapshot.rotation.1),
288 on_ground: snapshot.on_ground,
289 }),
290 Self::Full => EntityPositionRotSyncPacket::Full(snapshot.full_sync_packet()),
291 }
292 }
293}
294
295#[derive(Debug, Clone, Copy, PartialEq)]
297pub struct EntityPositionSyncState {
298 last_sent_position: DVec3,
299 last_sent_on_ground: bool,
300 sync_delay: i32,
301}
302
303impl EntityPositionSyncState {
304 #[must_use]
306 pub const fn new(position: DVec3, on_ground: bool) -> Self {
307 Self {
308 last_sent_position: position,
309 last_sent_on_ground: on_ground,
310 sync_delay: 0,
311 }
312 }
313
314 #[must_use]
316 pub const fn last_sent_position(self) -> DVec3 {
317 self.last_sent_position
318 }
319
320 #[must_use]
322 pub const fn last_sent_on_ground(self) -> bool {
323 self.last_sent_on_ground
324 }
325
326 #[must_use]
328 pub const fn sync_delay(self) -> i32 {
329 self.sync_delay
330 }
331
332 pub const fn advance_sync_delay(&mut self) -> i32 {
334 let delay = self.sync_delay;
335 self.sync_delay += 1;
336 delay
337 }
338
339 #[must_use]
341 pub fn position_changed(self, current_position: DVec3) -> bool {
342 let diff = current_position - self.last_sent_position;
343 diff.length_squared() >= POSITION_SYNC_THRESHOLD
344 }
345
346 #[must_use]
351 pub fn packed_delta(
352 self,
353 current_position: DVec3,
354 ) -> Option<(PackedEntityDelta, PackedEntityDelta, PackedEntityDelta)> {
355 let dx = calc_delta(current_position.x, self.last_sent_position.x)?;
356 let dy = calc_delta(current_position.y, self.last_sent_position.y)?;
357 let dz = calc_delta(current_position.z, self.last_sent_position.z)?;
358 Some((dx, dy, dz))
359 }
360
361 pub const fn mark_delta_sent(&mut self, position: DVec3, on_ground: bool) {
363 self.last_sent_position = position;
364 self.last_sent_on_ground = on_ground;
365 }
366
367 pub const fn mark_full_sent(&mut self, position: DVec3, on_ground: bool) {
369 self.last_sent_position = position;
370 self.last_sent_on_ground = on_ground;
371 self.sync_delay = 0;
372 }
373
374 pub const fn reset_base_without_packet(&mut self, position: DVec3, on_ground: bool) {
376 self.last_sent_position = position;
377 self.last_sent_on_ground = on_ground;
378 }
379
380 pub fn record_movement_sync(
386 &mut self,
387 position: DVec3,
388 on_ground: bool,
389 force_full: bool,
390 ) -> EntityPositionSyncDecision {
391 if !force_full && let Some((dx, dy, dz)) = self.packed_delta(position) {
392 self.mark_delta_sent(position, on_ground);
393 return EntityPositionSyncDecision::Delta { dx, dy, dz };
394 }
395
396 self.mark_full_sent(position, on_ground);
397 EntityPositionSyncDecision::Full
398 }
399}
400
401#[derive(Debug, Clone, Copy, PartialEq)]
403pub struct EntityVelocitySyncState {
404 last_sent_velocity: DVec3,
405}
406
407impl EntityVelocitySyncState {
408 #[must_use]
410 pub const fn new(velocity: DVec3) -> Self {
411 Self {
412 last_sent_velocity: velocity,
413 }
414 }
415
416 #[must_use]
418 pub const fn last_sent_velocity(self) -> DVec3 {
419 self.last_sent_velocity
420 }
421
422 pub fn record_velocity_sync(
424 &mut self,
425 entity_id: i32,
426 current_velocity: DVec3,
427 ) -> Option<CSetEntityMotion> {
428 let diff = current_velocity - self.last_sent_velocity;
429 let diff_sq = diff.length_squared();
430 let became_stationary = diff_sq > 0.0 && current_velocity == DVec3::ZERO;
431 if diff_sq <= VELOCITY_SYNC_THRESHOLD && !became_stationary {
432 return None;
433 }
434
435 self.last_sent_velocity = current_velocity;
436 Some(CSetEntityMotion::new(entity_id, current_velocity))
437 }
438}
439
440#[derive(Debug, Clone, Copy, PartialEq)]
442pub struct EntityMovementSyncState {
443 position: EntityPositionSyncState,
444 rotation: EntityRotationSyncState,
445}
446
447#[derive(Debug, Clone, Copy, PartialEq)]
449pub struct EntityMovementSyncUpdate {
450 pub entity_id: i32,
452 pub has_position: bool,
454 pub has_rotation: bool,
456 pub position: DVec3,
458 pub velocity: DVec3,
460 pub body_rotation: (f32, f32),
462 pub head_yaw: f32,
464 pub on_ground: bool,
466}
467
468#[derive(Debug, Clone, Copy, PartialEq)]
470pub struct ServerEntityMovementSyncState {
471 position: EntityPositionSyncState,
472 rotation: EntityRotationSyncState,
473 velocity: EntityVelocitySyncState,
474 update_interval: i32,
475 track_delta: bool,
476 tick_count: i32,
477 teleport_delay: i32,
478 was_riding: bool,
479 was_on_ground: bool,
480}
481
482#[derive(Debug, Clone, Copy, PartialEq)]
484pub struct ServerEntityMovementSyncUpdate {
485 pub entity_type: EntityTypeRef,
487 pub vertical_collision: bool,
489 pub horizontal_collision: bool,
491 pub requires_precise_position: bool,
493 pub entity_id: i32,
495 pub is_passenger: bool,
497 pub position: DVec3,
499 pub velocity: DVec3,
501 pub body_rotation: (f32, f32),
503 pub head_yaw: f32,
505 pub on_ground: bool,
507 pub needs_velocity_sync: bool,
509 pub has_dirty_entity_data: bool,
511 pub force_velocity_sync: bool,
513}
514
515impl ServerEntityMovementSyncUpdate {
516 fn is_full_precision_encoding_required(
517 &self,
518 (dx, _, dz): (PackedEntityDelta, PackedEntityDelta, PackedEntityDelta),
519 ) -> bool {
520 if self.entity_type != &vanilla_entities::ITEM {
521 return false;
522 }
523
524 let moving_x = dx.as_i16() != 0;
525 let moving_z = dz.as_i16() != 0;
526 let loss_x = PackedEntityDelta::encoding_precision_loss(self.position.x) != 0.0;
527 let loss_z = PackedEntityDelta::encoding_precision_loss(self.position.z) != 0.0;
528
529 (self.vertical_collision && ((moving_x && loss_x) || (moving_z && loss_z)))
530 || (self.horizontal_collision && ((moving_x && loss_z) || (moving_z && loss_x)))
531 }
532}
533
534#[derive(Clone, Debug, Default)]
536pub struct ServerEntityMovementSyncResult {
537 packets: Vec<EntityMovementSyncPacket>,
538 clear_velocity_sync: bool,
539}
540
541impl ServerEntityMovementSyncResult {
542 #[must_use]
544 pub const fn should_clear_velocity_sync(&self) -> bool {
545 self.clear_velocity_sync
546 }
547
548 pub fn for_each_packet(self, send: impl FnMut(EntityMovementSyncPacket)) {
550 self.packets.into_iter().for_each(send);
551 }
552}
553
554impl ServerEntityMovementSyncState {
555 #[must_use]
557 pub const fn new(
558 position: DVec3,
559 velocity: DVec3,
560 on_ground: bool,
561 body_rotation: (f32, f32),
562 head_yaw: f32,
563 update_interval: i32,
564 track_delta: bool,
565 ) -> Self {
566 Self {
567 position: EntityPositionSyncState::new(position, on_ground),
568 rotation: EntityRotationSyncState::new(body_rotation, head_yaw),
569 velocity: EntityVelocitySyncState::new(velocity),
570 update_interval,
571 track_delta,
572 tick_count: 0,
573 teleport_delay: 0,
574 was_riding: false,
575 was_on_ground: on_ground,
576 }
577 }
578
579 #[must_use]
581 pub fn record_send_changes(
582 &mut self,
583 update: ServerEntityMovementSyncUpdate,
584 ) -> ServerEntityMovementSyncResult {
585 let mut result = ServerEntityMovementSyncResult::default();
586 let should_process =
587 self.should_process(update.needs_velocity_sync, update.has_dirty_entity_data);
588 if should_process {
589 result.clear_velocity_sync = update.needs_velocity_sync;
590 let should_send_rotation = self.rotation.body_rotation_changed(update.body_rotation);
591
592 if update.is_passenger {
593 self.record_passenger_update(update, should_send_rotation, &mut result);
594 } else {
595 self.record_non_passenger_update(update, should_send_rotation, &mut result);
596 }
597
598 if let Some(head_y_rot) = self.rotation.record_head_yaw(update.head_yaw) {
599 result
600 .packets
601 .push(EntityMovementSyncPacket::from(CRotateHead {
602 entity_id: update.entity_id,
603 head_y_rot,
604 }));
605 }
606 }
607
608 self.tick_count = self.tick_count.wrapping_add(1);
609 result
610 }
611
612 const fn should_process(self, needs_velocity_sync: bool, has_dirty_entity_data: bool) -> bool {
613 needs_velocity_sync
614 || has_dirty_entity_data
615 || (self.update_interval > 0 && self.tick_count % self.update_interval == 0)
616 }
617
618 fn record_passenger_update(
619 &mut self,
620 update: ServerEntityMovementSyncUpdate,
621 should_send_rotation: bool,
622 result: &mut ServerEntityMovementSyncResult,
623 ) {
624 if should_send_rotation
625 && let Some(body_rotation) = self.rotation.record_body_rotation(update.body_rotation)
626 {
627 result
628 .packets
629 .push(EntityMovementSyncPacket::from(CMoveEntityRot {
630 entity_id: update.entity_id,
631 y_rot: body_rotation.yaw(),
632 x_rot: body_rotation.pitch(),
633 on_ground: update.on_ground,
634 }));
635 }
636
637 self.position
638 .reset_base_without_packet(update.position, update.on_ground);
639 self.was_riding = true;
640 }
641
642 fn record_non_passenger_update(
643 &mut self,
644 update: ServerEntityMovementSyncUpdate,
645 should_send_rotation: bool,
646 result: &mut ServerEntityMovementSyncResult,
647 ) {
648 self.teleport_delay = self.teleport_delay.wrapping_add(1);
649
650 let position_changed = self.position.position_changed(update.position);
651 let should_send_position =
652 position_changed || self.tick_count % FORCED_POS_UPDATE_PERIOD == 0;
653
654 let packed_delta = self.position.packed_delta(update.position);
655 let delta_too_big = packed_delta.is_none();
656 let full_precision_required = should_send_position
657 && packed_delta.is_some_and(|delta| update.is_full_precision_encoding_required(delta));
658
659 let force_full = delta_too_big
660 || update.requires_precise_position
661 || self.teleport_delay > FORCED_TELEPORT_PERIOD
662 || self.was_riding
663 || self.was_on_ground != update.on_ground;
664
665 if (update.needs_velocity_sync || self.track_delta || update.force_velocity_sync)
666 && let Some(packet) = self
667 .velocity
668 .record_velocity_sync(update.entity_id, update.velocity)
669 {
670 result.packets.push(EntityMovementSyncPacket::from(packet));
671 }
672
673 if force_full || full_precision_required {
674 if force_full {
675 self.was_on_ground = update.on_ground;
676 self.teleport_delay = 0;
677 }
678
679 let decision =
680 self.position
681 .record_movement_sync(update.position, update.on_ground, true);
682 self.rotation.mark_body_rotation_sent(update.body_rotation);
683 result.packets.push(EntityMovementSyncPacket::from(
684 decision.into_position_rot_packet(EntityPositionSyncSnapshot::new(
685 update.entity_id,
686 update.position,
687 update.body_rotation,
688 update.on_ground,
689 )),
690 ));
691 } else if should_send_position && should_send_rotation {
692 let decision =
693 self.position
694 .record_movement_sync(update.position, update.on_ground, false);
695 self.rotation.mark_body_rotation_sent(update.body_rotation);
696 result.packets.push(EntityMovementSyncPacket::from(
697 decision.into_position_rot_packet(EntityPositionSyncSnapshot::new(
698 update.entity_id,
699 update.position,
700 update.body_rotation,
701 update.on_ground,
702 )),
703 ));
704 } else if should_send_position {
705 let decision =
706 self.position
707 .record_movement_sync(update.position, update.on_ground, false);
708 result.packets.push(EntityMovementSyncPacket::from(
709 decision.into_position_packet(EntityPositionSyncSnapshot::new(
710 update.entity_id,
711 update.position,
712 update.body_rotation,
713 update.on_ground,
714 )),
715 ));
716 } else if should_send_rotation
717 && let Some(body_rotation) = self.rotation.record_body_rotation(update.body_rotation)
718 {
719 result
720 .packets
721 .push(EntityMovementSyncPacket::from(CMoveEntityRot {
722 entity_id: update.entity_id,
723 y_rot: body_rotation.yaw(),
724 x_rot: body_rotation.pitch(),
725 on_ground: update.on_ground,
726 }));
727 }
728
729 self.was_riding = false;
730 }
731}
732
733impl EntityMovementSyncState {
734 #[must_use]
736 pub const fn new(
737 position: DVec3,
738 on_ground: bool,
739 body_rotation: (f32, f32),
740 head_yaw: f32,
741 ) -> Self {
742 Self {
743 position: EntityPositionSyncState::new(position, on_ground),
744 rotation: EntityRotationSyncState::new(body_rotation, head_yaw),
745 }
746 }
747
748 #[must_use]
750 pub const fn last_sent_position(self) -> DVec3 {
751 self.position.last_sent_position()
752 }
753
754 pub fn record_position_sync_with_full_delay(
759 &mut self,
760 position: DVec3,
761 on_ground: bool,
762 full_sync_delay: i32,
763 ) -> EntityPositionSyncDecision {
764 let delay = self.position.advance_sync_delay();
765 let on_ground_changed = self.position.last_sent_on_ground() != on_ground;
766 let force_full = delay > full_sync_delay || on_ground_changed;
767 self.position
768 .record_movement_sync(position, on_ground, force_full)
769 }
770
771 pub fn record_update_with_full_delay(
773 &mut self,
774 update: EntityMovementSyncUpdate,
775 full_sync_delay: i32,
776 ) -> EntityMovementSyncPackets {
777 let head_rotation = if update.has_rotation {
778 self.record_head_yaw(update.head_yaw)
779 .map(|head_y_rot| CRotateHead {
780 entity_id: update.entity_id,
781 head_y_rot,
782 })
783 } else {
784 None
785 };
786
787 let primary = if update.has_position {
788 let decision = self.record_position_sync_with_full_delay(
789 update.position,
790 update.on_ground,
791 full_sync_delay,
792 );
793 let position_includes_rotation = matches!(decision, EntityPositionSyncDecision::Full);
794 let body_rotation = if position_includes_rotation {
795 self.mark_body_rotation_sent(update.body_rotation);
796 None
797 } else if update.has_rotation {
798 self.record_body_rotation(update.body_rotation)
799 } else {
800 None
801 };
802 let snapshot = EntityPositionSyncSnapshot::new(
803 update.entity_id,
804 update.position,
805 update.body_rotation,
806 update.on_ground,
807 );
808
809 if position_includes_rotation || body_rotation.is_some() {
810 Some(EntityMovementSyncPacket::from(
811 decision.into_position_rot_packet(snapshot),
812 ))
813 } else {
814 Some(EntityMovementSyncPacket::from(
815 decision.into_position_packet(snapshot),
816 ))
817 }
818 } else if update.has_rotation {
819 self.record_body_rotation(update.body_rotation)
820 .map(|body_rotation| {
821 EntityMovementSyncPacket::from(CMoveEntityRot {
822 entity_id: update.entity_id,
823 y_rot: body_rotation.yaw(),
824 x_rot: body_rotation.pitch(),
825 on_ground: update.on_ground,
826 })
827 })
828 } else {
829 None
830 };
831
832 EntityMovementSyncPackets::new(primary, head_rotation)
833 }
834
835 pub fn record_body_rotation(&mut self, rotation: (f32, f32)) -> Option<PackedEntityRotation> {
837 self.rotation.record_body_rotation(rotation)
838 }
839
840 pub const fn mark_body_rotation_sent(&mut self, rotation: (f32, f32)) {
842 self.rotation.mark_body_rotation_sent(rotation);
843 }
844
845 pub const fn record_head_yaw(&mut self, head_yaw: f32) -> Option<i8> {
847 self.rotation.record_head_yaw(head_yaw)
848 }
849}
850
851#[cfg(test)]
852mod tests {
853 use glam::DVec3;
854 use steel_protocol::packets::game::{VecDelta, calc_delta, to_angle_byte};
855 use steel_registry::vanilla_entities;
856
857 use super::{
858 EntityMovementSyncPacket, EntityMovementSyncState, EntityMovementSyncUpdate,
859 EntityPositionRotSyncPacket, EntityPositionSyncDecision, EntityPositionSyncPacket,
860 EntityPositionSyncSnapshot, EntityPositionSyncState, EntityRotationSyncState,
861 EntityVelocitySyncState, PackedEntityRotation, PositionPath, ServerEntityMovementSyncState,
862 ServerEntityMovementSyncUpdate,
863 };
864
865 #[test]
866 fn movement_sync_records_delta_when_packed_delta_fits() {
867 let mut state = EntityPositionSyncState::new(DVec3::ZERO, false);
868 state.advance_sync_delay();
869
870 let position = DVec3::new(0.25, -0.125, 0.5);
871 let decision = state.record_movement_sync(position, true, false);
872
873 assert_eq!(
874 decision,
875 EntityPositionSyncDecision::Delta {
876 dx: calc_delta(position.x, 0.0).expect("delta should fit"),
877 dy: calc_delta(position.y, 0.0).expect("delta should fit"),
878 dz: calc_delta(position.z, 0.0).expect("delta should fit"),
879 }
880 );
881 assert_eq!(state.last_sent_position(), position);
882 assert!(state.last_sent_on_ground());
883 assert_eq!(state.sync_delay(), 1);
884 }
885
886 #[test]
887 fn movement_sync_records_full_when_forced() {
888 let mut state = EntityPositionSyncState::new(DVec3::ZERO, false);
889 state.advance_sync_delay();
890
891 let decision = state.record_movement_sync(DVec3::new(0.25, 0.0, 0.0), true, true);
892
893 assert_eq!(decision, EntityPositionSyncDecision::Full);
894 assert_eq!(state.last_sent_position(), DVec3::new(0.25, 0.0, 0.0));
895 assert!(state.last_sent_on_ground());
896 assert_eq!(state.sync_delay(), 0);
897 }
898
899 #[test]
900 fn movement_sync_records_full_when_delta_overflows() {
901 let mut state = EntityPositionSyncState::new(DVec3::ZERO, false);
902
903 let decision = state.record_movement_sync(DVec3::new(10.0, 0.0, 0.0), false, false);
904
905 assert_eq!(decision, EntityPositionSyncDecision::Full);
906 assert_eq!(state.last_sent_position(), DVec3::new(10.0, 0.0, 0.0));
907 }
908
909 #[test]
910 fn rotation_sync_records_body_rotation_only_when_packed_angle_changes() {
911 let mut state = EntityRotationSyncState::new((0.0, 0.0), 0.0);
912
913 assert_eq!(state.record_body_rotation((0.5, 0.5)), None);
914 assert_eq!(
915 state.record_body_rotation((2.0, 0.0)),
916 Some(PackedEntityRotation {
917 yaw: to_angle_byte(2.0),
918 pitch: to_angle_byte(0.0),
919 })
920 );
921 assert_eq!(state.record_body_rotation((2.5, 0.0)), None);
922 assert_eq!(
923 state.record_body_rotation((2.5, 2.0)),
924 Some(PackedEntityRotation {
925 yaw: to_angle_byte(2.5),
926 pitch: to_angle_byte(2.0),
927 })
928 );
929 }
930
931 #[test]
932 fn rotation_sync_records_head_rotation_only_when_packed_angle_changes() {
933 let mut state = EntityRotationSyncState::new((0.0, 0.0), 0.0);
934
935 assert_eq!(state.record_head_yaw(0.5), None);
936 assert_eq!(state.record_head_yaw(2.0), Some(to_angle_byte(2.0)));
937 assert_eq!(state.record_head_yaw(2.5), None);
938 }
939
940 #[test]
941 fn velocity_sync_records_packet_when_delta_exceeds_threshold() {
942 let mut state = EntityVelocitySyncState::new(DVec3::ZERO);
943
944 let packet = state
945 .record_velocity_sync(12, DVec3::new(0.001, 0.0, 0.0))
946 .expect("velocity should sync");
947
948 assert_eq!(packet.entity_id, 12);
949 assert_eq!(packet.vel.x.to_bits(), 0.001_f64.to_bits());
950 assert_eq!(packet.vel.y.to_bits(), 0.0_f64.to_bits());
951 assert_eq!(packet.vel.z.to_bits(), 0.0_f64.to_bits());
952 assert_eq!(state.last_sent_velocity(), DVec3::new(0.001, 0.0, 0.0));
953 }
954
955 #[test]
956 fn velocity_sync_skips_sub_threshold_non_zero_delta() {
957 let mut state = EntityVelocitySyncState::new(DVec3::ZERO);
958
959 assert!(
960 state
961 .record_velocity_sync(12, DVec3::new(0.000_1, 0.0, 0.0))
962 .is_none()
963 );
964 assert_eq!(state.last_sent_velocity(), DVec3::ZERO);
965 }
966
967 #[test]
968 fn velocity_sync_records_packet_when_entity_becomes_stationary() {
969 let mut state = EntityVelocitySyncState::new(DVec3::new(0.000_1, 0.0, 0.0));
970
971 let packet = state
972 .record_velocity_sync(12, DVec3::ZERO)
973 .expect("stationary transition should sync");
974
975 assert_eq!(packet.entity_id, 12);
976 assert_eq!(packet.vel.x.to_bits(), 0.0_f64.to_bits());
977 assert_eq!(packet.vel.y.to_bits(), 0.0_f64.to_bits());
978 assert_eq!(packet.vel.z.to_bits(), 0.0_f64.to_bits());
979 assert_eq!(state.last_sent_velocity(), DVec3::ZERO);
980 }
981
982 #[test]
983 fn movement_sync_state_tracks_position_and_rotation_together() {
984 let mut state = EntityMovementSyncState::new(DVec3::ZERO, false, (0.0, 0.0), 0.0);
985
986 let decision =
987 state.record_position_sync_with_full_delay(DVec3::new(0.25, 0.0, 0.0), true, 400);
988 assert_eq!(decision, EntityPositionSyncDecision::Full);
989 assert_eq!(state.last_sent_position(), DVec3::new(0.25, 0.0, 0.0));
990
991 assert_eq!(state.record_body_rotation((0.5, 0.5)), None);
992 assert_eq!(
993 state.record_body_rotation((2.0, 0.0)),
994 Some(PackedEntityRotation {
995 yaw: to_angle_byte(2.0),
996 pitch: to_angle_byte(0.0),
997 })
998 );
999 state.mark_body_rotation_sent((90.0, 45.0));
1000 assert_eq!(state.record_body_rotation((90.0, 45.0)), None);
1001 assert_eq!(state.record_head_yaw(2.0), Some(to_angle_byte(2.0)));
1002 }
1003
1004 #[test]
1005 fn movement_sync_update_emits_position_rotation_before_head_rotation() {
1006 let mut state = EntityMovementSyncState::new(DVec3::ZERO, false, (0.0, 0.0), 0.0);
1007 let position = DVec3::new(0.25, 0.0, 0.0);
1008 let update = EntityMovementSyncUpdate {
1009 entity_id: 12,
1010 has_position: true,
1011 has_rotation: true,
1012 position,
1013 velocity: DVec3::new(1.0, 2.0, 3.0),
1014 body_rotation: (2.0, 0.0),
1015 head_yaw: 2.0,
1016 on_ground: false,
1017 };
1018
1019 let packets = state.record_update_with_full_delay(update, 400);
1020 let mut emitted = Vec::new();
1021 packets.for_each(|packet| emitted.push(packet));
1022
1023 assert_eq!(emitted.len(), 2);
1024 let EntityMovementSyncPacket::PositionRotation(packet) = &emitted[0] else {
1025 panic!("expected position-rotation packet");
1026 };
1027 assert_eq!(packet.entity_id, 12);
1028 let VecDelta::Linear { dx, .. } = &packet.delta else {
1029 panic!("expected linear delta");
1030 };
1031 assert_eq!(*dx, calc_delta(position.x, 0.0).expect("delta should fit"));
1032 assert_eq!(packet.y_rot, to_angle_byte(2.0));
1033 assert_eq!(packet.x_rot, to_angle_byte(0.0));
1034
1035 let EntityMovementSyncPacket::HeadRotation(packet) = &emitted[1] else {
1036 panic!("expected head-rotation packet");
1037 };
1038 assert_eq!(packet.entity_id, 12);
1039 assert_eq!(packet.head_y_rot, to_angle_byte(2.0));
1040 }
1041
1042 #[test]
1043 fn movement_sync_update_full_position_marks_body_and_head_rotation_sent() {
1044 let mut state = EntityMovementSyncState::new(DVec3::ZERO, false, (0.0, 0.0), 0.0);
1045 let full_update = EntityMovementSyncUpdate {
1046 entity_id: 12,
1047 has_position: true,
1048 has_rotation: true,
1049 position: DVec3::new(0.25, 0.0, 0.0),
1050 velocity: DVec3::ZERO,
1051 body_rotation: (90.0, 45.0),
1052 head_yaw: 90.0,
1053 on_ground: true,
1054 };
1055
1056 let packets = state.record_update_with_full_delay(full_update, 400);
1057 let mut emitted = Vec::new();
1058 packets.for_each(|packet| emitted.push(packet));
1059
1060 assert_eq!(emitted.len(), 2);
1061 assert!(matches!(
1062 emitted[0],
1063 EntityMovementSyncPacket::PositionSync(_)
1064 ));
1065 assert!(matches!(
1066 emitted[1],
1067 EntityMovementSyncPacket::HeadRotation(_)
1068 ));
1069
1070 let rotation_only_update = EntityMovementSyncUpdate {
1071 has_position: false,
1072 position: full_update.position,
1073 ..full_update
1074 };
1075 let packets = state.record_update_with_full_delay(rotation_only_update, 400);
1076
1077 assert!(packets.is_empty());
1078 }
1079
1080 fn collect_server_packets(
1081 state: &mut ServerEntityMovementSyncState,
1082 update: ServerEntityMovementSyncUpdate,
1083 ) -> Vec<EntityMovementSyncPacket> {
1084 let result = state.record_send_changes(update);
1085 let mut packets = Vec::new();
1086 result.for_each_packet(|packet| packets.push(packet));
1087 packets
1088 }
1089
1090 fn server_update(position: DVec3, velocity: DVec3) -> ServerEntityMovementSyncUpdate {
1091 ServerEntityMovementSyncUpdate {
1092 entity_type: &vanilla_entities::PIG,
1093 vertical_collision: false,
1094 horizontal_collision: false,
1095 requires_precise_position: false,
1096 entity_id: 12,
1097 is_passenger: false,
1098 position,
1099 velocity,
1100 body_rotation: (0.0, 0.0),
1101 head_yaw: 0.0,
1102 on_ground: false,
1103 needs_velocity_sync: false,
1104 has_dirty_entity_data: false,
1105 force_velocity_sync: false,
1106 }
1107 }
1108
1109 #[test]
1110 fn server_entity_sync_sends_track_delta_velocity_before_position() {
1111 let mut state = ServerEntityMovementSyncState::new(
1112 DVec3::ZERO,
1113 DVec3::ZERO,
1114 false,
1115 (0.0, 0.0),
1116 0.0,
1117 20,
1118 true,
1119 );
1120 let packets = collect_server_packets(
1121 &mut state,
1122 server_update(DVec3::new(0.25, 0.0, 0.0), DVec3::new(0.001, 0.0, 0.0)),
1123 );
1124
1125 assert_eq!(packets.len(), 2);
1126 assert!(matches!(packets[0], EntityMovementSyncPacket::Velocity(_)));
1127 assert!(matches!(packets[1], EntityMovementSyncPacket::Position(_)));
1128 }
1129
1130 #[test]
1131 fn server_entity_sync_skips_velocity_for_non_track_delta_without_needs_sync() {
1132 let mut state = ServerEntityMovementSyncState::new(
1133 DVec3::ZERO,
1134 DVec3::ZERO,
1135 false,
1136 (0.0, 0.0),
1137 0.0,
1138 2,
1139 false,
1140 );
1141 let packets = collect_server_packets(
1142 &mut state,
1143 server_update(DVec3::new(0.25, 0.0, 0.0), DVec3::new(0.001, 0.0, 0.0)),
1144 );
1145
1146 assert_eq!(packets.len(), 1);
1147 assert!(matches!(packets[0], EntityMovementSyncPacket::Position(_)));
1148 }
1149
1150 #[test]
1151 fn server_entity_sync_processes_and_clears_explicit_needs_sync() {
1152 let mut state = ServerEntityMovementSyncState::new(
1153 DVec3::ZERO,
1154 DVec3::ZERO,
1155 false,
1156 (0.0, 0.0),
1157 0.0,
1158 20,
1159 false,
1160 );
1161 let mut update = server_update(DVec3::new(0.25, 0.0, 0.0), DVec3::new(0.001, 0.0, 0.0));
1162 update.needs_velocity_sync = true;
1163
1164 let result = state.record_send_changes(update);
1165 assert!(result.should_clear_velocity_sync());
1166 let mut packets = Vec::new();
1167 result.for_each_packet(|packet| packets.push(packet));
1168
1169 assert_eq!(packets.len(), 2);
1170 assert!(matches!(packets[0], EntityMovementSyncPacket::Velocity(_)));
1171 assert!(matches!(packets[1], EntityMovementSyncPacket::Position(_)));
1172 }
1173
1174 #[test]
1175 fn server_entity_sync_processes_dirty_data_gate_between_intervals() {
1176 let mut state = ServerEntityMovementSyncState::new(
1177 DVec3::ZERO,
1178 DVec3::ZERO,
1179 false,
1180 (0.0, 0.0),
1181 0.0,
1182 20,
1183 false,
1184 );
1185 let first_packets =
1186 collect_server_packets(&mut state, server_update(DVec3::ZERO, DVec3::ZERO));
1187 assert_eq!(first_packets.len(), 1);
1188
1189 let mut update = server_update(DVec3::ZERO, DVec3::ZERO);
1190 update.body_rotation = (2.0, 0.0);
1191 update.has_dirty_entity_data = true;
1192
1193 let packets = collect_server_packets(&mut state, update);
1194
1195 assert_eq!(packets.len(), 1);
1196 assert!(matches!(packets[0], EntityMovementSyncPacket::Rotation(_)));
1197 }
1198
1199 #[test]
1200 fn server_entity_sync_forces_full_position_when_on_ground_changes() {
1201 let mut state = ServerEntityMovementSyncState::new(
1202 DVec3::ZERO,
1203 DVec3::ZERO,
1204 false,
1205 (0.0, 0.0),
1206 0.0,
1207 20,
1208 false,
1209 );
1210 let mut update = server_update(DVec3::new(0.25, 0.0, 0.0), DVec3::ZERO);
1211 update.on_ground = true;
1212
1213 let packets = collect_server_packets(&mut state, update);
1214
1215 assert_eq!(packets.len(), 1);
1216 assert!(matches!(
1217 packets[0],
1218 EntityMovementSyncPacket::PositionSync(_)
1219 ));
1220 }
1221
1222 #[test]
1223 fn server_entity_sync_rotates_passenger_without_position_packet() {
1224 let mut state = ServerEntityMovementSyncState::new(
1225 DVec3::ZERO,
1226 DVec3::ZERO,
1227 false,
1228 (0.0, 0.0),
1229 0.0,
1230 1,
1231 true,
1232 );
1233 let mut update = server_update(DVec3::new(0.25, 0.0, 0.0), DVec3::new(0.001, 0.0, 0.0));
1234 update.is_passenger = true;
1235 update.body_rotation = (2.0, 0.0);
1236
1237 let packets = collect_server_packets(&mut state, update);
1238
1239 assert_eq!(packets.len(), 1);
1240 assert!(matches!(packets[0], EntityMovementSyncPacket::Rotation(_)));
1241 }
1242
1243 #[test]
1244 fn sync_decision_builds_position_delta_packet() {
1245 let position = DVec3::new(0.25, 0.0, -0.5);
1246 let decision = EntityPositionSyncDecision::Delta {
1247 dx: calc_delta(position.x, 0.0).expect("delta should fit"),
1248 dy: calc_delta(position.y, 0.0).expect("delta should fit"),
1249 dz: calc_delta(position.z, 0.0).expect("delta should fit"),
1250 };
1251
1252 let packet = decision.into_position_packet(EntityPositionSyncSnapshot::new(
1253 12,
1254 position,
1255 (90.0, 45.0),
1256 true,
1257 ));
1258
1259 let EntityPositionSyncPacket::Delta(packet) = packet else {
1260 panic!("expected delta packet");
1261 };
1262 assert_eq!(packet.entity_id, 12);
1263 assert_eq!(
1264 packet.delta,
1265 VecDelta::Linear {
1266 dx: calc_delta(position.x, 0.0).expect("delta should fit"),
1267 dy: calc_delta(position.y, 0.0).expect("delta should fit"),
1268 dz: calc_delta(position.z, 0.0).expect("delta should fit"),
1269 }
1270 );
1271 assert!(packet.on_ground);
1272 }
1273
1274 #[test]
1275 fn sync_decision_builds_position_rotation_delta_packet() {
1276 let position = DVec3::new(0.25, 0.0, -0.5);
1277 let decision = EntityPositionSyncDecision::Delta {
1278 dx: calc_delta(position.x, 0.0).expect("delta should fit"),
1279 dy: calc_delta(position.y, 0.0).expect("delta should fit"),
1280 dz: calc_delta(position.z, 0.0).expect("delta should fit"),
1281 };
1282
1283 let packet = decision.into_position_rot_packet(EntityPositionSyncSnapshot::new(
1284 12,
1285 position,
1286 (90.0, 45.0),
1287 true,
1288 ));
1289
1290 let EntityPositionRotSyncPacket::Delta(packet) = packet else {
1291 panic!("expected position-rotation delta packet");
1292 };
1293 assert_eq!(packet.entity_id, 12);
1294 assert_eq!(
1295 packet.delta,
1296 VecDelta::Linear {
1297 dx: calc_delta(position.x, 0.0).expect("delta should fit"),
1298 dy: calc_delta(position.y, 0.0).expect("delta should fit"),
1299 dz: calc_delta(position.z, 0.0).expect("delta should fit"),
1300 }
1301 );
1302 assert_eq!(packet.y_rot, to_angle_byte(90.0));
1303 assert_eq!(packet.x_rot, to_angle_byte(45.0));
1304 assert!(packet.on_ground);
1305 }
1306
1307 #[test]
1308 fn sync_decision_builds_full_position_sync_packet() {
1309 let snapshot =
1310 EntityPositionSyncSnapshot::new(12, DVec3::new(10.0, 20.0, 30.0), (90.0, 45.0), true);
1311
1312 let packet = EntityPositionSyncDecision::Full.into_position_packet(snapshot);
1313
1314 let EntityPositionSyncPacket::Full(packet) = packet else {
1315 panic!("expected full packet");
1316 };
1317 assert_eq!(packet.entity_id, 12);
1318 assert_eq!(
1319 packet.path,
1320 PositionPath::Linear(DVec3::new(10.0, 20.0, 30.0))
1321 );
1322 assert_eq!(packet.y_rot.to_bits(), 90.0_f32.to_bits());
1323 assert_eq!(packet.x_rot.to_bits(), 45.0_f32.to_bits());
1324 assert!(packet.on_ground);
1325 }
1326}