diff --git a/lib/rbx_loader/src/loader.rs b/lib/rbx_loader/src/loader.rs
index 099a080..eb804ce 100644
--- a/lib/rbx_loader/src/loader.rs
+++ b/lib/rbx_loader/src/loader.rs
@@ -5,6 +5,13 @@ use strafesnet_deferred_loader::{loader::Loader,texture::Texture};
 
 use crate::data::RobloxMeshBytes;
 
+fn read_entire_file(path:impl AsRef<std::path::Path>)->Result<Vec<u8>,std::io::Error>{
+	let mut file=std::fs::File::open(path)?;
+	let mut data=Vec::new();
+	file.read_to_end(&mut data)?;
+	Ok(data)
+}
+
 #[allow(dead_code)]
 #[derive(Debug)]
 pub enum TextureError{
@@ -41,9 +48,7 @@ impl<'a> Loader for TextureLoader<'a>{
 	fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{
 		let RobloxAssetId(asset_id)=index.parse()?;
 		let file_name=format!("textures/{}.dds",asset_id);
-		let mut file=std::fs::File::open(file_name)?;
-		let mut data=Vec::new();
-		file.read_to_end(&mut data)?;
+		let data=read_entire_file(file_name)?;
 		Ok(Texture::ImageDDS(data))
 	}
 }
@@ -53,7 +58,8 @@ impl<'a> Loader for TextureLoader<'a>{
 pub enum MeshError{
 	Io(std::io::Error),
 	RobloxAssetIdParse(RobloxAssetIdParseErr),
-	Mesh(crate::mesh::Error)
+	Mesh(crate::mesh::Error),
+	Union(crate::union::Error),
 
 }
 impl std::fmt::Display for MeshError{
@@ -77,6 +83,43 @@ impl From<crate::mesh::Error> for MeshError{
 		Self::Mesh(value)
 	}
 }
+impl From<crate::union::Error> for MeshError{
+	fn from(value:crate::union::Error)->Self{
+		Self::Union(value)
+	}
+}
+
+#[derive(Hash,Eq,PartialEq)]
+pub enum MeshType<'a>{
+	FileMesh,
+	Union{
+		mesh_data:&'a [u8],
+		physics_data:&'a [u8],
+	},
+}
+#[derive(Hash,Eq,PartialEq)]
+pub struct MeshIndex<'a>{
+	mesh_type:MeshType<'a>,
+	content:&'a str,
+}
+impl MeshIndex<'_>{
+	pub fn file_mesh(content:&str)->MeshIndex{
+		MeshIndex{
+			mesh_type:MeshType::FileMesh,
+			content,
+		}
+	}
+	pub fn union<'a>(
+		content:&'a str,
+		mesh_data:&'a [u8],
+		physics_data:&'a [u8],
+	)->MeshIndex<'a>{
+		MeshIndex{
+			mesh_type:MeshType::Union{mesh_data,physics_data},
+			content,
+		}
+	}
+}
 
 pub struct MeshLoader<'a>(std::marker::PhantomData<&'a ()>);
 impl MeshLoader<'_>{
@@ -86,17 +129,18 @@ impl MeshLoader<'_>{
 }
 impl<'a> Loader for MeshLoader<'a>{
 	type Error=MeshError;
-	type Index=&'a str;
+	type Index=MeshIndex<'a>;
 	type Resource=Mesh;
 	fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{
-		let RobloxAssetId(asset_id)=index.parse()?;
+		let RobloxAssetId(asset_id)=index.content.parse()?;
 		let file_name=format!("meshes/{}",asset_id);
-		let mut file=std::fs::File::open(file_name)?;
 		// reading the entire file is way faster than
 		// round tripping to disk every read from the parser
-		let mut data=Vec::new();
-		file.read_to_end(&mut data)?;
-		let mesh=crate::mesh::convert(RobloxMeshBytes::new(data))?;
+		let data=read_entire_file(file_name)?;
+		let mesh=match index.mesh_type{
+			MeshType::FileMesh=>crate::mesh::convert(RobloxMeshBytes::new(data))?,
+			MeshType::Union{physics_data,mesh_data}=>crate::union::convert(physics_data,mesh_data)?,
+		};
 		Ok(mesh)
 	}
 }
diff --git a/lib/rbx_loader/src/rbx.rs b/lib/rbx_loader/src/rbx.rs
index 37e3881..0b69776 100644
--- a/lib/rbx_loader/src/rbx.rs
+++ b/lib/rbx_loader/src/rbx.rs
@@ -1,4 +1,5 @@
 use std::collections::HashMap;
+use crate::loader::MeshIndex;
 use crate::primitives;
 use strafesnet_common::map;
 use strafesnet_common::model;
@@ -487,31 +488,24 @@ enum Shape{
 	MeshPart,
 	PhysicsData,
 }
