diff --git a/lib/bsp_loader/src/bsp.rs b/lib/bsp_loader/src/bsp.rs
index 38fb404..9c7b11b 100644
--- a/lib/bsp_loader/src/bsp.rs
+++ b/lib/bsp_loader/src/bsp.rs
@@ -1,4 +1,5 @@
 use std::borrow::Cow;
+use std::collections::HashMap;
 
 use vbsp_entities_css::Entity;
 
@@ -6,7 +7,7 @@ use strafesnet_common::{map,model,integer,gameplay_attributes as attr};
 use strafesnet_deferred_loader::deferred_loader::{MeshDeferredLoader,RenderConfigDeferredLoader};
 use strafesnet_deferred_loader::mesh::Meshes;
 use strafesnet_deferred_loader::texture::{RenderConfigs,Texture};
-use strafesnet_common::gameplay_modes::{NormalizedMode,NormalizedModes,Mode,Stage};
+use strafesnet_common::gameplay_modes::{self as modes,NormalizedMode,NormalizedModes,Mode,Stage};
 
 use crate::valve_transform;
 
@@ -35,6 +36,12 @@ fn ingest_vertex(
 	})
 }
 
+#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)]
+enum AddBrush{
+	Available(model::ModelId),
+	Deferred(model::ModelId),
+}
+
 fn add_brush<'a>(
 	mesh_deferred_loader:&mut MeshDeferredLoader<&'a str>,
 	world_models:&mut Vec<model::Model>,
@@ -43,7 +50,7 @@ fn add_brush<'a>(
 	origin:vbsp::Vector,
 	rendercolor:vbsp::Color,
 	attributes:attr::CollisionAttributesId,
-){
+)->Option<AddBrush>{
 	let transform=integer::Planar64Affine3::from_translation(
 		valve_transform(origin.into())
 	);
@@ -58,20 +65,24 @@ fn add_brush<'a>(
 		("*",id_str)=>match id_str.parse(){
 			Ok(mesh_id)=>{
 				let mesh=model::MeshId::new(mesh_id);
+				let model_id=model::ModelId::new(world_models.len() as u32);
 				world_models.push(
 					model::Model{mesh,attributes,transform,color}
 				);
+				Some(AddBrush::Available(model_id))
 			},
 			Err(e)=>{
 				println!("Brush model int parse error: {e} model={model}");
-				return;
+				None
 			},
 		},
 		_=>{
 			let mesh=mesh_deferred_loader.acquire_mesh_id(model);
+			let model_id=model::ModelId::new(prop_models.len() as u32);
 			prop_models.push(
 				model::Model{mesh,attributes,transform,color}
 			);
+			Some(AddBrush::Deferred(model_id))
 		}
 	}
 }
@@ -80,7 +91,7 @@ pub fn convert<'a>(
 	bsp:&'a crate::Bsp,
 	render_config_deferred_loader:&mut RenderConfigDeferredLoader<Cow<'a,str>>,
 	mesh_deferred_loader:&mut MeshDeferredLoader<&'a str>,
