From c0eaa882af00c4c8d36e17da601c08895b35fbd0 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Thu, 7 Aug 2025 19:17:35 -0700 Subject: [PATCH 1/2] validator: sequential check --- validation/src/check.rs | 61 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/validation/src/check.rs b/validation/src/check.rs index bf37214..1727f9b 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -47,12 +47,20 @@ impl From for CheckRequest{ } } -#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)] +#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq,Ord,PartialOrd)] struct ModeID(u64); impl ModeID{ const MAIN:Self=Self(0); const BONUS:Self=Self(1); } +impl std::fmt::Display for ModeID{ + fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ + match self{ + &ModeID::MAIN=>write!(f,"Main"), + &ModeID(mode_id)=>write!(f,"Bonus{mode_id}"), + } + } +} enum Zone{ Start, Finish, @@ -420,6 +428,11 @@ impl TryFrom> for MapInfoOwned{ struct Exists; struct Absent; +struct NonSequentialCheck{ + sequential:usize, + non_sequential:Vec, +} + /// The result of every map check. struct MapCheck<'a>{ // === METADATA CHECKS === @@ -446,6 +459,8 @@ struct MapCheck<'a>{ mode_finish_counts:SetDifferenceCheck>>, // Check for dangling MapAnticheat zones (no associated MapStart) mode_anticheat_counts:SetDifferenceCheck>>, + // Check that modes are sequential + modes_sequential:Result<(),NonSequentialCheck>, // Spawn1 must exist spawn1:Result, // Check for dangling Teleport# (no associated Spawn#) @@ -514,6 +529,36 @@ impl<'a> ModelInfo<'a>{ let mode_anticheat_counts=SetDifferenceCheckContextAllowNone::new(self.counts.mode_anticheat_counts) .check(&self.counts.mode_start_counts); + // There must not be non-sequential modes. If Bonus100 exists, Bonuses 1-99 had better also exist. + fn count_sequential(modes:&HashMap>)->usize{ + for mode_id in 0..modes.len(){ + if !modes.contains_key(&ModeID(mode_id as u64)){ + return mode_id; + } + } + return modes.len(); + } + let modes_sequential={ + let sequential=count_sequential(&self.counts.mode_start_counts); + if sequential==self.counts.mode_start_counts.len(){ + Ok(()) + }else{ + let mut non_sequential=Vec::with_capacity(self.counts.mode_start_counts.len()-sequential); + for (&mode_id,_) in &self.counts.mode_start_counts{ + let ModeID(mode_id_u64)=mode_id; + if !(mode_id_u64 ModelInfo<'a>{ mode_start_counts, mode_finish_counts, mode_anticheat_counts, + modes_sequential, spawn1, teleport_counts, spawn_counts, @@ -573,6 +619,7 @@ impl MapCheck<'_>{ mode_start_counts:DuplicateCheck(Ok(())), mode_finish_counts:SetDifferenceCheck(Ok(())), mode_anticheat_counts:SetDifferenceCheck(Ok(())), + modes_sequential:Ok(()), spawn1:Ok(Exists), teleport_counts:SetDifferenceCheck(Ok(())), spawn_counts:DuplicateCheck(Ok(())), @@ -746,6 +793,17 @@ impl MapCheck<'_>{ } } }; + let sequential_modes=match &self.modes_sequential{ + Ok(())=>passed!("SequentialModes"), + Err(context)=>{ + let sequential=context.sequential; + let non_sequential=context.non_sequential.len(); + let plural_sequential=if sequential==1{"mode"}else{"modes"}; + let plural_non_sequential=if non_sequential==1{"mode"}else{"modes"}; + let comma_separated=Separated::new(", ",||context.non_sequential.iter()); + summary_format!("SequentialModes","{sequential} {plural_sequential} OK; {non_sequential} {plural_non_sequential} should use a lower ModeID: {comma_separated}") + } + }; let spawn1=match &self.spawn1{ Ok(Exists)=>passed!("Spawn1"), Err(Absent)=>summary_format!("Spawn1","Model has no Spawn1"), @@ -824,6 +882,7 @@ impl MapCheck<'_>{ extra_finish, missing_finish, dangling_anticheat, + sequential_modes, spawn1, dangling_teleport, duplicate_spawns, -- 2.49.1 From 7e3b8645e02443f2b89a2148bb376a9f11c087c6 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Thu, 7 Aug 2025 19:19:07 -0700 Subject: [PATCH 2/2] validator: refine SequentialModes error message --- validation/src/check.rs | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/validation/src/check.rs b/validation/src/check.rs index 1727f9b..8523f6b 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -428,11 +428,6 @@ impl TryFrom> for MapInfoOwned{ struct Exists; struct Absent; -struct NonSequentialCheck{ - sequential:usize, - non_sequential:Vec, -} - /// The result of every map check. struct MapCheck<'a>{ // === METADATA CHECKS === @@ -460,7 +455,7 @@ struct MapCheck<'a>{ // Check for dangling MapAnticheat zones (no associated MapStart) mode_anticheat_counts:SetDifferenceCheck>>, // Check that modes are sequential - modes_sequential:Result<(),NonSequentialCheck>, + modes_sequential:Result<(),Vec>, // Spawn1 must exist spawn1:Result, // Check for dangling Teleport# (no associated Spawn#) @@ -552,10 +547,7 @@ impl<'a> ModelInfo<'a>{ } // sort so it's prettier when it prints out non_sequential.sort(); - Err(NonSequentialCheck{ - sequential, - non_sequential, - }) + Err(non_sequential) } }; @@ -796,12 +788,10 @@ impl MapCheck<'_>{ let sequential_modes=match &self.modes_sequential{ Ok(())=>passed!("SequentialModes"), Err(context)=>{ - let sequential=context.sequential; - let non_sequential=context.non_sequential.len(); - let plural_sequential=if sequential==1{"mode"}else{"modes"}; + let non_sequential=context.len(); let plural_non_sequential=if non_sequential==1{"mode"}else{"modes"}; - let comma_separated=Separated::new(", ",||context.non_sequential.iter()); - summary_format!("SequentialModes","{sequential} {plural_sequential} OK; {non_sequential} {plural_non_sequential} should use a lower ModeID: {comma_separated}") + let comma_separated=Separated::new(", ",||context.iter()); + summary_format!("SequentialModes","{non_sequential} {plural_non_sequential} should use a lower ModeID (no gaps): {comma_separated}") } }; let spawn1=match &self.spawn1{ -- 2.49.1