diff --git a/lib/bsp_loader/src/brush.rs b/lib/bsp_loader/src/brush.rs
new file mode 100644
index 0000000..f0797a2
--- /dev/null
+++ b/lib/bsp_loader/src/brush.rs
@@ -0,0 +1,342 @@
+use strafesnet_common::integer::Planar64;
+use strafesnet_common::{model,integer};
+use strafesnet_common::integer::{vec3::Vector3,Fixed,Ratio};
+
+use crate::{valve_transform_normal,valve_transform_dist};
+
+#[derive(Hash,Eq,PartialEq)]
+struct Face{
+	normal:integer::Planar64Vec3,
+	dot:integer::Planar64,
+}
+
+#[derive(Debug)]
+struct Faces{
+	faces:Vec<Vec<integer::Planar64Vec3>>,
+}
+
+fn solve3(c0:&Face,c1:&Face,c2:&Face)->Option<Ratio<Vector3<Fixed<3,96>>,Fixed<3,96>>>{
+	let n0_n1=c0.normal.cross(c1.normal);
+	let det=c2.normal.dot(n0_n1);
+	if det.abs().is_zero(){
+		return None;
+	}
+	Some((
+		c1.normal.cross(c2.normal)*c0.dot
+		+c2.normal.cross(c0.normal)*c1.dot
+		+c0.normal.cross(c1.normal)*c2.dot
+	)/det)
+}
+
+#[derive(Debug)]
+pub enum PlanesToFacesError{
+	InitFace1,
+	InitFace2,
+	InitIntersection,
+	FindNewIntersection,
+	EmptyFaces,
+	InfiniteLoop1,
+	InfiniteLoop2,
+}
+impl std::fmt::Display for PlanesToFacesError{
+	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
+		write!(f,"{self:?}")
+	}
+}
+impl core::error::Error for PlanesToFacesError{}
+
+fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,PlanesToFacesError>{
+	let mut faces=Vec::new();
+	// for each face, determine one edge at a time until you complete the face
+	'face: for face0 in &face_list{
+		// 1. find first edge
+		// 2. follow edges around face
+
+		// === finding first edge ===
+		// 1. pick the most perpendicular set of 3 faces
+		// 2. check if any faces occlude the intersection
+		// 3. use this test to replace left and right alternating until they are not occluded
+
+		// find the most perpendicular face to face0
+		let mut face1=face_list.iter().min_by_key(|&p|{
+			face0.normal.dot(p.normal).abs()
+		}).ok_or(PlanesToFacesError::InitFace1)?;
+
+		// direction of edge formed by face0 x face1
+		let edge_dir=face0.normal.cross(face1.normal);
+
+		// find the most perpendicular face to both face0 and face1
+		let mut face2=face_list.iter().max_by_key(|&p|{
+			// find the best *oriented* face (no .abs())
+			edge_dir.dot(p.normal)
+		}).ok_or(PlanesToFacesError::InitFace2)?;
+
+		let mut detect_loop=200u8;
+
+		let mut intersection=solve3(face0,face1,face2).ok_or(PlanesToFacesError::InitIntersection)?;
+
+		// repeatedly update face1, face2 until all faces form part of the convex solid
+		'find: loop{
+			detect_loop=detect_loop.checked_sub(1).ok_or(PlanesToFacesError::InfiniteLoop1)?;
+			// test if any *other* faces occlude the intersection
+			for new_face in &face_list{
+				// new face occludes intersection point
+				if (new_face.dot.fix_2()/Planar64::ONE).lt_ratio(new_face.normal.dot(intersection.num)/intersection.den){
+					// replace one of the faces with the new face
+					// dont' try to replace face0 because we are exploring that face in particular
+					if let Some(new_intersection)=solve3(face0,new_face,face2){
+						// face1 does not occlude (or intersect) the new intersection
+						if (face1.dot.fix_2()/Planar64::ONE).gt_ratio(face1.normal.dot(new_intersection.num)/new_intersection.den){
+							face1=new_face;
+							intersection=new_intersection;
+							continue 'find;
+						}
+					}
+					if let Some(new_intersection)=solve3(face0,face1,new_face){
+						// face2 does not occlude (or intersect) the new intersection
+						if (face2.dot.fix_2()/Planar64::ONE).gt_ratio(face2.normal.dot(new_intersection.num)/new_intersection.den){
+							face2=new_face;
+							intersection=new_intersection;
+							continue 'find;
+						}
+					}
+				}
+			}
+
+			// we have found a set of faces for which the intersection is on the convex solid
+			break 'find;
+		}
+
+		// check if face0 must go, meaning it is a degenerate face and does not contribute anything to the convex solid
+		for new_face in &face_list{
+			if core::ptr::eq(face0,new_face){
+				continue;
+			}
+			if core::ptr::eq(face1,new_face){
+				continue;
+			}
+			if core::ptr::eq(face2,new_face){
+				continue;
+			}
+			// new_face occludes intersection meaning intersection is not on convex solid and face0 is degenrate
+			if (new_face.dot.fix_2()/Planar64::ONE).lt_ratio(new_face.normal.dot(intersection.num)/intersection.den){
+				// abort! reject face0 entirely
+				continue 'face;
+			}
+		}
+
+		// === follow edges around face ===
+		// Note that we chose face2 such that the 3 faces create a particular winding order.
+		// If we choose a consistent face to follow (face1, face2) it will always wind with a consistent chirality
+
+		let mut detect_loop=200u8;
+
+		// keep looping until we meet this face again
+		let face1=face1;
+		let mut face=Vec::new();
+		loop{
+			// push point onto vertices
+			// problem: this may push a vertex that does not fit in the fixed point range and is thus meaningless
+			face.push(intersection.divide().fix_1());
+
+			// we looped back around to face1, we're done!
+			if core::ptr::eq(face1,face2){
+				break;
+			}
+
+			// the measure
+			let edge_dir=face0.normal.cross(face2.normal);
+
+			// the dot product to beat
+			let d_intersection=edge_dir.dot(intersection.num)/intersection.den;
+
+			// find the next face moving clockwise around face0
+			let (new_face,new_intersection,_)=face_list.iter().filter_map(|new_face|{
+				// ignore faces that are part of the current edge
+				if core::ptr::eq(face0,new_face)
+				  |core::ptr::eq(face2,new_face){
+					return None;
+				}
+				let new_intersection=solve3(face0,face2,new_face)?;
+
+				// the d value must be larger
+				let d_new_intersection=edge_dir.dot(new_intersection.num)/new_intersection.den;
+				if d_new_intersection.le_ratio(d_intersection){
+					return None;
+				}
+
+				Some((new_face,new_intersection,d_new_intersection))
+			}).min_by_key(|&(_,_,d)|d).ok_or(PlanesToFacesError::FindNewIntersection)?;
+
+			face2=new_face;
+			intersection=new_intersection;
+
+			detect_loop=detect_loop.checked_sub(1).ok_or(PlanesToFacesError::InfiniteLoop2)?;
+		}
+
+		faces.push(face);
+	}
+
+	if faces.is_empty(){
+		Err(PlanesToFacesError::EmptyFaces)
+	}else{
+		Ok(Faces{
+			faces,
+		})
+	}
+}
+
+#[allow(dead_code)]
+#[derive(Debug)]
+pub enum BrushToMeshError{
+	SliceBrushSides,
+	MissingPlane,
+	InvalidFaceCount{
+		count:usize,
+	},
+	InvalidPlanes(PlanesToFacesError),
+}
+impl std::fmt::Display for BrushToMeshError{
+	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
+		write!(f,"{self:?}")
+	}
+}
+impl core::error::Error for BrushToMeshError{}
+
+pub fn faces_to_mesh(faces:Vec<Vec<integer::Planar64Vec3>>)->model::Mesh{
+	// generate the mesh
+	let mut mb=model::MeshBuilder::new();
+	let color=mb.acquire_color_id(glam::Vec4::ONE);
+	let tex=mb.acquire_tex_id(glam::Vec2::ZERO);
+	// normals are ignored by physics
+	let normal=mb.acquire_normal_id(integer::vec3::ZERO);
+
+	let polygon_list=faces.into_iter().map(|face|{
+		face.into_iter().map(|pos|{
+			let pos=mb.acquire_pos_id(pos);
+			mb.acquire_vertex_id(model::IndexedVertex{
+				pos,
+				tex,
+				normal,
+				color,
+			})
+		}).collect()
+	}).collect();
+
+	let polygon_groups=vec![model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list))];
+	let physics_groups=vec![model::IndexedPhysicsGroup{
+		groups:vec![model::PolygonGroupId::new(0)],
+	}];
+	let graphics_groups=vec![];
+
+	mb.build(polygon_groups,graphics_groups,physics_groups)
+}
+
+pub fn brush_to_mesh(bsp:&vbsp::Bsp,brush:&vbsp::Brush)->Result<model::Mesh,BrushToMeshError>{
+	let brush_start_idx=brush.brush_side as usize;
+	let sides_range=brush_start_idx..brush_start_idx+brush.num_brush_sides as usize;
+	let sides=bsp.brush_sides.get(sides_range).ok_or(BrushToMeshError::SliceBrushSides)?;
+	let face_list=sides.iter().map(|side|{
+		// The so-called tumor brushes have TRIGGER bit set
+		// but also ignore visleaf hint and skip sides
+		const TUMOR:vbsp::TextureFlags=vbsp::TextureFlags::HINT.union(vbsp::TextureFlags::SKIP).union(vbsp::TextureFlags::TRIGGER);
+		if let Some(texture_info)=bsp.textures_info.get(side.texture_info as usize){
+			if texture_info.flags.intersects(TUMOR){
+				return None;
+			}
+		}
+		let plane=bsp.plane(side.plane as usize)?;
+		Some(Face{
+			normal:valve_transform_normal(plane.normal.into()),
+			dot:valve_transform_dist(plane.dist.into()),
+		})
+	}).collect::<Option<std::collections::HashSet<_>>>().ok_or(BrushToMeshError::MissingPlane)?;
+
+	if face_list.len()<4{
+		return Err(BrushToMeshError::InvalidFaceCount{count:face_list.len()});
+	}
+
+	let faces=planes_to_faces(face_list).map_err(BrushToMeshError::InvalidPlanes)?;
+
+	let mesh=faces_to_mesh(faces.faces);
+
+	Ok(mesh)
+}
+
+pub fn unit_cube()->model::Mesh{
+	let face_list=[
+		Face{normal:integer::vec3::X,dot:Planar64::ONE},
+		Face{normal:integer::vec3::Y,dot:Planar64::ONE},
+		Face{normal:integer::vec3::Z,dot:Planar64::ONE},
+		Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE},
+		Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE},
+		Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE},
+	].into_iter().collect();
+	let faces=planes_to_faces(face_list).unwrap();
+	let mesh=faces_to_mesh(faces.faces);
+	mesh
+}
+
+#[cfg(test)]
+mod test{
+	use super::*;
+	#[test]
+	fn test_cube(){
+		let face_list=[
+			Face{normal:integer::vec3::X,dot:Planar64::ONE},
+			Face{normal:integer::vec3::Y,dot:Planar64::ONE},
+			Face{normal:integer::vec3::Z,dot:Planar64::ONE},
+			Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE},
+			Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE},
+			Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE},
+		].into_iter().collect();
+		let faces=planes_to_faces(face_list).unwrap();
+		assert_eq!(faces.faces.len(),6);
+		dbg!(faces);
+	}
+	#[test]
+	fn test_cube_with_degernate_face(){
+		let face_list=[
+			Face{normal:integer::vec3::X,dot:Planar64::ONE},
+			Face{normal:integer::vec3::Y,dot:Planar64::ONE},
+			Face{normal:integer::vec3::Z,dot:Planar64::ONE},
+			Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE},
+			Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE},
+			Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE},
+			Face{normal:integer::vec3::NEG_Z,dot:Planar64::EPSILON},
+		].into_iter().collect();
+		let faces=planes_to_faces(face_list).unwrap();
+		assert_eq!(faces.faces.len(),6);
+		dbg!(faces);
+	}
+	#[test]
+	fn test_cube_with_degernate_face2(){
+		let face_list=[
+			Face{normal:integer::vec3::X,dot:Planar64::ONE},
+			Face{normal:integer::vec3::Y,dot:Planar64::ONE},
+			Face{normal:integer::vec3::Z,dot:Planar64::ONE},
+			Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE},
+			Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE},
+			Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE},
+			Face{normal:integer::vec3::NEG_X+integer::vec3::NEG_Z,dot:-Planar64::EPSILON},
+		].into_iter().collect();
+		let faces=planes_to_faces(face_list).unwrap();
+		assert_eq!(faces.faces.len(),5);
+		dbg!(faces);
+	}
+	#[test]
+	fn test_cube_with_degernate_face3(){
+		let face_list=[
+			Face{normal:integer::vec3::X,dot:Planar64::ONE},
+			Face{normal:integer::vec3::Y,dot:Planar64::ONE},
+			Face{normal:integer::vec3::Z,dot:Planar64::ONE},
+			Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE},
+			Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE},
+			Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE},
+			Face{normal:integer::vec3::NEG_X+integer::vec3::NEG_Z,dot:Planar64::EPSILON},
+		].into_iter().collect();
+		let faces=planes_to_faces(face_list).unwrap();
+		assert_eq!(faces.faces.len(),7);
+		dbg!(faces);
+	}
+}
diff --git a/lib/bsp_loader/src/bsp.rs b/lib/bsp_loader/src/bsp.rs
index 6c4897e..0cbf32b 100644
--- a/lib/bsp_loader/src/bsp.rs
+++ b/lib/bsp_loader/src/bsp.rs
@@ -1,5 +1,7 @@
 use std::borrow::Cow;
 
