@@ -170,13 +170,29 @@ impl InputHistory {
170170 }
171171 }
172172
173- /// 丢弃所有 tick ≤ server_tick 的遗留记录(服务端已追上 )。
174- pub fn drop_until ( & mut self , server_tick : u32 ) {
175- while self . records . front ( ) . is_some_and ( |r| r. tick <= server_tick ) {
173+ /// 丢弃所有 tick ≤ input_tick 的遗留记录(服务端已确认这些输入 )。
174+ pub fn drop_until ( & mut self , input_tick : u32 ) {
175+ while self . records . front ( ) . is_some_and ( |r| r. tick <= input_tick ) {
176176 self . records . pop_front ( ) ;
177177 }
178178 }
179179
180+ /// 查找某个本地输入序号对应的预测位置。
181+ pub fn position_at ( & self , input_tick : u32 ) -> Option < Vec3 > {
182+ self . records
183+ . iter ( )
184+ . find ( |r| r. tick == input_tick)
185+ . map ( |r| r. position )
186+ }
187+
188+ /// 服务端确认某个输入后,若发现该输入时刻存在差值,后续未确认记录也要平移同样的差值。
189+ /// 否则下一次回执会继续拿“旧基准线”比较,造成重复校正。
190+ pub fn translate_remaining ( & mut self , delta : Vec3 ) {
191+ for record in & mut self . records {
192+ record. position += delta;
193+ }
194+ }
195+
180196 pub fn is_empty ( & self ) -> bool {
181197 self . records . is_empty ( )
182198 }
@@ -189,38 +205,70 @@ pub const SOFT_THRESHOLD_M: f32 = 0.1;
189205pub const HARD_THRESHOLD_M : f32 = 2.0 ;
190206
191207/// reconcile_self 的结果——方便调用方区分状态并在 HUD 上显示不同颜色。
192- #[ derive( Copy , Clone , Debug , PartialEq , Eq ) ]
208+ #[ derive( Copy , Clone , Debug , PartialEq ) ]
193209pub enum ReconcileResult {
194210 /// 误差可接受,不做修正。
195211 Ok ,
196- /// 误差超过 HARD_THRESHOLD,已把 physics 瞬移回服务端位置。
197- Snap ,
212+ /// 本地已没有对应输入 tick 的历史,通常是旧包或历史过短;忽略以避免高延迟旧回声拉扯。
213+ MissingHistory ,
214+ /// 误差中等,调用方应把该差值加入 pending correction,按帧软修正。
215+ SoftCorrection ( Vec3 ) ,
216+ /// 误差过大,已把当前 physics 位置按同 tick 差值立即平移。
217+ HardCorrection ( Vec3 ) ,
198218}
199219
200- /// 对比服务端权威位置与本地 physics,决定是接受还是 Snap 。
220+ /// 对比服务端权威位置与同一输入 tick 的本地预测记录,决定是否校正 。
201221///
202222/// * `server_position` — PlayerTick 中与自己 entity_id 对应的权威位置(脚底)
203- /// * `server_tick ` — 该 PlayerTick 的 server tick(用于清历史)
223+ /// * `acked_input_tick ` — 服务端已处理到的本地 PlayerInput. tick
204224/// * `physics` — 本地物理状态(feet_position 在此被修正)
205- /// * `history` — 输入记录历史(用于清掉 server 已处理过的步数 )
225+ /// * `history` — 输入记录历史(用于查找同 tick 位置并清掉已确认输入 )
206226pub fn reconcile_self (
207227 server_position : Vec3 ,
208- server_tick : u32 ,
228+ acked_input_tick : u32 ,
209229 physics : & mut LocalPhysics ,
210230 history : & mut InputHistory ,
211231) -> ReconcileResult {
212- history. drop_until ( server_tick) ;
232+ let Some ( predicted_position) = history. position_at ( acked_input_tick) else {
233+ history. drop_until ( acked_input_tick) ;
234+ return ReconcileResult :: MissingHistory ;
235+ } ;
236+
237+ let correction = server_position - predicted_position;
238+ let error = correction. length ( ) ;
213239
214- let error = ( physics . feet_position - server_position ) . length ( ) ;
240+ history . drop_until ( acked_input_tick ) ;
215241
216- if error >= HARD_THRESHOLD_M {
217- physics. feet_position = server_position;
218- ReconcileResult :: Snap
219- } else if error < SOFT_THRESHOLD_M {
242+ if error < SOFT_THRESHOLD_M {
220243 ReconcileResult :: Ok
244+ } else if error >= HARD_THRESHOLD_M {
245+ physics. feet_position += correction;
246+ history. translate_remaining ( correction) ;
247+ ReconcileResult :: HardCorrection ( correction)
221248 } else {
222- // 中等误差:Phase 5 不做软插值(Phase 7 加 blend)
223- ReconcileResult :: Ok
249+ history. translate_remaining ( correction) ;
250+ ReconcileResult :: SoftCorrection ( correction)
251+ }
252+ }
253+
254+ /// 每渲染帧应用一部分待修正位移,避免中等误差造成肉眼可见瞬移。
255+ pub fn apply_pending_position_correction (
256+ physics : & mut LocalPhysics ,
257+ pending_correction : & mut Vec3 ,
258+ dt : f32 ,
259+ ) {
260+ if pending_correction. length_squared ( ) < 0.000001 {
261+ * pending_correction = Vec3 :: ZERO ;
262+ return ;
263+ }
264+
265+ let blend = ( dt * 5.0 ) . clamp ( 0.0 , 1.0 ) ;
266+ let step = * pending_correction * blend;
267+ physics. feet_position += step;
268+ * pending_correction -= step;
269+
270+ if pending_correction. length_squared ( ) < 0.000001 {
271+ * pending_correction = Vec3 :: ZERO ;
224272 }
225273}
226274
@@ -254,21 +302,47 @@ mod prediction_tests {
254302 fn reconcile_returns_ok_for_small_error ( ) {
255303 let mut physics = LocalPhysics :: new ( Vec3 :: new ( 10.0 , 64.0 , 10.0 ) ) ;
256304 let mut history = InputHistory :: new ( 10 ) ;
257- history. push ( 1 , Vec3 :: new ( 10.0 , 64.0 , 10.0 ) ) ;
305+ history. push ( 2 , Vec3 :: new ( 10.0 , 64.0 , 10.0 ) ) ;
258306 // 误差 0.05 < SOFT
259307 let r = reconcile_self ( Vec3 :: new ( 10.03 , 64.0 , 10.04 ) , 2 , & mut physics, & mut history) ;
260308 assert_eq ! ( r, ReconcileResult :: Ok ) ;
261309 assert ! ( ( physics. feet_position - Vec3 :: new( 10.0 , 64.0 , 10.0 ) ) . length( ) < 0.001 ) ;
262310 }
263311
264312 #[ test]
265- fn reconcile_snaps_for_large_error ( ) {
266- let mut physics = LocalPhysics :: new ( Vec3 :: new ( 10 .0, 64.0 , 10.0 ) ) ;
313+ fn reconcile_shifts_current_by_same_tick_error_for_large_error ( ) {
314+ let mut physics = LocalPhysics :: new ( Vec3 :: new ( 30 .0, 64.0 , 10.0 ) ) ;
267315 let mut history = InputHistory :: new ( 10 ) ;
268- history. push ( 1 , Vec3 :: new ( 10.0 , 64.0 , 10.0 ) ) ;
269- // 误差 10m > HARD → Snap
316+ history. push ( 2 , Vec3 :: new ( 10.0 , 64.0 , 10.0 ) ) ;
317+ history. push ( 3 , Vec3 :: new ( 12.0 , 64.0 , 10.0 ) ) ;
318+ // 误差 10m > HARD → 当前预测位置按差值平移,而不是退回旧快照位置。
270319 let r = reconcile_self ( Vec3 :: new ( 20.0 , 64.0 , 10.0 ) , 2 , & mut physics, & mut history) ;
271- assert_eq ! ( r, ReconcileResult :: Snap ) ;
272- assert ! ( ( physics. feet_position - Vec3 :: new( 20.0 , 64.0 , 10.0 ) ) . length( ) < 0.001 ) ;
320+ assert_eq ! (
321+ r,
322+ ReconcileResult :: HardCorrection ( Vec3 :: new( 10.0 , 0.0 , 0.0 ) )
323+ ) ;
324+ assert ! ( ( physics. feet_position - Vec3 :: new( 40.0 , 64.0 , 10.0 ) ) . length( ) < 0.001 ) ;
325+ assert_eq ! ( history. position_at( 3 ) , Some ( Vec3 :: new( 22.0 , 64.0 , 10.0 ) ) ) ;
326+ }
327+
328+ #[ test]
329+ fn reconcile_returns_soft_correction_for_mid_error ( ) {
330+ let mut physics = LocalPhysics :: new ( Vec3 :: new ( 10.5 , 64.0 , 10.0 ) ) ;
331+ let mut history = InputHistory :: new ( 10 ) ;
332+ history. push ( 7 , Vec3 :: new ( 10.0 , 64.0 , 10.0 ) ) ;
333+
334+ let r = reconcile_self ( Vec3 :: new ( 10.5 , 64.0 , 10.0 ) , 7 , & mut physics, & mut history) ;
335+ assert_eq ! ( r, ReconcileResult :: SoftCorrection ( Vec3 :: new( 0.5 , 0.0 , 0.0 ) ) ) ;
336+ assert ! ( ( physics. feet_position - Vec3 :: new( 10.5 , 64.0 , 10.0 ) ) . length( ) < 0.001 ) ;
337+ }
338+
339+ #[ test]
340+ fn reconcile_missing_history_does_not_snap_to_stale_echo ( ) {
341+ let mut physics = LocalPhysics :: new ( Vec3 :: new ( 30.0 , 64.0 , 10.0 ) ) ;
342+ let mut history = InputHistory :: new ( 10 ) ;
343+
344+ let r = reconcile_self ( Vec3 :: new ( 10.0 , 64.0 , 10.0 ) , 99 , & mut physics, & mut history) ;
345+ assert_eq ! ( r, ReconcileResult :: MissingHistory ) ;
346+ assert ! ( ( physics. feet_position - Vec3 :: new( 30.0 , 64.0 , 10.0 ) ) . length( ) < 0.001 ) ;
273347 }
274348}
0 commit comments