validation: check for unanchored parts #230

Merged
Quaternions merged 1 commits from anchored into staging 2025-07-08 00:07:28 +00:00

View File

@@ -223,6 +223,7 @@ pub struct ModelInfo<'a>{
model_name:&'a str,
map_info:MapInfo<'a>,
counts:Counts<'a>,
unanchored_parts:Vec<&'a Instance>,
}
pub fn get_model_info<'a>(dom:&'a rbx_dom_weak::WeakDom,model_instance:&'a rbx_dom_weak::Instance)->ModelInfo<'a>{
@@ -232,6 +233,10 @@ pub fn get_model_info<'a>(dom:&'a rbx_dom_weak::WeakDom,model_instance:&'a rbx_d
// count objects (default count is 0)
let mut counts=Counts::default();
// locate unanchored parts
let mut unanchored_parts=Vec::new();
let anchored_ustr=rbx_dom_weak::ustr("Anchored");
let db=rbx_reflection_database::get();
let base_part=&db.classes["BasePart"];
let base_parts=dom.descendants_of(model_instance.referent()).filter(|&instance|
@@ -259,6 +264,10 @@ pub fn get_model_info<'a>(dom:&'a rbx_dom_weak::WeakDom,model_instance:&'a rbx_d
Ok(WormholeElement{behaviour:WormholeBehaviour::Out,wormhole_id})=>*counts.wormhole_out_counts.entry(wormhole_id).or_insert(0)+=1,
Err(_)=>(),
}
// Unanchored parts
if let Some(rbx_dom_weak::types::Variant::Bool(false))=instance.properties.get(&anchored_ustr){
unanchored_parts.push(instance);
}
}
ModelInfo{
@@ -266,6 +275,7 @@ pub fn get_model_info<'a>(dom:&'a rbx_dom_weak::WeakDom,model_instance:&'a rbx_d
model_name:model_instance.name.as_str(),
map_info,
counts,
unanchored_parts,
}
}
@@ -447,6 +457,9 @@ struct MapCheck<'a>{
// No duplicate WormholeOut# (duplicate WormholeIn# ok)
// No dangling WormholeOut#
wormhole_out_counts:DuplicateCheck<WormholeID,u64>,
// === GENERAL CHECKS ===
unanchored_parts:Result<(),Vec<&'a Instance>>,
}
impl<'a> ModelInfo<'a>{
@@ -520,6 +533,13 @@ impl<'a> ModelInfo<'a>{
// There must be exactly one of any perticular wormhole out id in the map.
let wormhole_out_counts=DuplicateCheckContext(self.counts.wormhole_out_counts).check(|&c|1<c);
// There must not be any unanchored parts
let unanchored_parts=if self.unanchored_parts.is_empty(){
Ok(())
}else{
Err(self.unanchored_parts)
};
MapCheck{
model_class,
model_name,
@@ -535,6 +555,7 @@ impl<'a> ModelInfo<'a>{
spawn_counts,
wormhole_in_counts,
wormhole_out_counts,
unanchored_parts,
}
}
}
@@ -557,6 +578,7 @@ impl MapCheck<'_>{
spawn_counts:DuplicateCheck(Ok(())),
wormhole_in_counts:SetDifferenceCheck(Ok(())),
wormhole_out_counts:DuplicateCheck(Ok(())),
unanchored_parts:Ok(()),
}=>{
Ok(MapInfoOwned{
display_name:display_name.to_owned(),
@@ -780,6 +802,17 @@ impl MapCheck<'_>{
summary_format!("DuplicateWormholeOut","Duplicate WormholeOut: {context}")
}
};
let unanchored_parts=match &self.unanchored_parts{
Ok(())=>passed!("UnanchoredParts"),
Err(unanchored_parts)=>{
let count=unanchored_parts.len();
let plural=if count==1{"part"}else{"parts"};
let context=Separated::new(", ",||unanchored_parts.iter().map(|&instance|
instance.name.as_str()
).take(20));
summary_format!("UnanchoredParts","{count} unanchored {plural}: {context}")
}
};
Ok(MapCheckList{checks:Box::new([
model_class,
model_name,
@@ -797,13 +830,14 @@ impl MapCheck<'_>{
extra_wormhole_in,
missing_wormhole_in,
duplicate_wormhole_out,
unanchored_parts,
])})
}
}
#[derive(serde::Serialize)]
pub struct MapCheckList{
pub checks:Box<[Check;16]>,
pub checks:Box<[Check;17]>,
}
pub struct CheckListAndVersion{