-)->PartialMap1{
+)->PartialMap1<'a>{
 	let bsp=bsp.as_ref();
 	//figure out real attributes later
 	let unique_attributes=vec![
@@ -151,7 +162,7 @@ pub fn convert<'a>(
 
 		let color=mb.acquire_color_id(glam::Vec4::ONE);
 		let mut graphics_groups=Vec::new();
-		let mut render_id_to_graphics_group_id=std::collections::HashMap::new();
+		let mut render_id_to_graphics_group_id=HashMap::new();
 		let polygon_groups=world_model.faces().enumerate().map(|(polygon_group_id,face)|{
 			let polygon_group_id=model::PolygonGroupId::new(polygon_group_id as u32);
 			let face_texture=face.texture();
@@ -213,22 +224,25 @@ pub fn convert<'a>(
 	let destination_mesh_id=model::MeshId::new(world_meshes.len() as u32);
 	world_meshes.push(crate::brush::unit_cube());
 
+	let mut teleports=HashMap::new();
+	let mut teleport_destinations=HashMap::new();
+
 	const WHITE:vbsp::Color=vbsp::Color{r:255,g:255,b:255};
 	const ENTITY_ATTRIBUTE:gameplay_attributes::CollisionAttributesId=ATTRIBUTE_DECORATION;
 	const ENTITY_TRIGGER_ATTRIBUTE:gameplay_attributes::CollisionAttributesId=ATTRIBUTE_INTERSECT_DEFAULT;
 	for raw_ent in &bsp.entities{
 		macro_rules! ent_brush_default{
 			($entity:ident)=>{
-				add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,$entity.model,$entity.origin,$entity.rendercolor,ENTITY_ATTRIBUTE)
+				{add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,$entity.model,$entity.origin,$entity.rendercolor,ENTITY_ATTRIBUTE);}
 			};
 		}	macro_rules! ent_brush_prop{
 			($entity:ident)=>{
-				add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,$entity.model,$entity.origin,WHITE,ENTITY_ATTRIBUTE)
+				{add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,$entity.model,$entity.origin,WHITE,ENTITY_ATTRIBUTE);}
 			};
 		}
 		macro_rules! ent_brush_trigger{
 			($entity:ident)=>{
-				add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,$entity.model,$entity.origin,WHITE,ENTITY_TRIGGER_ATTRIBUTE)
+				{add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,$entity.model,$entity.origin,WHITE,ENTITY_TRIGGER_ATTRIBUTE);}
 			};
 		}
 		match raw_ent.parse(){
@@ -287,7 +301,7 @@ pub fn convert<'a>(
 			Ok(Entity::FuncFishPool(_func_fish_pool))=>(),
 			Ok(Entity::FuncFootstepControl(_func_footstep_control))=>(),
 			Ok(Entity::FuncHostageRescue(_func_hostage_rescue))=>(),
-			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::FuncIllusionary(brush))=>{add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION);},
 			Ok(Entity::FuncLod(_func_lod))=>(),
 			Ok(Entity::FuncMonitor(brush))=>ent_brush_default!(brush),
 			Ok(Entity::FuncMovelinear(brush))=>ent_brush_default!(brush),
@@ -300,9 +314,9 @@ pub fn convert<'a>(
 			Ok(Entity::FuncSmokevolume(_func_smokevolume))=>(),
 			Ok(Entity::FuncTracktrain(brush))=>ent_brush_default!(brush),
 			Ok(Entity::FuncTrain(brush))=>ent_brush_default!(brush),
-			Ok(Entity::FuncWall(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ENTITY_ATTRIBUTE),
-			Ok(Entity::FuncWallToggle(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ENTITY_ATTRIBUTE),
-			Ok(Entity::FuncWaterAnalog(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor.unwrap_or(WHITE),ENTITY_ATTRIBUTE),
+			Ok(Entity::FuncWall(brush))=>{add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ENTITY_ATTRIBUTE);},
+			Ok(Entity::FuncWallToggle(brush))=>{add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ENTITY_ATTRIBUTE);},
+			Ok(Entity::FuncWaterAnalog(brush))=>{add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor.unwrap_or(WHITE),ENTITY_ATTRIBUTE);},
 			Ok(Entity::GamePlayerEquip(_game_player_equip))=>(),
 			Ok(Entity::GameText(_game_text))=>(),
 			Ok(Entity::GameUi(_game_ui))=>(),
@@ -321,7 +335,17 @@ pub fn convert<'a>(
 			Ok(Entity::InfoPlayerTerrorist(spawn))=>found_spawn=Some(spawn.origin),
 			Ok(Entity::InfoTarget(_info_target))=>(),
 			// InfoTeleportDestination is Spawn#
-			Ok(Entity::InfoTeleportDestination(_info_teleport_destination))=>(),
+			Ok(Entity::InfoTeleportDestination(info_teleport_destination))=>if let Some(target)=info_teleport_destination.targetname{
+				// create a new model
+				let model_id=model::ModelId::new(world_models.len() as u32);
+				world_models.push(model::Model{
+					mesh:destination_mesh_id,
+					attributes:ATTRIBUTE_INTERSECT_DEFAULT,
+					transform:integer::Planar64Affine3::from_translation(valve_transform(info_teleport_destination.origin.into())),
+					color:glam::Vec4::W,
+				});
+				teleport_destinations.insert(target,model_id);
+			},
 			Ok(Entity::Infodecal(_infodecal))=>(),
 			Ok(Entity::KeyframeRope(_keyframe_rope))=>(),
 			Ok(Entity::Light(_light))=>(),
