Download crates/urt-gameplay/src/client_timer.rs from nmarshall/urt-rs: direct link, hf CLI and curl.
- Browser
- Download file 17.3 kB
-
https://huggingface.co/nmarshall/urt-rs/resolve/main/crates/urt-gameplay/src/client_timer.rs
- Command line
-
hf download hf://nmarshall/urt-rs/crates/urt-gameplay/src/client_timer.rs
-
curl -L -o client_timer.rs https://huggingface.co/nmarshall/urt-rs/resolve/main/crates/urt-gameplay/src/client_timer.rs
17.3 kB
| //! Recovered client timer contracts; native damage and frame scheduling stay explicit. | |
| pub struct TimerTerminalState { | |
| pub entity_flag: u32, | |
| pub client_flags: u32, | |
| pub elapsed_total: u32, | |
| } | |
| 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); | |
| } | |
| mod terminal_tests { | |
| use super::*; | |
| 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); | |
| } | |
| 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]; | |
| pub struct TimerLoopState { | |
| pub flags: u32, | |
| pub status: u32, | |
| pub rate: u32, | |
| pub phase: u32, | |
| pub timer: u32, | |
| pub residual: u32, | |
| } | |
| pub struct TimerLoopInput { | |
| pub health: u32, | |
| pub weapon: u32, | |
| pub target: u32, | |
| pub owner: u32, | |
| } | |
| 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<Vec<TimerDamage>, 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) | |
| } | |
| mod tick_tests { | |
| use super::*; | |
| fn input() -> TimerLoopInput { | |
| TimerLoopInput { | |
| health: 1, | |
| weapon: 7, | |
| target: 2, | |
| owner: 3, | |
| } | |
| } | |
| 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 | |
| }] | |
| ); | |
| } | |
| 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); | |
| } | |
| } | |
| mod recovery_tests { | |
| use super::*; | |
| fn input() -> TimerLoopInput { | |
| TimerLoopInput { | |
| health: 1, | |
| weapon: 7, | |
| target: 2, | |
| owner: 3, | |
| } | |
| } | |
| 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]); | |
| } | |
| 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); | |
| } | |
| } | |
| pub struct CompleteTimerState { | |
| pub activity: u32, | |
| pub entity_bit: u32, | |
| pub terminal: TimerTerminalState, | |
| pub ticks: TimerLoopState, | |
| } | |
| pub struct CompleteTimerInput { | |
| pub time: TimerTerminalInput, | |
| pub last_tick: u32, | |
| pub velocity: [u32; 3], | |
| pub entity_bits: u32, | |
| pub now: u32, | |
| pub damage: TimerLoopInput, | |
| } | |
| pub enum TimerEvent { | |
| Time, | |
| Damage(TimerDamage), | |
| } | |
| pub struct CompleteTimerResult { | |
| pub vector_squared: Option<u32>, | |
| pub events: Vec<TimerEvent>, | |
| } | |
| /// 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<CompleteTimerResult, Error> { | |
| 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, | |
| }) | |
| } | |
| 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, | |
| }, | |
| } | |
| } | |
| 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); | |
| } | |
| 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); | |
| } | |
| } | |
| 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, | |
| } | |
| pub struct TimerPreparation { | |
| pub proceed: bool, | |
| pub msec: Option<u32>, | |
| } | |
| /// 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), | |
| } | |
| } | |
| 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, | |
| } | |
| } | |
| 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); | |
| } | |
| 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)); | |
| } | |
| } | |