-enum MeshAvailability<'a>{
+enum MeshAvailability{
 	Immediate,
 	DeferredMesh(RenderConfigId),
-	DeferredUnion(RobloxPartDescription,UnionDeferredAttributes<'a>),
+	DeferredUnion(RobloxPartDescription),
 }
-struct DeferredModelDeferredAttributes{
+struct DeferredModelDeferredAttributes<'a>{
 	render:RenderConfigId,
-	model:ModelDeferredAttributes,
+	model:ModelDeferredAttributes<'a>,
 }
-struct ModelDeferredAttributes{
+struct ModelDeferredAttributes<'a>{
 	mesh:model::MeshId,
-	deferred_attributes:GetAttributesArgs,
+	deferred_attributes:GetAttributesArgs<'a>,
 	color:model::Color4,//transparency is in here
 	transform:Planar64Affine3,
 }
 struct DeferredUnionDeferredAttributes<'a>{
 	render:RobloxPartDescription,
-	model:ModelDeferredAttributes,
-	union:UnionDeferredAttributes<'a>,
-}
-#[derive(Hash)]
-struct UnionDeferredAttributes<'a>{
-	asset_id:Option<&'a str>,
-	mesh_data:Option<&'a [u8]>,
-	physics_data:Option<&'a [u8]>,
+	model:ModelDeferredAttributes<'a>,
 }
 struct ModelOwnedAttributes{
 	mesh:model::MeshId,
@@ -519,17 +513,18 @@ struct ModelOwnedAttributes{
 	color:model::Color4,//transparency is in here
 	transform:Planar64Affine3,
 }
-struct GetAttributesArgs{
-	name:Box<str>,
+struct GetAttributesArgs<'a>{
+	name:&'a str,
 	can_collide:bool,
 	velocity:Planar64Vec3,
 }
 pub fn convert<'a>(
 	dom:&'a rbx_dom_weak::WeakDom,
 	render_config_deferred_loader:&mut RenderConfigDeferredLoader<&'a str>,
-	mesh_deferred_loader:&mut MeshDeferredLoader<&'a str>,
+	mesh_deferred_loader:&mut MeshDeferredLoader<MeshIndex<'a>>,
 )->PartialMap1<'a>{
 	let mut deferred_models_deferred_attributes=Vec::new();
+	let mut deferred_unions_deferred_attributes=Vec::new();
 	let mut primitive_models_deferred_attributes=Vec::new();
 	let mut primitive_meshes=Vec::new();
 	let mut mesh_id_from_description=HashMap::new();
