diff --git a/validation/src/check.rs b/validation/src/check.rs
index da861f3..41fb341 100644
--- a/validation/src/check.rs
+++ b/validation/src/check.rs
@@ -12,6 +12,7 @@ pub enum Error{
 	CreatorTypeMustBeUser,
 	Download(crate::download::Error),
 	ModelFileDecode(ReadDomError),
+	GetRootInstance(GetRootInstanceError),
 }
 impl std::fmt::Display for Error{
 	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
@@ -155,38 +156,29 @@ pub fn get_model_info(dom:&rbx_dom_weak::WeakDom)->Result<ModelInfo,GetRootInsta
 	})
 }
 
-
-
-pub enum Check<Context>{
-	Pass,
-	Fail(Context),
-}
-
 // check if an observed string matches and expected string
-pub struct StringCheck<'a>(Check<StringCheckContext<'a>>);
+pub struct StringCheck<'a,T>(Result<T,StringCheckContext<'a>>);
 pub struct StringCheckContext<'a>{
 	observed:&'a str,
 	expected:Cow<'a,str>,
 }
 impl<'a> StringCheckContext<'a>{
-	fn check(self)->StringCheck<'a>{
+	fn check<T>(self,value:T)->StringCheck<'a,T>{
 		if self.observed==self.expected{
-			StringCheck(Check::Pass)
+			StringCheck(Ok(value))
 		}else{
-			StringCheck(Check::Fail(self))
+			StringCheck(Err(self))
 		}
 	}
 }
 
 // check if a string is empty
-pub enum StringEmptyCheck<Context>{
-	Empty,
-	Passed(Context),
-}
+pub struct StringEmpty;
+pub struct StringEmptyCheck<Context>(Result<Context,StringEmpty>);
 
 // check for duplicate objects
 pub struct DuplicateCheckContext<ID>(HashMap<ID,u64>);
-pub struct DuplicateCheck<ID>(Check<DuplicateCheckContext<ID>>);
+pub struct DuplicateCheck<ID>(Result<(),DuplicateCheckContext<ID>>);
 impl<ID> DuplicateCheckContext<ID>{
 	fn check(self)->DuplicateCheck<ID>{
 		let Self(mut set)=self;
@@ -194,9 +186,9 @@ impl<ID> DuplicateCheckContext<ID>{
 		set.retain(|_,&mut c|c!=1);
 		// if any entries remain, they are incorrect
 		if set.is_empty(){
-			DuplicateCheck(Check::Pass)
+			DuplicateCheck(Ok(()))
 		}else{
-			DuplicateCheck(Check::Fail(Self(set)))
+			DuplicateCheck(Err(Self(set)))
 		}
 	}
 }
@@ -206,7 +198,7 @@ pub struct AtLeastOneMatchingAndNoExtraCheckContext<ID>{
 	set:HashMap<ID,u64>,
 	missing:HashSet<ID>,
 }
-pub struct AtLeastOneMatchingAndNoExtraCheck<ID>(Check<AtLeastOneMatchingAndNoExtraCheckContext<ID>>);
+pub struct AtLeastOneMatchingAndNoExtraCheck<ID>(Result<(),AtLeastOneMatchingAndNoExtraCheckContext<ID>>);
 impl<ID> AtLeastOneMatchingAndNoExtraCheckContext<ID>{
 	fn new(set:HashMap<ID,u64>)->Self{
 		Self{
@@ -227,9 +219,9 @@ impl<ID:Copy+Eq+std::hash::Hash> AtLeastOneMatchingAndNoExtraCheckContext<ID>{
 		}
 		// if any entries remain, they are incorrect
 		if set.is_empty()&&missing.is_empty(){
-			AtLeastOneMatchingAndNoExtraCheck(Check::Pass)
+			AtLeastOneMatchingAndNoExtraCheck(Ok(()))
 		}else{
-			AtLeastOneMatchingAndNoExtraCheck(Check::Fail(Self{set,missing}))
+			AtLeastOneMatchingAndNoExtraCheck(Err(Self{set,missing}))
 		}
 	}
 }
@@ -242,15 +234,15 @@ pub struct MapInfoOwned{
 
 // crazy!
 pub struct MapCheck<'a>{
-	model_class:StringCheck<'a>,
-	model_name:StringCheck<'a>,
-	display_name:Result<StringEmptyCheck<StringCheck<'a>>,StringValueError>,
-	creator:Result<StringEmptyCheck<()>,StringValueError>,
+	model_class:StringCheck<'a,()>,
+	model_name:StringCheck<'a,()>,
+	display_name:Result<StringEmptyCheck<StringCheck<'a,&'a str>>,StringValueError>,
+	creator:Result<StringEmptyCheck<&'a str>,StringValueError>,
 	game_id:Result<GameID,ParseGameIDError>,
-	mapstart:Check<()>,
+	mapstart:Result<(),()>,
 	mode_start_counts:DuplicateCheck<ModeID>,
 	mode_finish_counts:AtLeastOneMatchingAndNoExtraCheck<ModeID>,
-	spawn1:Check<()>,
+	spawn1:Result<(),()>,
 	spawn_counts:DuplicateCheck<SpawnID>,
 	wormhole_out_counts:DuplicateCheck<WormholeOutID>,
 }
@@ -260,32 +252,32 @@ impl<'a> ModelInfo<'a>{
 		let model_class=StringCheckContext{
 			observed:self.model_class,
 			expected:Cow::Borrowed("Model"),
-		}.check();
+		}.check(());
 
 		let model_name=StringCheckContext{
 			observed:self.model_name,
 			expected:Cow::Owned(self.model_name.to_snake_case()),
-		}.check();
+		}.check(());
 
 		// check display name
 		let display_name=self.map_info.display_name.map(|display_name|{
 			if display_name.is_empty(){
-				StringEmptyCheck::Empty
+				StringEmptyCheck(Err(StringEmpty))
 			}else{
 				let display_name=StringCheckContext{
 					observed:display_name,
 					expected:Cow::Owned(display_name.to_title_case()),
-				}.check();
-				StringEmptyCheck::Passed(display_name)
+				}.check(display_name);
+				StringEmptyCheck(Ok(display_name))
 			}
 		});
 
 		// check Creator
 		let creator=self.map_info.creator.map(|creator|{
 			if creator.is_empty(){
-				StringEmptyCheck::Empty
+				StringEmptyCheck(Err(StringEmpty))
 			}else{
-				StringEmptyCheck::Passed(())
+				StringEmptyCheck(Ok(creator))
 			}
 		});
 
