//! Recovered client timer contracts; native damage and frame scheduling stay explicit. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct TimerTerminalState { pub entity_flag: u32, pub client_flags: u32, pub elapsed_total: u32, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct TimerTerminalInput { pub stamp: u32, pub cooldown: u32, pub tick: u32, pub deadline: u32, pub msec: u32, } /// Original150791 pre-loop fragment: signed comparison after word arithmetic. pub fn timer_entity_deadline(state: &mut TimerTerminalState, input: TimerTerminalInput) { let limit = input.stamp.wrapping_add(input.cooldown.wrapping_mul(1000)); if (limit as i32) < input.tick as i32 { state.entity_flag = 0; } } /// Original151085 terminal fragment: preserve unrelated flag bits and timestamps. pub fn timer_terminal_tail(state: &mut TimerTerminalState, input: TimerTerminalInput) { if state.client_flags & 2 != 0 && (input.tick as i32) > input.deadline as i32 { state.client_flags &= !2; } state.elapsed_total = state.elapsed_total.wrapping_add(input.msec); } #[cfg(test)] mod terminal_tests { use super::*; #[test] fn strict_equalities_preserve_flags_and_accumulation_wraps() { let mut s = TimerTerminalState { entity_flag: 7, client_flags: 7, elapsed_total: u32::MAX, }; let i = TimerTerminalInput { stamp: 8000, cooldown: 2, tick: 10000, deadline: 10000, msec: 2, }; timer_entity_deadline(&mut s, i); timer_terminal_tail(&mut s, i); assert_eq!( s, TimerTerminalState { entity_flag: 7, client_flags: 7, elapsed_total: 1 } ); let i = TimerTerminalInput { tick: 10001, ..i }; timer_entity_deadline(&mut s, i); timer_terminal_tail(&mut s, i); assert_eq!(s.entity_flag, 0); assert_eq!(s.client_flags, 5); } #[test] fn cooldown_wrap_precedes_signed_compare() { let mut s = TimerTerminalState { entity_flag: 7, client_flags: 0, elapsed_total: 0, }; let i = TimerTerminalInput { stamp: 7999, cooldown: i32::MAX as u32, tick: 10000, deadline: 0, msec: 0, }; timer_entity_deadline(&mut s, i); assert_eq!(s.entity_flag, 0); s.entity_flag = 7; timer_entity_deadline( &mut s, TimerTerminalInput { tick: i32::MIN as u32, ..i }, ); assert_eq!(s.entity_flag, 7); } } use crate::native_policy::Error; pub const TIMER_DIVISORS: [i32; 6] = [1120403456, 9, 4, 3, 2, 1]; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct TimerLoopState { pub flags: u32, pub status: u32, pub rate: u32, pub phase: u32, pub timer: u32, pub residual: u32, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct TimerLoopInput { pub health: u32, pub weapon: u32, pub target: u32, pub owner: u32, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct TimerDamage { pub target: u32, pub inflictor: u32, pub attacker: u32, pub direction: u32, pub point: u32, pub amount: u32, pub flags: u32, pub means: u32, pub weapon: u32, } /// Original150810 complete finite-domain loop. Damage effects are requests; /// supplied health is deliberately unchanged. Rate0 aliases the preceding word. pub fn timer_tick_loop( state: &mut TimerLoopState, input: TimerLoopInput, ) -> Result, Error> { if state.rate > 5 { return Err(Error::InvalidIndex); } if state.residual as i32 > 10000 { return Err(Error::InvalidInput); } if state.residual as i32 >= 100 && state.flags & 512 != 0 && !f32::from_bits(state.timer).is_finite() { return Err(Error::NonFinite); } let mut next = *state; let mut damage = vec![]; while next.residual as i32 >= 100 { next.phase = ((next.phase.wrapping_add(1) as i32) % TIMER_DIVISORS[next.rate as usize]) as u32; next.residual = next.residual.wrapping_sub(100); if next.flags & 256 != 0 && (input.health as i32) > 0 && next.phase == 0 { damage.push(TimerDamage { target: input.target, inflictor: input.owner, attacker: input.owner, direction: 0, point: 0, amount: 1, flags: 0, means: 23, weapon: input.weapon, }); } if next.flags & 512 != 0 { let mut timer = f32::from_bits(next.timer); if next.flags & 256 != 0 && timer <= 0.0 { next.flags &= !256; next.rate = 0; timer = 0.0; } if timer > 0.0 { timer -= 0.1f32; if timer <= 0.0 { next.flags &= !256; next.rate = 0; timer = 0.0; } } else if next.flags & 128 != 0 { next.flags &= !128; next.status &= !1; } next.timer = timer.to_bits(); } } *state = next; Ok(damage) } #[cfg(test)] mod tick_tests { use super::*; fn input() -> TimerLoopInput { TimerLoopInput { health: 1, weapon: 7, target: 2, owner: 3, } } #[test] fn phase_wrap_signed_remainder_alias_and_request_arguments() { let mut s = TimerLoopState { flags: 256, status: 3, rate: 0, phase: 0, timer: 0, residual: 250, }; assert!(timer_tick_loop(&mut s, input()).unwrap().is_empty()); assert_eq!(s.phase, 2); assert_eq!(s.residual, 50); s.rate = 5; s.residual = 100; s.phase = i32::MAX as u32; let p = timer_tick_loop(&mut s, input()).unwrap(); assert_eq!(s.phase, 0); assert_eq!( p, [TimerDamage { target: 2, inflictor: 3, attacker: 3, direction: 0, point: 0, amount: 1, flags: 0, means: 23, weapon: 7 }] ); } #[test] fn negative_residual_skips_and_unsupported_domains_are_atomic() { let mut s = TimerLoopState { flags: 256, status: 3, rate: 2, phase: 0, timer: 0, residual: u32::MAX, }; let old = s; assert!(timer_tick_loop(&mut s, input()).unwrap().is_empty()); assert_eq!(s, old); s.rate = 6; let old = s; assert!(timer_tick_loop(&mut s, input()).is_err()); assert_eq!(s, old); } } #[cfg(test)] mod recovery_tests { use super::*; fn input() -> TimerLoopInput { TimerLoopInput { health: 1, weapon: 7, target: 2, owner: 3, } } #[test] fn zero_crossing_delays_secondary_cleanup_and_switches_divisor() { let mut s = TimerLoopState { flags: 896, status: 3, rate: 2, phase: 3, timer: 0.05f32.to_bits(), residual: 100, }; let p = timer_tick_loop(&mut s, input()).unwrap(); assert_eq!(p.len(), 1); assert_eq!( [s.flags, s.status, s.rate, s.phase, s.timer], [640, 3, 0, 0, 0] ); s.residual = 100; timer_tick_loop(&mut s, input()).unwrap(); assert_eq!([s.flags, s.status, s.rate, s.phase], [512, 2, 0, 1]); } #[test] fn initial_nonpositive_timer_cleans_immediately_and_nonfinite_rejects_atomically() { let mut s = TimerLoopState { flags: 896, status: 3, rate: 2, phase: 3, timer: (-0.1f32).to_bits(), residual: 100, }; timer_tick_loop(&mut s, input()).unwrap(); assert_eq!([s.flags, s.status, s.rate, s.timer], [512, 2, 0, 0]); s.timer = f32::NAN.to_bits(); s.residual = 100; let old = s; assert_eq!(timer_tick_loop(&mut s, input()), Err(Error::NonFinite)); assert_eq!(s, old); } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct CompleteTimerState { pub activity: u32, pub entity_bit: u32, pub terminal: TimerTerminalState, pub ticks: TimerLoopState, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct CompleteTimerInput { pub time: TimerTerminalInput, pub last_tick: u32, pub velocity: [u32; 3], pub entity_bits: u32, pub now: u32, pub damage: TimerLoopInput, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum TimerEvent { Time, Damage(TimerDamage), } #[derive(Clone, Debug, PartialEq, Eq)] pub struct CompleteTimerResult { pub vector_squared: Option, pub events: Vec, } /// Complete150692 persistent-state body through its void-return boundary. Time /// is supplied and damage effects stay captured; this is not a live health VM. pub fn complete_client_timer( records: &mut [crate::native_policy::FilterRecord], state: &mut CompleteTimerState, input: CompleteTimerInput, ) -> Result { use crate::filter_commands::{FilterFrameInput, FilterFrameState, filter_frame_prefix}; let mut staged = records.to_vec(); let mut next = *state; let mut prefix = FilterFrameState { activity: next.activity, flag: next.entity_bit, accumulator: next.ticks.residual, }; let out = filter_frame_prefix( &mut staged, &mut prefix, FilterFrameInput { tick: input.time.tick, last_tick: input.last_tick, velocity: input.velocity, entity_flags: input.entity_bits, msec: input.time.msec, now: input.now, }, )?; next.activity = prefix.activity; next.entity_bit = prefix.flag; next.ticks.residual = prefix.accumulator; timer_entity_deadline(&mut next.terminal, input.time); let damage = timer_tick_loop(&mut next.ticks, input.damage)?; timer_terminal_tail(&mut next.terminal, input.time); let mut events = vec![TimerEvent::Time]; events.extend(damage.into_iter().map(TimerEvent::Damage)); records.copy_from_slice(&staged); *state = next; Ok(CompleteTimerResult { vector_squared: out.vector_squared, events, }) } #[cfg(test)] mod complete_tests { use super::*; fn state() -> CompleteTimerState { CompleteTimerState { activity: 0, entity_bit: 17, terminal: TimerTerminalState { entity_flag: 7, client_flags: 7, elapsed_total: u32::MAX, }, ticks: TimerLoopState { flags: 896, status: 3, rate: 2, phase: 3, timer: 0.05f32.to_bits(), residual: 250, }, } } fn input() -> CompleteTimerInput { CompleteTimerInput { time: TimerTerminalInput { stamp: 7999, cooldown: 2, tick: 10000, deadline: 9999, msec: 100, }, last_tick: 6999, velocity: [11f32.to_bits(), 0, 0], entity_bits: 64, now: 60, damage: TimerLoopInput { health: 1, weapon: 7, target: 2, owner: 3, }, } } #[test] fn shared_residual_ordering_terminal_fields_and_full_expiry() { let mut rows = vec![ crate::native_policy::FilterRecord { mask: 7, comparison: 8, metadata: 0 }; 4096 ]; let mut s = state(); let out = complete_client_timer(&mut rows, &mut s, input()).unwrap(); assert_eq!(s.activity, 1); assert_eq!(s.entity_bit, 1); assert_eq!( s.terminal, TimerTerminalState { entity_flag: 0, client_flags: 5, elapsed_total: 99 } ); assert_eq!( [ s.ticks.flags, s.ticks.status, s.ticks.rate, s.ticks.phase, s.ticks.timer, s.ticks.residual ], [512, 2, 0, 2, 0, 50] ); assert_eq!(out.events.len(), 2); assert_eq!(out.events[0], TimerEvent::Time); assert!(matches!(out.events[1], TimerEvent::Damage(_))); assert_eq!(rows[4095].comparison, u32::MAX); } #[test] fn unsupported_loop_rolls_back_prefix_and_expiry_together() { let mut rows = vec![ crate::native_policy::FilterRecord { mask: 7, comparison: 8, metadata: 0 }; 4096 ]; let before = rows.clone(); let mut s = state(); s.ticks.rate = 6; let old = s; assert!(complete_client_timer(&mut rows, &mut s, input()).is_err()); assert_eq!(rows, before); assert_eq!(s, old); } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct TimerPrepareState { pub connection: u32, pub exception: u32, pub command_time: u32, pub current_time: u32, pub quantize: u32, pub copied_tick: u32, pub copied_command: u32, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct TimerPreparation { pub proceed: bool, pub msec: Option, } /// Original154089 prefix only. Sequential signed bounds use wrapping words; /// optional8ms command rounding occurs after delta calculation, without recompute. pub fn prepare_client_timing(state: &mut TimerPrepareState, tick: u32) -> TimerPreparation { if state.connection != 2 { return TimerPreparation { proceed: false, msec: None, }; } state.copied_tick = tick; state.copied_command = state.command_time; let upper = tick.wrapping_add(200); let lower = tick.wrapping_sub(1000); if state.command_time as i32 > upper as i32 { state.command_time = upper; } if (state.command_time as i32) < lower as i32 { state.command_time = lower; } let mut delta = state.command_time.wrapping_sub(state.current_time); if (delta as i32) < 1 && state.exception != 2 { return TimerPreparation { proceed: false, msec: Some(delta), }; } if delta as i32 > 200 { delta = 200; } if state.quantize != 0 { state.command_time = ((state.command_time.wrapping_add(7) as i32) / 8).wrapping_mul(8) as u32; } TimerPreparation { proceed: true, msec: Some(delta), } } #[cfg(test)] mod prepare_tests { use super::*; fn state() -> TimerPrepareState { TimerPrepareState { connection: 2, exception: 0, command_time: 9999, current_time: 9990, quantize: 1, copied_tick: 17, copied_command: 18, } } #[test] fn rounding_preserves_pre_round_delta_and_disconnected_state_is_untouched() { let mut s = state(); let out = prepare_client_timing(&mut s, 10000); assert_eq!( out, TimerPreparation { proceed: true, msec: Some(9) } ); assert_eq!(s.command_time, 10000); assert_eq!(s.current_time, 9990); assert_eq!(s.copied_command, 9999); assert_eq!(s.copied_tick, 10000); s.connection = 1; let before = s; assert_eq!( prepare_client_timing(&mut s, 20000), TimerPreparation { proceed: false, msec: None } ); assert_eq!(s, before); } #[test] fn exception_allows_negative_delta_and_bounds_are_sequential_under_wrap() { let mut s = state(); s.exception = 2; s.command_time = 8000; s.current_time = 10000; let out = prepare_client_timing(&mut s, 10000); assert_eq!( out, TimerPreparation { proceed: true, msec: Some((-1000i32) as u32) } ); assert_eq!(s.command_time, 9000); s.exception = 0; s.command_time = i32::MAX as u32; s.current_time = 0; s.quantize = 0; let out = prepare_client_timing(&mut s, i32::MAX as u32); assert_eq!(s.command_time, (i32::MAX - 1000) as u32); assert_eq!(out.msec, Some(200)); } }