+use vbsp_entities::css::Entity;
+
 use strafesnet_common::{map,model,integer,gameplay_attributes};
 use strafesnet_deferred_loader::deferred_loader::{MeshDeferredLoader,RenderConfigDeferredLoader};
 use strafesnet_deferred_loader::mesh::Meshes;
@@ -32,6 +34,47 @@ fn ingest_vertex(
 	})
 }
 
+fn add_brush<'a>(
+	mesh_deferred_loader:&mut MeshDeferredLoader<&'a str>,
+	world_models:&mut Vec<model::Model>,
+	prop_models:&mut Vec<model::Model>,
+	model:&'a str,
+	origin:vbsp::Vector,
+	rendercolor:vbsp::Color,
+	attributes:gameplay_attributes::CollisionAttributesId,
+){
+	let transform=integer::Planar64Affine3::from_translation(
+		valve_transform(origin.into())
+	);
+	let color=(glam::Vec3::from_array([
+		rendercolor.r as f32,
+		rendercolor.g as f32,
+		rendercolor.b as f32
+	])/255.0).extend(1.0);
+
+	match model.split_at(1){
+		// The first character of brush.model is '*'
+		("*",id_str)=>match id_str.parse(){
+			Ok(mesh_id)=>{
+				let mesh=model::MeshId::new(mesh_id);
+				world_models.push(
+					model::Model{mesh,attributes,transform,color}
+				);
+			},
+			Err(e)=>{
+				println!("Brush model int parse error: {e} model={model}");
+				return;
+			},
+		},
+		_=>{
+			let mesh=mesh_deferred_loader.acquire_mesh_id(model);
+			prop_models.push(
+				model::Model{mesh,attributes,transform,color}
+			);
+		}
+	}
+}
+
 pub fn convert<'a>(
 	bsp:&'a crate::Bsp,
 	render_config_deferred_loader:&mut RenderConfigDeferredLoader<Cow<'a,str>>,
