diff --git a/lib/snf/src/bot.rs b/lib/snf/src/bot.rs index 3712341b..77936aa3 100644 --- a/lib/snf/src/bot.rs +++ b/lib/snf/src/bot.rs @@ -28,12 +28,16 @@ pub enum Error{ /* block types BLOCK_BOT_HEADER: -// Tegments are laid out in chronological order, +// Segments are laid out in chronological order, // but block_id is not necessarily in ascending order. // // This is to place the final segment close to the start of the file, // which allows the duration of the bot to be conveniently calculated // from the first and last instruction timestamps. +// +// Use exact physics version for replay playback +// Use highest compatible physics version for verification +u32 physics_version u32 num_segments for _ in 0..num_segments{ i64 time @@ -61,6 +65,7 @@ struct SegmentHeader{ #[binrw] #[brw(little)] struct Header{ + physics_version:u32, num_segments:u32, #[br(count=num_segments)] segments:Vec<SegmentHeader>, @@ -151,7 +156,7 @@ impl<R:BinReaderExt> StreamableBot<R>{ } const MAX_BLOCK_SIZE:usize=64*1024;//64 kB -pub fn write_bot<W:BinWriterExt>(mut writer:W,instructions:impl IntoIterator<Item=TimedPhysicsInstruction>)->Result<(),Error>{ +pub fn write_bot<W:BinWriterExt>(mut writer:W,physics_version:u32,instructions:impl IntoIterator<Item=TimedPhysicsInstruction>)->Result<(),Error>{ // decide which instructions to put in which segment // write segment 1 to block 1 // write segment N to block 2 @@ -251,6 +256,7 @@ pub fn write_bot<W:BinWriterExt>(mut writer:W,instructions:impl IntoIterator<Ite }; let header=Header{ + physics_version, num_segments:num_segments as u32, segments, }; diff --git a/strafe-client/src/physics.rs b/strafe-client/src/physics.rs index b0388764..b969c570 100644 --- a/strafe-client/src/physics.rs +++ b/strafe-client/src/physics.rs @@ -14,6 +14,45 @@ use strafesnet_common::integer::{self,vec3,mat3,Planar64,Planar64Vec3,Planar64Ma pub use strafesnet_common::physics::{Time,TimeInner}; use gameplay::ModeState; +// Physics bug fixes can easily desync all bots. +// +// When replaying a bot, use the exact physics version which it was recorded with. +// +// When validating a new bot, use the latest compatible physics version +// from the compatiblity matrix, since it may include bugfixes +// for things like clipping through walls with surgical precision +// i.e. without breaking bots which don't exploit the bug. +// +// Compatible physics versions should be determined empirically via leaderboard resimulation. +// Compatible physics versions should result in an identical leaderboard state, +// or the only bots which fail are ones exploiting a surgically patched bug. +#[derive(Clone,Copy,Hash,Debug,id::Id,Eq,PartialEq,Ord,PartialOrd)] +pub struct PhysicsVersion(u32); +pub const VERSION:PhysicsVersion=PhysicsVersion(0); +const LATEST_COMPATIBLE_VERSION:[u32;1+VERSION.0 as usize]=const{ + let compat=[0]; + + let mut input_version=0; + while input_version<compat.len(){ + // compatible version must be greater that or equal to the input version + assert!(input_version as u32<=compat[input_version]); + // compatible version must be a version that exists + assert!(compat[input_version]<=VERSION.0); + input_version+=1; + } + compat +}; +pub enum PhysicsVersionError{ + UnknownPhysicsVersion, +} +pub const fn get_latest_compatible_version(PhysicsVersion(version):PhysicsVersion)->Result<PhysicsVersion,PhysicsVersionError>{ + if (version as usize)<LATEST_COMPATIBLE_VERSION.len(){ + Ok(PhysicsVersion(LATEST_COMPATIBLE_VERSION[version as usize])) + }else{ + Err(PhysicsVersionError::UnknownPhysicsVersion) + } +} + pub type Body=crate::body::Body<TimeInner>; type MouseState=strafesnet_common::mouse::MouseState<TimeInner>; diff --git a/strafe-client/src/session.rs b/strafe-client/src/session.rs index d6518e65..1fa0d89d 100644 --- a/strafe-client/src/session.rs +++ b/strafe-client/src/session.rs @@ -299,7 +299,7 @@ impl InstructionConsumer<Instruction<'_>> for Session{ std::thread::spawn(move ||{ std::fs::create_dir_all("replays").unwrap(); let file=std::fs::File::create(file_name).unwrap(); - strafesnet_snf::bot::write_bot(std::io::BufWriter::new(file),replay.recording.instructions).unwrap(); + strafesnet_snf::bot::write_bot(std::io::BufWriter::new(file),crate::physics::VERSION.get(),replay.recording.instructions).unwrap(); }); }, }