@@ -371,14 +395,18 @@ pub fn convert<'a>(
 			Ok(Entity::TriggerGravity(brush))=>ent_brush_trigger!(brush),
 			Ok(Entity::TriggerHurt(brush))=>ent_brush_trigger!(brush),
 			Ok(Entity::TriggerLook(brush))=>ent_brush_trigger!(brush),
-			Ok(Entity::TriggerMultiple(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ENTITY_TRIGGER_ATTRIBUTE),
+			Ok(Entity::TriggerMultiple(brush))=>{add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ENTITY_TRIGGER_ATTRIBUTE);},
 			Ok(Entity::TriggerOnce(brush))=>ent_brush_trigger!(brush),
 			Ok(Entity::TriggerProximity(brush))=>ent_brush_trigger!(brush),
 			// TriggerPush is booster
 			Ok(Entity::TriggerPush(brush))=>ent_brush_trigger!(brush),
 			Ok(Entity::TriggerSoundscape(brush))=>ent_brush_trigger!(brush),
 			// TriggerTeleport is Trigger#
-			Ok(Entity::TriggerTeleport(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ENTITY_TRIGGER_ATTRIBUTE),
+			Ok(Entity::TriggerTeleport(brush))=>{
+				if let (Some(model_id),Some(target))=(add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ENTITY_TRIGGER_ATTRIBUTE),brush.target){
+					teleports.insert(model_id,target);
+				}
+			},
 			Ok(Entity::TriggerVphysicsMotion(brush))=>ent_brush_trigger!(brush),
 			Ok(Entity::TriggerWind(brush))=>ent_brush_trigger!(brush),
 			Ok(Entity::WaterLodControl(_water_lod_control))=>(),
@@ -456,8 +484,7 @@ pub fn convert<'a>(
 		}
 	}
 
-	let mut modes_list=Vec::new();
-	if let Some(spawn_point)=found_spawn{
+	let first_stage=found_spawn.map(|spawn_point|{
 		// create a new model
 		let model_id=model::ModelId::new(world_models.len() as u32);
 		world_models.push(model::Model{
@@ -467,58 +494,58 @@ pub fn convert<'a>(
 			color:glam::Vec4::W,
 		});
 
-		let first_stage=Stage::empty(model_id);
-		let main_mode=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(NormalizedMode::new(main_mode));
-	}
+		Stage::empty(model_id)
+	});
 
 	PartialMap1{
 		attributes:unique_attributes,
 		world_meshes,
 		prop_models,
 		world_models,
-		modes:NormalizedModes::new(modes_list),
+		first_stage,
+		teleports,
+		teleport_destinations,
 	}
 }
 
 //partially constructed map types
-pub struct PartialMap1{
+pub struct PartialMap1<'a>{
 	attributes:Vec<attr::CollisionAttributes>,
 	prop_models:Vec<model::Model>,
 	world_meshes:Vec<model::Mesh>,
 	world_models:Vec<model::Model>,
-	modes:NormalizedModes,
+	first_stage:Option<Stage>,
+	teleports:HashMap<AddBrush,&'a str>,
+	teleport_destinations:HashMap<&'a str,model::ModelId>,
 }
-impl PartialMap1{
-	pub fn add_prop_meshes<'a>(
+impl<'a> PartialMap1<'a>{
+	pub fn add_prop_meshes(
 		self,
 		prop_meshes:Meshes,
-	)->PartialMap2{
+	)->PartialMap2<'a>{
 		PartialMap2{
 			attributes:self.attributes,
 			prop_meshes:prop_meshes.consume().collect(),
 			prop_models:self.prop_models,
 			world_meshes:self.world_meshes,
 			world_models:self.world_models,
-			modes:self.modes,
+			first_stage:self.first_stage,
+			teleports:self.teleports,
+			teleport_destinations:self.teleport_destinations,
 		}
 	}
 }
-pub struct PartialMap2{
+pub struct PartialMap2<'a>{
 	attributes:Vec<attr::CollisionAttributes>,
 	prop_meshes:Vec<(model::MeshId,model::Mesh)>,
 	prop_models:Vec<model::Model>,
 	world_meshes:Vec<model::Mesh>,
 	world_models:Vec<model::Model>,
-	modes:NormalizedModes,
+	first_stage:Option<Stage>,
+	teleports:HashMap<AddBrush,&'a str>,
+	teleport_destinations:HashMap<&'a str,model::ModelId>,
 }