@@ -41,16 +84,20 @@ pub fn convert<'a>(
 	//figure out real attributes later
 	let mut unique_attributes=Vec::new();
 	unique_attributes.push(gameplay_attributes::CollisionAttributes::Decoration);
-	const TEMP_TOUCH_ME_ATTRIBUTE:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(0);
+	unique_attributes.push(gameplay_attributes::CollisionAttributes::contact_default());
+	unique_attributes.push(gameplay_attributes::CollisionAttributes::intersect_default());
+	const ATTRIBUTE_DECORATION:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(0);
+	const ATTRIBUTE_CONTACT_DEFAULT:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(1);
+	const ATTRIBUTE_INTERSECT_DEFAULT:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(2);
 
 	//declare all prop models to Loader
-	let prop_models=bsp.static_props().map(|prop|{
+	let mut prop_models=bsp.static_props().map(|prop|{
 		//get or create mesh_id
 		let mesh_id=mesh_deferred_loader.acquire_mesh_id(prop.model());
 		let placement=prop.as_prop_placement();
 		model::Model{
 			mesh:mesh_id,
-			attributes:TEMP_TOUCH_ME_ATTRIBUTE,
+			attributes:ATTRIBUTE_DECORATION,
 			transform:integer::Planar64Affine3::new(
 				integer::mat3::try_from_f32_array_2d((
 					glam::Mat3A::from_diagonal(glam::Vec3::splat(placement.scale))
@@ -67,7 +114,7 @@ pub fn convert<'a>(
 
 	//the generated MeshIds in here will collide with the Loader Mesh Ids
 	//but I can't think of a good workaround other than just remapping one later.
-	let world_meshes:Vec<model::Mesh>=bsp.models().map(|world_model|{
+	let mut world_meshes:Vec<model::Mesh>=bsp.models().map(|world_model|{
 		let mut mb=model::MeshBuilder::new();
 
 		let color=mb.acquire_color_id(glam::Vec4::ONE);
@@ -118,49 +165,126 @@ pub fn convert<'a>(
 		mb.build(polygon_groups,graphics_groups,vec![])
 	}).collect();
 
-	let world_models:Vec<model::Model>=
-	//one instance of the main world mesh
-	std::iter::once((
-		//world_model
-		model::MeshId::new(0),
-		//model_origin
-		vbsp::Vector::from([0.0,0.0,0.0]),
-		//model_color
-		vbsp::Color{r:255,g:255,b:255},
-	)).chain(
-		//entities sprinkle instances of the other meshes around
-		bsp.entities.iter()
-		.flat_map(|ent|ent.parse())//ignore entity parsing errors
-		.filter_map(|ent|match ent{
-			vbsp::Entity::Brush(brush)=>Some(brush),
-			vbsp::Entity::BrushIllusionary(brush)=>Some(brush),
-			vbsp::Entity::BrushWall(brush)=>Some(brush),
-			vbsp::Entity::BrushWallToggle(brush)=>Some(brush),
-			_=>None,
-		}).flat_map(|brush|
-			//The first character of brush.model is '*'
-			brush.model[1..].parse().map(|mesh_id|//ignore parse int errors
-				(model::MeshId::new(mesh_id),brush.origin,brush.color)
-			)
-		)
-	).map(|(mesh_id,model_origin,vbsp::Color{r,g,b})|{
-		model::Model{
-			mesh:mesh_id,
-			attributes:TEMP_TOUCH_ME_ATTRIBUTE,
-			transform:integer::Planar64Affine3::new(
-				integer::mat3::identity(),
-				valve_transform(model_origin.into())
-			),
-			color:(glam::Vec3::from_array([r as f32,g as f32,b as f32])/255.0).extend(1.0),
+	let mut found_spawn=None;
+
+	let mut world_models=Vec::new();
+
+	// the one and only world model 0
+	world_models.push(model::Model{
+		mesh:model::MeshId::new(0),
+		attributes:ATTRIBUTE_DECORATION,
+		transform:integer::Planar64Affine3::IDENTITY,
+		color:glam::Vec4::W,
+	});
+
+	const WHITE:vbsp::Color=vbsp::Color{r:255,g:255,b:255};
+	for raw_ent in &bsp.entities{
+		match raw_ent.parse(){
+			Ok(Entity::Cycler(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
+			Ok(Entity::EnvSprite(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor.parse().unwrap_or(WHITE),ATTRIBUTE_DECORATION),
+			Ok(Entity::FuncBreakable(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
+			Ok(Entity::FuncBrush(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
+			Ok(Entity::FuncButton(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
+			Ok(Entity::FuncDoor(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
+			Ok(Entity::FuncDoorRotating(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
+			Ok(Entity::FuncIllusionary(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
+			Ok(Entity::FuncMonitor(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
+			Ok(Entity::FuncMovelinear(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
+			Ok(Entity::FuncPhysbox(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
+			Ok(Entity::FuncPhysboxMultiplayer(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
+			Ok(Entity::FuncRotButton(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
+			Ok(Entity::FuncRotating(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
+			Ok(Entity::FuncTracktrain(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
+			Ok(Entity::FuncTrain(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
+			Ok(Entity::FuncWall(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ATTRIBUTE_DECORATION),
+			Ok(Entity::FuncWallToggle(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ATTRIBUTE_DECORATION),
+			Ok(Entity::FuncWaterAnalog(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor.unwrap_or(WHITE),ATTRIBUTE_DECORATION),
+			Ok(Entity::PropDoorRotating(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
+			Ok(Entity::PropDynamic(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
+			Ok(Entity::PropDynamicOverride(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
+			Ok(Entity::PropPhysics(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
+			Ok(Entity::PropPhysicsMultiplayer(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
+			Ok(Entity::PropPhysicsOverride(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
+			Ok(Entity::PropRagdoll(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
+			Ok(Entity::TriggerGravity(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
+			Ok(Entity::TriggerHurt(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
+			Ok(Entity::TriggerLook(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
+			Ok(Entity::TriggerMultiple(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ATTRIBUTE_DECORATION),
+			Ok(Entity::TriggerOnce(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
+			Ok(Entity::TriggerProximity(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
+			Ok(Entity::TriggerPush(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
+			Ok(Entity::TriggerSoundscape(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
+			Ok(Entity::TriggerTeleport(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ATTRIBUTE_DECORATION),
+			Ok(Entity::TriggerVphysicsMotion(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
+			Ok(Entity::TriggerWind(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
+			Ok(Entity::InfoPlayerCounterterrorist(spawn))=>{
+				found_spawn=Some(valve_transform(spawn.origin.into()));
+			},
+			Ok(Entity::InfoPlayerTerrorist(spawn))=>{
+				found_spawn=Some(valve_transform(spawn.origin.into()));
+			},
+			Err(e)=>{
+				println!("Bsp Entity parse error: {e}");
+			},
+			_=>(),
 		}
-	}).collect();
+	}
+
+	// physics models
+	for brush in &bsp.brushes{
+		if !brush.flags.contains(vbsp::BrushFlags::SOLID){
+			continue;
+		}
+		let mesh_result=crate::brush::brush_to_mesh(bsp,brush);
+		match mesh_result{
+			Ok(mesh)=>{
+				let mesh_id=model::MeshId::new(world_meshes.len() as u32);
+				world_meshes.push(mesh);
+				world_models.push(model::Model{
+					mesh:mesh_id,
+					attributes:ATTRIBUTE_CONTACT_DEFAULT,
+					transform:integer::Planar64Affine3::new(
+						integer::mat3::identity(),
+						integer::vec3::ZERO,
+					),
+					color:glam::Vec4::ONE,
+				});
+			},
+			Err(e)=>println!("Brush mesh error: {e}"),
+		}
+	}
+
+	let mut modes_list=Vec::new();
+	if let Some(spawn_point)=found_spawn{
+		// create a new mesh
+		let mesh_id=model::MeshId::new(world_meshes.len() as u32);
+		world_meshes.push(crate::brush::unit_cube());
+		// create a new model
+		let model_id=model::ModelId::new(world_models.len() as u32);
+		world_models.push(model::Model{
+			mesh:mesh_id,
+			attributes:ATTRIBUTE_INTERSECT_DEFAULT,
+			transform:integer::Planar64Affine3::from_translation(spawn_point),
+			color:glam::Vec4::W,
+		});
+
+		let first_stage=strafesnet_common::gameplay_modes::Stage::empty(model_id);
+		let main_mode=strafesnet_common::gameplay_modes::Mode::new(
+			strafesnet_common::gameplay_style::StyleModifiers::source_bhop(),
+			model_id,
+			std::collections::HashMap::new(),
+			vec![first_stage],
+			std::collections::HashMap::new(),
+		);
+		modes_list.push(main_mode);
+	}
 
 	PartialMap1{
 		attributes:unique_attributes,
 		world_meshes,
 		prop_models,
 		world_models,
-		modes:strafesnet_common::gameplay_modes::Modes::new(Vec::new()),
+		modes:strafesnet_common::gameplay_modes::Modes::new(modes_list),
 	}
 }
 
diff --git a/lib/bsp_loader/src/lib.rs b/lib/bsp_loader/src/lib.rs
index 2905a16..6774ccc 100644
--- a/lib/bsp_loader/src/lib.rs
+++ b/lib/bsp_loader/src/lib.rs
@@ -2,6 +2,7 @@ use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLo
 
 mod bsp;
 mod mesh;
+mod brush;
 pub mod loader;
 
 const VALVE_SCALE:f32=1.0/16.0;