@@ -600,7 +595,7 @@ pub fn convert<'a>(
 				let (availability,mesh_id)=match shape{
 					Shape::Primitive(primitive_shape)=>{
 				//TODO: TAB TAB
-				let part_texture_description=get_texture_description(&mut temp_objects,&mut render_config_deferred_loader,dom,object,size);
+				let part_texture_description=get_texture_description(&mut temp_objects,render_config_deferred_loader,dom,object,size);
 				//obscure rust syntax "slice pattern"
 				let [
 					f0,//Cube::Right
@@ -714,7 +709,7 @@ pub fn convert<'a>(
 					){
 						(
 							MeshAvailability::DeferredMesh(render_config_deferred_loader.acquire_render_config_id(Some(texture_asset_id.as_ref()))),
-							mesh_deferred_loader.acquire_mesh_id(mesh_asset_id.as_ref()),
+							mesh_deferred_loader.acquire_mesh_id(MeshIndex::file_mesh(mesh_asset_id.as_ref())),
 						)
 					}else{
 						panic!("Mesh has no Mesh or Texture");
@@ -723,34 +718,22 @@ pub fn convert<'a>(
 						//The union mesh is sized already
 						model_transform=planar64_affine3_from_roblox(cf,&rbx_dom_weak::types::Vector3{x:2.0,y:2.0,z:2.0});
 
-						let mut asset_id=None;
-						let mut mesh_data=None;
-						let mut physics_data=None;
-						if let Some(rbx_dom_weak::types::Variant::Content(content))=object.properties.get("AssetId"){
-							let value:&str=content.as_ref();
-							if !value.is_empty(){
-								asset_id=Some(value);
-							}
+						let mut content="";
+						let mut mesh_data:&[u8]=&[];
+						let mut physics_data:&[u8]=&[];
+						if let Some(rbx_dom_weak::types::Variant::Content(asset_id))=object.properties.get("AssetId"){
+							content=asset_id.as_ref();
 						}
 						if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=object.properties.get("MeshData"){
-							let value:&[u8]=data.as_ref();
-							if !value.is_empty(){
-								mesh_data=Some(value);
-							}
+							mesh_data=data.as_ref();
 						}
 						if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=object.properties.get("PhysicsData"){
-							let value:&[u8]=data.as_ref();
-							if !value.is_empty(){
-								physics_data=Some(value);
-							}
+							physics_data=data.as_ref();
 						}
-						let part_texture_description=get_texture_description(&mut temp_objects,&mut render_config_deferred_loader,dom,object,size);
-						let union_deferred_attributes=UnionDeferredAttributes{
-							asset_id,
-							mesh_data,
-							physics_data,
-						};
-						(MeshAvailability::DeferredUnion(part_texture_description,union_deferred_attributes),mesh_id)
+						let part_texture_description=get_texture_description(&mut temp_objects,render_config_deferred_loader,dom,object,size);
+						let mesh_index=MeshIndex::union(content,mesh_data,physics_data);
+						let mesh_id=mesh_deferred_loader.acquire_mesh_id(mesh_index);
+						(MeshAvailability::DeferredUnion(part_texture_description),mesh_id)
 					},
 				};
 				let model_deferred_attributes=ModelDeferredAttributes{
@@ -758,7 +741,7 @@ pub fn convert<'a>(
 					transform:model_transform,
 					color:glam::vec4(color3.r as f32/255f32, color3.g as f32/255f32, color3.b as f32/255f32, 1.0-*transparency),
 					deferred_attributes:GetAttributesArgs{
-						name:object.name.as_str().into(),
+						name:object.name.as_str(),
 						can_collide:*can_collide,
 						velocity:vec3::try_from_f32_array([velocity.x,velocity.y,velocity.z]).unwrap(),
 					},
@@ -769,10 +752,9 @@ pub fn convert<'a>(
 						render,
 						model:model_deferred_attributes
 					}),
-					MeshAvailability::DeferredUnion(part_texture_description,union_deferred_attributes)=>deferred_unions_deferred_attributes.push(DeferredUnionDeferredAttributes{
+					MeshAvailability::DeferredUnion(part_texture_description)=>deferred_unions_deferred_attributes.push(DeferredUnionDeferredAttributes{
 						render:part_texture_description,
 						model:model_deferred_attributes,
-						union:union_deferred_attributes,
 					}),
 				}
 			}
@@ -782,6 +764,7 @@ pub fn convert<'a>(
 		primitive_meshes,
 		primitive_models_deferred_attributes,
 		deferred_models_deferred_attributes,
+		deferred_unions_deferred_attributes,
 	}
 }
 struct MeshWithAabb{
@@ -790,11 +773,11 @@ struct MeshWithAabb{
 }
 pub struct PartialMap1<'a>{
 	primitive_meshes:Vec<model::Mesh>,
-	primitive_models_deferred_attributes:Vec<ModelDeferredAttributes>,
-	deferred_models_deferred_attributes:Vec<DeferredModelDeferredAttributes>,
-	deferred_union_deferred_attributes:Vec<DeferredModelDeferredAttributes>,
+	primitive_models_deferred_attributes:Vec<ModelDeferredAttributes<'a>>,
+	deferred_models_deferred_attributes:Vec<DeferredModelDeferredAttributes<'a>>,
+	deferred_unions_deferred_attributes:Vec<DeferredUnionDeferredAttributes<'a>>,
 }
-impl PartialMap1{
+impl PartialMap1<'_>{
 	pub fn add_meshpart_meshes_and_calculate_attributes(
 		mut self,
 		meshpart_meshes:Meshes,
diff --git a/lib/rbx_loader/src/union.rs b/lib/rbx_loader/src/union.rs
index 8124275..5ba7f13 100644
--- a/lib/rbx_loader/src/union.rs
+++ b/lib/rbx_loader/src/union.rs
@@ -33,7 +33,7 @@ pub fn convert(roblox_physics_data:&[u8],roblox_mesh_data:&[u8])->Result<model::
 		std::io::Cursor::new(roblox_mesh_data)
 	).map_err(Error::RobloxMeshData)?;
 	let graphics_mesh=match mesh_data{
-		rbx_mesh::mesh_data::CSGPHS::CSGK(csgk)=>return Err(Error::NotSupposedToHappen),
+		rbx_mesh::mesh_data::CSGPHS::CSGK(_)=>return Err(Error::NotSupposedToHappen),
 		rbx_mesh::mesh_data::CSGPHS::CSGPHS2(mesh_data2)=>mesh_data2.mesh,
 		rbx_mesh::mesh_data::CSGPHS::CSGPHS4(mesh_data4)=>mesh_data4.mesh,
 	};
@@ -47,8 +47,7 @@ pub fn convert(roblox_physics_data:&[u8],roblox_mesh_data:&[u8])->Result<model::
 		// have not seen this format in practice
 		|rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::Block)
 		=>return Err(Error::NotSupposedToHappen),
-		rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::Meshes3(meshes))
-		|rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::Meshes5(meshes))
+		rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::Meshes(meshes))
 		=>meshes.meshes,
 		rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::PhysicsInfoMesh(pim))
 		=>vec![pim.mesh],