-impl PartialMap2{
+impl PartialMap2<'_>{
 	pub fn add_render_configs_and_textures(
 		mut self,
 		render_configs:RenderConfigs,
@@ -526,22 +553,70 @@ impl PartialMap2{
 		//merge mesh and model lists, flatten and remap all ids
 		let mesh_id_offset=self.world_meshes.len();
 		println!("prop_meshes.len()={}",self.prop_meshes.len());
-		let (mut prop_meshes,prop_mesh_id_map):(Vec<model::Mesh>,std::collections::HashMap<model::MeshId,model::MeshId>)
+		let (mut prop_meshes,prop_mesh_id_map):(Vec<model::Mesh>,HashMap<model::MeshId,model::MeshId>)
 		=self.prop_meshes.into_iter().enumerate().map(|(new_mesh_id,(old_mesh_id,mesh))|{
 			(mesh,(old_mesh_id,model::MeshId::new((mesh_id_offset+new_mesh_id) as u32)))
 		}).unzip();
 		self.world_meshes.append(&mut prop_meshes);
-		//there is no modes or runtime behaviour with references to the model ids currently
-		//so just relentlessly cull them if the mesh is missing
-		self.world_models.extend(self.prop_models.into_iter().filter_map(|mut model|
+
+		// cull models with a missing mesh
+		let model_id_offset=self.world_models.len();
+		let mut prop_model_id_to_final_model_id=HashMap::new();
+		self.world_models.extend(self.prop_models.into_iter().enumerate().filter_map(|(prop_model_id,mut model)|
 			prop_mesh_id_map.get(&model.mesh).map(|&new_mesh_id|{
 				model.mesh=new_mesh_id;
-				model
+				(prop_model_id,model)
 			})
+		).enumerate().map(|(model_id,(prop_model_id,model))|{
+			prop_model_id_to_final_model_id.insert(
+				model::ModelId::new(prop_model_id as u32),
+				model::ModelId::new((model_id_offset+model_id) as u32),
+			);
+			model
+		}));
+
+		//calculate teleports
+		let first_stage_spawn_model_id=self.first_stage.as_ref().unwrap().spawn();
+		let mut teleport_destinations=HashMap::new();
+		let stages={
+			let mut stages=self.teleport_destinations.iter().map(|(&target,&model_id)|(target,model_id)).collect::<Vec<_>>();
+			stages.sort_by_key(|&(target,_)|target);
+			self.first_stage.into_iter().chain(
+				stages.into_iter().enumerate().map(|(stage_id,(target,model_id))|{
+					let stage_id=modes::StageId::new(1+stage_id as u32);
+					teleport_destinations.insert(target,stage_id);
+					Stage::empty(model_id)
+				})
+			).collect()
+		};
+		let mut elements=HashMap::new();
+		for (teleport_model,target) in self.teleports{
+			if let Some(&stage_id)=teleport_destinations.get(target){
+				let model_id=match teleport_model{
+					AddBrush::Available(model_id)=>Some(model_id),
+					AddBrush::Deferred(model_id)=>prop_model_id_to_final_model_id.get(&model_id).copied(),
+				};
+				if let Some(model_id)=model_id{
+					elements.insert(model_id,modes::StageElement::new(
+						stage_id,
+						true,
+						modes::StageElementBehaviour::Teleport,
+						None,
+					));
+				}
+			}
+		}
+		let main_mode=NormalizedMode::new(Mode::new(
+			strafesnet_common::gameplay_style::StyleModifiers::default(),
+			first_stage_spawn_model_id,
+			HashMap::new(),
+			stages,
+			elements,
 		));
-		//let mut models=Vec::new();
+		let modes=NormalizedModes::new(vec![main_mode]);
+
 		let (textures,render_configs)=render_configs.consume();
-		let (textures,texture_id_map):(Vec<Vec<u8>>,std::collections::HashMap<model::TextureId,model::TextureId>)
+		let (textures,texture_id_map):(Vec<Vec<u8>>,HashMap<model::TextureId,model::TextureId>)
 		=textures.into_iter()
 		//.filter_map(f) cull unused textures
 		.enumerate().map(|(new_texture_id,(old_texture_id,Texture::ImageDDS(texture)))|{
@@ -555,7 +630,7 @@ impl PartialMap2{
 			render_config
 		}).collect();
 		map::CompleteMap{
-			modes:self.modes,
+			modes,
 			attributes:self.attributes,
 			meshes:self.world_meshes,
 			models:self.world_models,