diff --git a/validation/src/check.rs b/validation/src/check.rs
index f96e5c1..d001dd3 100644
--- a/validation/src/check.rs
+++ b/validation/src/check.rs
@@ -43,27 +43,28 @@ impl From<crate::nats_types::CheckSubmissionRequest> for CheckRequest{
 enum Zone{
 	Start(ModeID),
 	Finish(ModeID),
+	Anticheat(ModeID),
 }
 
 #[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
 struct ModeID(u64);
+macro_rules! write_zone{
+	($fname:ident,$zone:expr)=>{
+		fn $fname(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
+			match self{
+				ModeID(0)=>write!(f,concat!("Map",$zone)),
+				ModeID(1)=>write!(f,concat!("Bonus",$zone)),
+				ModeID(other)=>write!(f,concat!("Bonus{}",$zone),other),
+			}
+		}
+	};
+}
 impl ModeID{
 	const MAIN:Self=Self(0);
 	const BONUS:Self=Self(1);
-	fn write_start_zone(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
-		match self{
-			ModeID(0)=>write!(f,"MapStart"),
-			ModeID(1)=>write!(f,"BonusStart"),
-			ModeID(other)=>write!(f,"Bonus{other}Start"),
-		}
-	}
-	fn write_finish_zone(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
-		match self{
-			ModeID(0)=>write!(f,"MapFinish"),
-			ModeID(1)=>write!(f,"BonusFinish"),
-			ModeID(other)=>write!(f,"Bonus{other}Finish"),
-		}
-	}
+	write_zone!(write_start_zone,"Start");
+	write_zone!(write_finish_zone,"Finish");
+	write_zone!(write_anticheat_zone,"Anticheat");
 }
 #[allow(dead_code)]
 pub enum ZoneParseError{
@@ -76,8 +77,10 @@ impl std::str::FromStr for Zone{
 		match s{
 			"MapStart"=>Ok(Self::Start(ModeID::MAIN)),
 			"MapFinish"=>Ok(Self::Finish(ModeID::MAIN)),
+			"MapAnticheat"=>Ok(Self::Anticheat(ModeID::MAIN)),
 			"BonusStart"=>Ok(Self::Start(ModeID::BONUS)),
 			"BonusFinish"=>Ok(Self::Finish(ModeID::BONUS)),
+			"BonusAnticheat"=>Ok(Self::Anticheat(ModeID::BONUS)),
 			other=>{
 				let bonus_start_pattern=lazy_regex::lazy_regex!(r"^Bonus(\d+)Start$|^BonusStart(\d+)$");
 				if let Some(captures)=bonus_start_pattern.captures(other){
@@ -87,6 +90,10 @@ impl std::str::FromStr for Zone{
 				if let Some(captures)=bonus_finish_pattern.captures(other){
 					return Ok(Self::Finish(ModeID(captures[1].parse().map_err(ZoneParseError::ParseInt)?)));
 				}
+				let bonus_finish_pattern=lazy_regex::lazy_regex!(r"^Bonus(\d+)Anticheat$|^BonusAnticheat(\d+)$");
+				if let Some(captures)=bonus_finish_pattern.captures(other){
+					return Ok(Self::Anticheat(ModeID(captures[1].parse().map_err(ZoneParseError::ParseInt)?)));
+				}
 				Err(ZoneParseError::NoCaptures)
 			}
 		}
@@ -106,6 +113,7 @@ struct WormholeOutID(u64);
 struct Counts{
 	mode_start_counts:HashMap<ModeID,u64>,
 	mode_finish_counts:HashMap<ModeID,u64>,
+	mode_anticheat_counts:HashMap<ModeID,u64>,
 	spawn_counts:HashMap<SpawnID,u64>,
 	wormhole_out_counts:HashMap<WormholeOutID,u64>,
 }
@@ -129,7 +137,8 @@ pub fn get_model_info<'a>(dom:&'a rbx_dom_weak::WeakDom,model_instance:&'a rbx_d
 			match instance.name.parse(){
 				Ok(Zone::Start(mode_id))=>*counts.mode_start_counts.entry(mode_id).or_insert(0)+=1,
 				Ok(Zone::Finish(mode_id))=>*counts.mode_finish_counts.entry(mode_id).or_insert(0)+=1,
-				_=>(),
+				Ok(Zone::Anticheat(mode_id))=>*counts.mode_anticheat_counts.entry(mode_id).or_insert(0)+=1,
+				Err(_)=>(),
 			}
 			// Spawns
 			let spawn_pattern=lazy_regex::lazy_regex!(r"^Spawn(\d+)$");
@@ -217,12 +226,36 @@ impl<ID> DuplicateCheckContext<ID>{
 }
 
 // check that there is at least one matching item for each item in a reference set, and no extra items
-pub struct SetDifferenceCheckContext<ID>{
+pub struct SetDifferenceCheckContextAllowNone<ID>{
+	extra:HashMap<ID,u64>,
+}
+pub struct SetDifferenceCheckContextAtLeastOne<ID>{
 	extra:HashMap<ID,u64>,
 	missing:HashSet<ID>,
 }
-pub struct SetDifferenceCheck<ID>(Result<(),SetDifferenceCheckContext<ID>>);
-impl<ID> SetDifferenceCheckContext<ID>{
+pub struct SetDifferenceCheck<Context>(Result<(),Context>);
+impl<ID> SetDifferenceCheckContextAllowNone<ID>{
+	fn new(initial_set:HashMap<ID,u64>)->Self{
+		Self{
+			extra:initial_set,
+		}
+	}
+}
+impl<ID:Eq+std::hash::Hash> SetDifferenceCheckContextAllowNone<ID>{
+	fn check<T>(mut self,reference_set:&HashMap<ID,T>)->SetDifferenceCheck<Self>{
+		// remove correct entries
+		for (id,_) in reference_set{
+			self.extra.remove(id);
+		}
+		// if any entries remain, they are incorrect
+		if self.extra.is_empty(){
+			SetDifferenceCheck(Ok(()))
+		}else{
+			SetDifferenceCheck(Err(self))
+		}
+	}
+}
+impl<ID> SetDifferenceCheckContextAtLeastOne<ID>{
 	fn new(initial_set:HashMap<ID,u64>)->Self{
 		Self{
 			extra:initial_set,
@@ -230,21 +263,20 @@ impl<ID> SetDifferenceCheckContext<ID>{
 		}
 	}
 }
-impl<ID:Copy+Eq+std::hash::Hash> SetDifferenceCheckContext<ID>{
-	fn check<T>(self,reference_set:&HashMap<ID,T>)->SetDifferenceCheck<ID>{
-		let Self{mut extra,mut missing}=self;
+impl<ID:Copy+Eq+std::hash::Hash> SetDifferenceCheckContextAtLeastOne<ID>{
+	fn check<T>(mut self,reference_set:&HashMap<ID,T>)->SetDifferenceCheck<Self>{
 		// remove correct entries
 		for (id,_) in reference_set{
-			if extra.remove(id).is_none(){
+			if self.extra.remove(id).is_none(){
 				// the set did not contain a required item.  This is a fail
-				missing.insert(*id);
+				self.missing.insert(*id);
 			}
 		}
 		// if any entries remain, they are incorrect
-		if extra.is_empty()&&missing.is_empty(){
+		if self.extra.is_empty()&&self.missing.is_empty(){
 			SetDifferenceCheck(Ok(()))
 		}else{
-			SetDifferenceCheck(Err(Self{extra,missing}))
+			SetDifferenceCheck(Err(self))
 		}
 	}
 }
@@ -278,8 +310,9 @@ pub struct MapCheck<'a>{
 	// No duplicate map starts (including bonuses)
 	mode_start_counts:DuplicateCheck<ModeID>,
 	// At least one finish zone for each start zone, and no finishes with no start
-	mode_finish_counts:SetDifferenceCheck<ModeID>,
-	// TODO: check for dangling MapAnticheat zones (no associated MapStart)
+	mode_finish_counts:SetDifferenceCheck<SetDifferenceCheckContextAtLeastOne<ModeID>>,
+	// check for dangling MapAnticheat zones (no associated MapStart)
+	mode_anticheat_counts:SetDifferenceCheck<SetDifferenceCheckContextAllowNone<ModeID>>,
 	// Spawn1 must exist
 	spawn1:Result<(),()>,
 	// No duplicate Spawn#
@@ -331,7 +364,12 @@ impl<'a> ModelInfo<'a>{
 		};
 
 		// check that at least one end zone exists for each start zone.
-		let mode_finish_counts=SetDifferenceCheckContext::new(self.counts.mode_finish_counts)
+		let mode_finish_counts=SetDifferenceCheckContextAtLeastOne::new(self.counts.mode_finish_counts)
+			.check(&self.counts.mode_start_counts);
+
+		// check that there are no anticheat zones that have no corresponding start zone.
+		// modes are allowed to have 0 anticheat zones.
+		let mode_anticheat_counts=SetDifferenceCheckContextAllowNone::new(self.counts.mode_anticheat_counts)
 			.check(&self.counts.mode_start_counts);
 
 		// there must be exactly one start zone for every mode in the map.
@@ -352,6 +390,7 @@ impl<'a> ModelInfo<'a>{
 			mapstart,
 			mode_start_counts,
 			mode_finish_counts,
+			mode_anticheat_counts,
 			spawn1,
 			spawn_counts,
 			wormhole_out_counts,
@@ -371,6 +410,7 @@ impl<'a> MapCheck<'a>{
 				mapstart:Ok(()),
 				mode_start_counts:DuplicateCheck(Ok(())),
 				mode_finish_counts:SetDifferenceCheck(Ok(())),
+				mode_anticheat_counts:SetDifferenceCheck(Ok(())),
 				spawn1:Ok(()),
 				spawn_counts:DuplicateCheck(Ok(())),
 				wormhole_out_counts:DuplicateCheck(Ok(())),
@@ -443,7 +483,8 @@ impl<'a> std::fmt::Display for MapCheck<'a>{
 			if !context.extra.is_empty(){
 				write!(f,"Extra finish zones with no matching start zone: ")?;
 				comma_separated(f,context.extra.iter(),|f,(mode_id,_count)|
-					mode_id.write_finish_zone(f))?;
+					mode_id.write_finish_zone(f)
+				)?;
 				writeln!(f,"")?;
 			}
 			// perhaps there are missing end zones (context.missing)
@@ -455,6 +496,16 @@ impl<'a> std::fmt::Display for MapCheck<'a>{
 				writeln!(f,"")?;
 			}
 		}
+		if let SetDifferenceCheck(Err(context))=&self.mode_anticheat_counts{
+			// perhaps there are extra end zones (context.extra)
+			if !context.extra.is_empty(){
+				write!(f,"Extra anticheat zones with no matching start zone: ")?;
+				comma_separated(f,context.extra.iter(),|f,(mode_id,_count)|
+					mode_id.write_anticheat_zone(f)
+				)?;
+				writeln!(f,"")?;
+			}
+		}
 		if let Err(())=&self.spawn1{
 			writeln!(f,"Model has no Spawn1")?;
 		}