1use glam::DVec3;
4
5pub const MOVEMENT_ERROR_THRESHOLD: f64 = 0.0625;
8
9pub const Y_TOLERANCE: f64 = 0.5;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct MovementCollisionValidation {
18 pub no_physics: bool,
20 pub moved_wrongly: bool,
22 pub old_collision: bool,
24 pub new_collision: bool,
26}
27
28impl MovementCollisionValidation {
29 #[must_use]
31 pub const fn rejects(self) -> bool {
32 !self.no_physics && ((self.moved_wrongly && !self.old_collision) || self.new_collision)
33 }
34}
35
36#[derive(Debug)]
43pub(crate) struct ClientAuthoredMovementState {
44 last_good_position: DVec3,
46 first_good_position: DVec3,
48 received_move_packet_count: i32,
50 known_move_packet_count: i32,
52 last_known_client_movement: DVec3,
54 client_is_floating: bool,
56 above_ground_tick_count: i32,
58}
59
60impl ClientAuthoredMovementState {
61 #[must_use]
63 pub(crate) const fn new() -> Self {
64 Self {
65 last_good_position: DVec3::ZERO,
66 first_good_position: DVec3::ZERO,
67 received_move_packet_count: 0,
68 known_move_packet_count: 0,
69 last_known_client_movement: DVec3::ZERO,
70 client_is_floating: false,
71 above_ground_tick_count: 0,
72 }
73 }
74
75 pub(crate) const fn reset_for_tick(&mut self, position: DVec3) {
77 self.first_good_position = position;
78 self.last_good_position = position;
79 self.known_move_packet_count = self.received_move_packet_count;
80 }
81
82 pub(crate) const fn reset_for_position_sync(&mut self, position: DVec3) {
84 self.last_good_position = position;
85 self.first_good_position = position;
86 self.received_move_packet_count = 0;
87 self.known_move_packet_count = 0;
88 self.last_known_client_movement = DVec3::ZERO;
89 self.above_ground_tick_count = 0;
90 }
91
92 #[must_use]
94 pub(crate) const fn good_positions(&self) -> (DVec3, DVec3) {
95 (self.first_good_position, self.last_good_position)
96 }
97
98 pub(crate) const fn record_move_packet_delta(&mut self) -> i32 {
100 self.received_move_packet_count += 1;
101 self.received_move_packet_count - self.known_move_packet_count
102 }
103
104 pub(crate) const fn mark_last_good_position(&mut self, position: DVec3) {
106 self.last_good_position = position;
107 }
108
109 pub(crate) const fn set_last_known_client_movement(&mut self, movement: DVec3) {
111 self.last_known_client_movement = movement;
112 }
113
114 pub(crate) const fn reset_last_known_client_movement(&mut self) {
116 self.last_known_client_movement = DVec3::ZERO;
117 }
118
119 #[must_use]
121 pub(crate) const fn last_known_client_movement(&self) -> DVec3 {
122 self.last_known_client_movement
123 }
124
125 pub(crate) const fn record_client_floating(&mut self, client_is_floating: bool) {
127 self.client_is_floating = client_is_floating;
128 }
129
130 pub(crate) const fn clear_client_floating(&mut self) {
132 self.client_is_floating = false;
133 self.above_ground_tick_count = 0;
134 }
135
136 pub(crate) const fn reset_flying_ticks(&mut self) {
138 self.above_ground_tick_count = 0;
139 }
140
141 pub(crate) const fn tick_client_floating(
145 &mut self,
146 should_count: bool,
147 maximum_flying_ticks: i32,
148 ) -> bool {
149 if self.client_is_floating && should_count {
150 self.above_ground_tick_count = self.above_ground_tick_count.saturating_add(1);
151 return self.above_ground_tick_count > maximum_flying_ticks;
152 }
153
154 self.client_is_floating = false;
155 self.above_ground_tick_count = 0;
156 false
157 }
158}
159
160#[must_use]
162pub fn movement_error_delta(target_pos: DVec3, simulated_pos: DVec3) -> DVec3 {
163 let error_x = target_pos.x - simulated_pos.x;
164 let mut error_y = target_pos.y - simulated_pos.y;
165 if error_y > -Y_TOLERANCE || error_y < Y_TOLERANCE {
166 error_y = 0.0;
167 }
168 let error_z = target_pos.z - simulated_pos.z;
169 DVec3::new(error_x, error_y, error_z)
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn movement_error_delta_matches_vanilla_y_branch() {
178 let delta = movement_error_delta(DVec3::new(10.0, 120.0, -5.0), DVec3::new(8.0, 0.0, -8.0));
179
180 assert_eq!(delta, DVec3::new(2.0, 0.0, 3.0));
181 }
182
183 #[test]
184 fn movement_validation_accepts_no_physics_even_with_new_collision() {
185 assert!(
186 !MovementCollisionValidation {
187 no_physics: true,
188 moved_wrongly: true,
189 old_collision: false,
190 new_collision: true,
191 }
192 .rejects()
193 );
194 }
195
196 #[test]
197 fn movement_validation_rejects_new_collision_for_physical_entity() {
198 assert!(
199 MovementCollisionValidation {
200 no_physics: false,
201 moved_wrongly: false,
202 old_collision: false,
203 new_collision: true,
204 }
205 .rejects()
206 );
207 }
208
209 #[test]
210 fn client_movement_tick_reset_updates_good_positions_and_packet_base() {
211 let mut state = ClientAuthoredMovementState::new();
212 state.mark_last_good_position(DVec3::new(1.0, 2.0, 3.0));
213 state.record_move_packet_delta();
214 state.record_move_packet_delta();
215
216 state.reset_for_tick(DVec3::new(4.0, 5.0, 6.0));
217
218 assert_eq!(
219 state.good_positions(),
220 (DVec3::new(4.0, 5.0, 6.0), DVec3::new(4.0, 5.0, 6.0))
221 );
222 assert_eq!(state.record_move_packet_delta(), 1);
223 }
224
225 #[test]
226 fn client_movement_position_sync_reset_clears_packet_counts_and_known_movement() {
227 let mut state = ClientAuthoredMovementState::new();
228 state.record_move_packet_delta();
229 state.set_last_known_client_movement(DVec3::new(0.1, 0.0, 0.0));
230
231 state.reset_for_position_sync(DVec3::new(2.0, 3.0, 4.0));
232
233 assert_eq!(
234 state.good_positions(),
235 (DVec3::new(2.0, 3.0, 4.0), DVec3::new(2.0, 3.0, 4.0))
236 );
237 assert_eq!(state.last_known_client_movement(), DVec3::ZERO);
238 assert_eq!(state.record_move_packet_delta(), 1);
239 }
240
241 #[test]
242 fn client_movement_floating_tracker_counts_only_while_floating() {
243 let mut state = ClientAuthoredMovementState::new();
244 state.record_client_floating(true);
245
246 assert!(!state.tick_client_floating(true, 2));
247 assert!(!state.tick_client_floating(true, 2));
248 assert!(state.tick_client_floating(true, 2));
249
250 state.record_client_floating(false);
251 assert!(!state.tick_client_floating(true, 2));
252
253 state.record_client_floating(true);
254 assert!(!state.tick_client_floating(true, 2));
255 }
256
257 #[test]
258 fn client_movement_floating_tracker_resets_when_tick_conditions_do_not_count() {
259 let mut state = ClientAuthoredMovementState::new();
260 state.record_client_floating(true);
261
262 assert!(!state.tick_client_floating(true, 1));
263 assert!(!state.tick_client_floating(false, 1));
264
265 state.record_client_floating(true);
266 assert!(!state.tick_client_floating(true, 1));
267 }
268
269 #[test]
270 fn client_movement_clear_floating_resets_flag_and_counter() {
271 let mut state = ClientAuthoredMovementState::new();
272 state.record_client_floating(true);
273 assert!(!state.tick_client_floating(true, 1));
274
275 state.clear_client_floating();
276
277 assert!(!state.tick_client_floating(true, 1));
278 assert!(!state.tick_client_floating(true, 1));
279 }
280
281 #[test]
282 fn client_movement_reset_flying_ticks_preserves_current_floating_status() {
283 let mut state = ClientAuthoredMovementState::new();
284 state.record_client_floating(true);
285
286 assert!(!state.tick_client_floating(true, 1));
287 state.reset_flying_ticks();
288 assert!(!state.tick_client_floating(true, 1));
289 assert!(state.tick_client_floating(true, 1));
290 }
291}