@@ -294,16 +286,16 @@ impl<'a> ModelInfo<'a>{
 
 		// MapStart must exist
 		let mapstart=if self.counts.mode_start_counts.get(&ModeID::MAIN).is_some(){
-			Check::Pass
+			Ok(())
 		}else{
-			Check::Fail(())
+			Err(())
 		};
 
 		// Spawn1 must exist
 		let spawn1=if self.counts.spawn_counts.get(&SpawnID::FIRST).is_some(){
-			Check::Pass
+			Ok(())
 		}else{
-			Check::Fail(())
+			Err(())
 		};
 
 		// check that at least one end zone exists for each start zone.
@@ -338,17 +330,17 @@ impl<'a> MapCheck<'a>{
 	fn pass(self)->Result<MapInfoOwned,Self>{
 		match self{
 			MapCheck{
-				model_class:StringCheck(Check::Pass),
-				model_name:StringCheck(Check::Pass),
-				display_name:Ok(StringEmptyCheck::Passed(StringCheck(Check::Pass))),
-				creator:Ok(StringEmptyCheck::Passed(())),
+				model_class:StringCheck(Ok(())),
+				model_name:StringCheck(Ok(())),
+				display_name:Ok(StringEmptyCheck(Ok(StringCheck(Ok(display_name))))),
+				creator:Ok(StringEmptyCheck(Ok(creator))),
 				game_id:Ok(game_id),
-				mapstart,
-				mode_start_counts,
-				mode_finish_counts,
-				spawn1,
-				spawn_counts,
-				wormhole_out_counts,
+				mapstart:Ok(()),
+				mode_start_counts:DuplicateCheck(Ok(())),
+				mode_finish_counts:AtLeastOneMatchingAndNoExtraCheck(Ok(())),
+				spawn1:Ok(()),
+				spawn_counts:DuplicateCheck(Ok(())),
+				wormhole_out_counts:DuplicateCheck(Ok(())),
 			}=>{
 				Ok(MapInfoOwned{
 					display_name:display_name.to_owned(),
@@ -362,7 +354,7 @@ impl<'a> MapCheck<'a>{
 }
 
 pub struct CheckReportAndVersion{
-	pub status:CheckStatus,
+	pub status:Result<MapInfoOwned,String>,
 	pub version:u64,
 }
 
@@ -389,7 +381,14 @@ impl crate::message_handler::MessageHandler{
 		// decode dom (slow!)
 		let dom=maybe_gzip.read_with(read_dom,read_dom).map_err(Error::ModelFileDecode)?;
 
-		let status=check(&dom);
+		// extract information from the model
+		let model_info=get_model_info(&dom).map_err(Error::GetRootInstance)?;
+
+		// convert the model information into a structured report
+		let map_check=model_info.check();
+
+		// check the report, generate an error message if it fails the check
+		let status=map_check.pass().map_err(|e|e.to_string());
 
 		Ok(CheckReportAndVersion{status,version})
 	}