diff --git a/Cargo.lock b/Cargo.lock
index 0a553ac5..34a5c360 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2019,6 +2019,12 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
 
+[[package]]
+name = "replace_with"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3a8614ee435691de62bcffcf4a66d91b3594bf1428a5722e79103249a095690"
+
 [[package]]
 name = "rmp"
 version = "0.8.14"
@@ -2236,6 +2242,7 @@ dependencies = [
  "id",
  "parking_lot",
  "pollster",
+ "replace_with",
  "strafesnet_bsp_loader",
  "strafesnet_common",
  "strafesnet_deferred_loader",
diff --git a/lib/common/src/physics.rs b/lib/common/src/physics.rs
index dc6be13d..5f5fd77d 100644
--- a/lib/common/src/physics.rs
+++ b/lib/common/src/physics.rs
@@ -1,11 +1,34 @@
+use crate::mouse::MouseState;
+
 #[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
 pub enum TimeInner{}
 pub type Time=crate::integer::Time<TimeInner>;
 
 #[derive(Clone,Debug)]
 pub enum Instruction{
-	ReplaceMouse(crate::mouse::MouseState<TimeInner>,crate::mouse::MouseState<TimeInner>),
-	SetNextMouse(crate::mouse::MouseState<TimeInner>),
+	Mouse(MouseInstruction),
+	Other(OtherInstruction),
+}
+impl Instruction{
+	pub const IDLE:Self=Self::Other(OtherInstruction::Other(OtherOtherInstruction::Idle));
+}
+#[derive(Clone,Debug)]
+pub enum OtherInstruction{
+	SetControl(SetControlInstruction),
+	Mode(ModeInstruction),
+	Other(OtherOtherInstruction),
+}
+#[derive(Clone,Debug)]
+pub enum MouseInstruction{
+	/// Replace the entire interpolation state to avoid dividing by zero when replacing twice
+	ReplaceMouse{
+		m0:MouseState<TimeInner>,
+		m1:MouseState<TimeInner>,
+	},
+	SetNextMouse(MouseState<TimeInner>),
+}
+#[derive(Clone,Debug)]
+pub enum SetControlInstruction{
 	SetMoveRight(bool),
 	SetMoveUp(bool),
 	SetMoveBack(bool),
@@ -14,6 +37,9 @@ pub enum Instruction{
 	SetMoveForward(bool),
 	SetJump(bool),
 	SetZoom(bool),
+}
+#[derive(Clone,Debug)]
+pub enum ModeInstruction{
 	/// Reset: fully replace the physics state.
 	/// This forgets all inputs and settings which need to be reapplied.
 	Reset,
@@ -22,10 +48,11 @@ pub enum Instruction{
 	/// Spawn: Teleport to a specific mode's spawn
 	/// Sets current mode & spawn
 	Spawn(crate::gameplay_modes::ModeId,crate::gameplay_modes::StageId),
+}
+#[derive(Clone,Debug)]
+pub enum OtherOtherInstruction{
+	/// Idle: there were no input events, but the simulation is safe to advance to this timestep
 	Idle,
-		//Idle: there were no input events, but the simulation is safe to advance to this timestep
-		//for interpolation / networking / playback reasons, most playback heads will always want
-		//to be 1 instruction ahead to generate the next state for interpolation.
 	PracticeFly,
 	SetSensitivity(crate::integer::Ratio64Vec2),
 }
diff --git a/strafe-client/Cargo.toml b/strafe-client/Cargo.toml
index 19ce1770..a631d0a0 100644
--- a/strafe-client/Cargo.toml
+++ b/strafe-client/Cargo.toml
@@ -23,6 +23,7 @@ glam = "0.29.0"
 id = { version = "0.1.0", registry = "strafesnet" }
 parking_lot = "0.12.1"
 pollster = "0.4.0"
+replace_with = "0.1.7"
 strafesnet_bsp_loader = { path = "../lib/bsp_loader", registry = "strafesnet", optional = true }
 strafesnet_common = { path = "../lib/common", registry = "strafesnet" }
 strafesnet_deferred_loader = { path = "../lib/deferred_loader", features = ["legacy"], registry = "strafesnet", optional = true }
diff --git a/strafe-client/src/graphics.rs b/strafe-client/src/graphics.rs
index b870ad23..8002da74 100644
--- a/strafe-client/src/graphics.rs
+++ b/strafe-client/src/graphics.rs
@@ -1,7 +1,6 @@
 use std::borrow::Cow;
 use std::collections::{HashSet,HashMap};
 use strafesnet_common::map;
-use strafesnet_common::integer;
 use strafesnet_common::model::{self, ColorId, NormalId, PolygonIter, PositionId, RenderConfigId, TextureCoordinateId, VertexId};
 use wgpu::{util::DeviceExt,AstcBlock,AstcChannel};
 use crate::model_graphics::{self,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelColor4,GraphicsModelOwned,GraphicsVertex};
@@ -876,7 +875,7 @@ impl GraphicsState{
 		view:&wgpu::TextureView,
 		device:&wgpu::Device,
 		queue:&wgpu::Queue,
-		frame_state:crate::physics_worker::FrameState,
+		frame_state:crate::session::FrameState,
 	){
 		//TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input
 
diff --git a/strafe-client/src/graphics_worker.rs b/strafe-client/src/graphics_worker.rs
index dba4203a..76d6ecaa 100644
--- a/strafe-client/src/graphics_worker.rs
+++ b/strafe-client/src/graphics_worker.rs
@@ -1,5 +1,5 @@
 pub enum Instruction{
-	Render(crate::physics_worker::FrameState),
+	Render(crate::session::FrameState),
 	//UpdateModel(crate::graphics::GraphicsModelUpdate),
 	Resize(winit::dpi::PhysicalSize<u32>,crate::settings::UserSettings),
 	ChangeMap(strafesnet_common::map::CompleteMap),
@@ -14,13 +14,13 @@ WorkerDescription{
 */
 //up to three frames in flight, dropping new frame requests when all three are busy, and dropping output frames when one renders out of order
 
-pub fn new<'a>(
+pub fn new(
 	mut graphics:crate::graphics::GraphicsState,
 	mut config:wgpu::SurfaceConfiguration,
-	surface:wgpu::Surface<'a>,
+	surface:wgpu::Surface,
 	device:wgpu::Device,
 	queue:wgpu::Queue,
-)->crate::compat_worker::INWorker<'a,Instruction>{
+)->crate::compat_worker::INWorker<'_,Instruction>{
 	crate::compat_worker::INWorker::new(move |ins:Instruction|{
 		match ins{
 			Instruction::ChangeMap(map)=>{
diff --git a/strafe-client/src/main.rs b/strafe-client/src/main.rs
index faae7147..968c6309 100644
--- a/strafe-client/src/main.rs
+++ b/strafe-client/src/main.rs
@@ -4,6 +4,7 @@ mod setup;
 mod window;
 mod worker;
 mod physics;
+mod session;
 mod graphics;
 mod settings;
 mod push_solve;
@@ -13,6 +14,7 @@ mod model_physics;
 mod model_graphics;
 mod physics_worker;
 mod graphics_worker;
+mod mouse_interpolator;
 
 const TITLE:&'static str=concat!("Strafe Client v",env!("CARGO_PKG_VERSION"));
 
diff --git a/strafe-client/src/mouse_interpolator.rs b/strafe-client/src/mouse_interpolator.rs
new file mode 100644
index 00000000..8b26fe1c
--- /dev/null
+++ b/strafe-client/src/mouse_interpolator.rs
@@ -0,0 +1,245 @@
+use strafesnet_common::mouse::MouseState;
+use strafesnet_common::physics::{
+	Instruction as PhysicsInputInstruction,
+	TimeInner as PhysicsTimeInner,
+	Time as PhysicsTime,
+	MouseInstruction,
+	OtherInstruction,
+};
+use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
+use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,TimedInstruction};
+
+type TimedPhysicsInstruction=TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>;
+type TimedUnbufferedInstruction=TimedInstruction<Instruction,PhysicsTimeInner>;
+type DoubleTimedUnbufferedInstruction=TimedInstruction<TimedUnbufferedInstruction,SessionTimeInner>;
+
+const MOUSE_TIMEOUT:SessionTime=SessionTime::from_millis(10);
+
+/// To be fed into MouseInterpolator
+#[derive(Clone,Debug)]
+pub enum Instruction{
+	MoveMouse(glam::IVec2),
+	Other(OtherInstruction),
+}
+
+pub enum StepInstruction{
+	Pop,
+	Timeout,
+}
+
+#[derive(Clone,Debug)]
+enum BufferState{
+	Unbuffered,
+	Initializing(SessionTime,MouseState<PhysicsTimeInner>),
+	Buffered(SessionTime,MouseState<PhysicsTimeInner>),
+}
+
+pub struct MouseInterpolator{
+	buffer_state:BufferState,
+	// double timestamped timeline?
+	buffer:std::collections::VecDeque<TimedPhysicsInstruction>,
+	output:std::collections::VecDeque<TimedPhysicsInstruction>,
+}
+// Maybe MouseInterpolator manipulation is better expressed using impls
+// and called from Instruction trait impls in session
+impl InstructionConsumer<TimedUnbufferedInstruction> for MouseInterpolator{
+	type TimeInner=SessionTimeInner;
+	fn process_instruction(&mut self,ins:DoubleTimedUnbufferedInstruction){
+		self.push_unbuffered_input(ins)
+	}
+}
+impl InstructionEmitter<StepInstruction> for MouseInterpolator{
+	type TimeInner=SessionTimeInner;
+	fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::TimeInner>>{
+		self.buffered_instruction_with_timeout(time_limit)
+	}
+}
+impl MouseInterpolator{
+	pub fn new()->MouseInterpolator{
+		MouseInterpolator{
+			buffer_state:BufferState::Unbuffered,
+			buffer:std::collections::VecDeque::new(),
+			output:std::collections::VecDeque::new(),
+		}
+	}
+	fn push_mouse_and_flush_buffer(&mut self,ins:TimedInstruction<MouseInstruction,PhysicsTimeInner>){
+		self.buffer.push_front(TimedInstruction{
+			time:ins.time,
+			instruction:PhysicsInputInstruction::Mouse(ins.instruction),
+		});
+		// flush buffer to output
+		if self.output.len()==0{
+			// swap buffers
+			core::mem::swap(&mut self.buffer,&mut self.output);
+		}else{
+			// append buffer contents to output
+			self.output.append(&mut self.buffer);
+		}
+	}
+	fn get_mouse_timedout_at(&self,time_limit:SessionTime)->Option<SessionTime>{
+		match &self.buffer_state{
+			BufferState::Unbuffered=>None,
+			BufferState::Initializing(time,_mouse_state)
+			|BufferState::Buffered(time,_mouse_state)=>{
+				let timeout=*time+MOUSE_TIMEOUT;
+				(timeout<time_limit).then_some(timeout)
+			}
+		}
+	}
+	fn timeout_mouse(&mut self,time:PhysicsTime){
+		let buffer_state=core::mem::replace(&mut self.buffer_state,BufferState::Unbuffered);
+		match buffer_state{
+			BufferState::Unbuffered=>(),
+			BufferState::Initializing(_time,mouse_state)=>{
+				// only a single mouse move was sent in 10ms, this is very much an edge case!
+				self.push_mouse_and_flush_buffer(TimedInstruction{
+					time:mouse_state.time,
+					instruction:MouseInstruction::ReplaceMouse{
+						m1:MouseState{pos:mouse_state.pos,time},
+						m0:mouse_state,
+					},
+				});
+			}
+			BufferState::Buffered(_time,mouse_state)=>{
+				// convert to BufferState::Unbuffered
+				// use the first instruction which should be a mouse instruction
+				// to push a ReplaceMouse instruction
+				// duplicate the current mouse
+				self.push_mouse_and_flush_buffer(TimedInstruction{
+					// This should be simulation_timer.time(timeout)
+					// but the timer is not accessible from this scope
+					// and it's just here to say that the mouse isn't moving anyways.
+					// I think this is a divide by zero bug, two identical mouse_states will occupy the interpolation state
+					time:mouse_state.time,
+					instruction:MouseInstruction::SetNextMouse(MouseState{pos:mouse_state.pos,time}),
+				});
+			},
+		}
+	}
+	pub fn push_unbuffered_input(&mut self,ins:DoubleTimedUnbufferedInstruction){
+		// new input
+		// if there is zero instruction buffered, it means the mouse is not moving
+		// case 1: unbuffered
+		// no mouse event is buffered
+		// - ins is mouse event? change to buffered
+		// - ins other -> write to timeline
+		// case 2: buffered
+		// a mouse event is buffered, and exists within the last 10ms
+		// case 3: stop
+		// a mouse event is buffered, but no mouse events have transpired within 10ms
+
+		// push buffered mouse instruction and flush buffer to output
+		if self.get_mouse_timedout_at(ins.time).is_some(){
+			self.timeout_mouse(ins.instruction.time);
+		}
+		// replace_with allows the enum variant to safely be replaced from behind a mutable reference
+		let (ins_mouse,ins_other)=replace_with::replace_with_or_abort_and_return(&mut self.buffer_state,|buffer_state|{
+			match ins.instruction.instruction{
+				Instruction::MoveMouse(pos)=>{
+					let next_mouse_state=MouseState{pos,time:ins.instruction.time};
+					match buffer_state{
+						BufferState::Unbuffered=>{
+							((None,None),BufferState::Initializing(ins.time,next_mouse_state))
+						},
+						BufferState::Initializing(_time,mouse_state)=>{
+							let ins_mouse=TimedInstruction{
+								time:mouse_state.time,
+								instruction:MouseInstruction::ReplaceMouse{
+									m0:mouse_state,
+									m1:next_mouse_state.clone(),
+								},
+							};
+							((Some(ins_mouse),None),BufferState::Buffered(ins.time,next_mouse_state))
+						},
+						BufferState::Buffered(_time,mouse_state)=>{
+							let ins_mouse=TimedInstruction{
+								time:mouse_state.time,
+								instruction:MouseInstruction::SetNextMouse(next_mouse_state.clone()),
+							};
+							((Some(ins_mouse),None),BufferState::Buffered(ins.time,next_mouse_state))
+						},
+					}
+				},
+				Instruction::Other(other_instruction)=>((None,Some(TimedInstruction{
+					time:ins.instruction.time,
+					instruction:other_instruction,
+				})),buffer_state),
+			}
+		});
+		if let Some(ins)=ins_mouse{
+			self.push_mouse_and_flush_buffer(ins);
+		}
+		if let Some(ins)=ins_other{
+			let instruction=TimedInstruction{
+				time:ins.time,
+				instruction:PhysicsInputInstruction::Other(ins.instruction),
+			};
+			if matches!(self.buffer_state,BufferState::Unbuffered){
+				self.output.push_back(instruction);
+			}else{
+				self.buffer.push_back(instruction);
+			}
+		}
+	}
+	pub fn buffered_instruction_with_timeout(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,SessionTimeInner>>{
+		match self.get_mouse_timedout_at(time_limit){
+			Some(timeout)=>Some(TimedInstruction{
+				time:timeout,
+				instruction:StepInstruction::Timeout,
+			}),
+			None=>(self.output.len()!=0).then_some(TimedInstruction{
+				// this timestamp should not matter
+				time:time_limit,
+				instruction:StepInstruction::Pop,
+			}),
+		}
+	}
+	pub fn pop_buffered_instruction(&mut self,ins:TimedInstruction<StepInstruction,PhysicsTimeInner>)->Option<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>{
+		match ins.instruction{
+			StepInstruction::Pop=>(),
+			StepInstruction::Timeout=>self.timeout_mouse(ins.time),
+		}
+		self.output.pop_front()
+	}
+}
+
+#[cfg(test)]
+mod test{
+	use super::*;
+	#[test]
+	fn test(){
+		let mut interpolator=MouseInterpolator::new();
+
+		let timer=strafesnet_common::timer::Timer::<strafesnet_common::timer::Scaled<SessionTimeInner,PhysicsTimeInner>>::unpaused(SessionTime::ZERO,PhysicsTime::from_secs(1000));
+
+		macro_rules! push{
+			($time:expr,$ins:expr)=>{
+				println!("in={:?}",$ins);
+				interpolator.push_unbuffered_input(TimedInstruction{
+					time:$time,
+					instruction:TimedInstruction{
+						time:timer.time($time),
+						instruction:$ins,
+					}
+				});
+				while let Some(ins)=interpolator.buffered_instruction_with_timeout($time){
+					let ins_retimed=TimedInstruction{
+						time:timer.time(ins.time),
+						instruction:ins.instruction,
+					};
+					let out=interpolator.pop_buffered_instruction(ins_retimed);
+					println!("out={out:?}");
+				}
+			};
+		}
+
+		// test each buffer_state transition
+		let mut t=SessionTime::ZERO;
+		push!(t,Instruction::MoveMouse(glam::ivec2(0,0)));
+		t+=SessionTime::from_millis(5);
+		push!(t,Instruction::MoveMouse(glam::ivec2(0,0)));
+		t+=SessionTime::from_millis(5);
+		push!(t,Instruction::MoveMouse(glam::ivec2(0,0)));
+		t+=SessionTime::from_millis(1);
+	}
+}
diff --git a/strafe-client/src/physics.rs b/strafe-client/src/physics.rs
index 16bbe48a..9d309b10 100644
--- a/strafe-client/src/physics.rs
+++ b/strafe-client/src/physics.rs
@@ -19,25 +19,18 @@ type MouseState=strafesnet_common::mouse::MouseState<TimeInner>;
 
 //external influence
 //this is how you influence the physics from outside
-use strafesnet_common::physics::Instruction as PhysicsInputInstruction;
+use strafesnet_common::physics::{Instruction,OtherInstruction,MouseInstruction,ModeInstruction,OtherOtherInstruction,SetControlInstruction};
 
 //internal influence
 //when the physics asks itself what happens next, this is how it's represented
 #[derive(Debug)]
-pub enum PhysicsInternalInstruction{
+pub enum InternalInstruction{
 	CollisionStart(Collision,model_physics::GigaTime),
 	CollisionEnd(Collision,model_physics::GigaTime),
 	StrafeTick,
 	ReachWalkTargetVelocity,
 	// Water,
 }
-#[derive(Debug)]
-pub enum PhysicsInstruction{
-	Internal(PhysicsInternalInstruction),
-	//InputInstructions conditionally activate RefreshWalkTarget
-	//(by doing what SetWalkTargetVelocity used to do and then flagging it)
-	Input(PhysicsInputInstruction),
-}
 
 #[derive(Clone,Debug,Default)]
 pub struct InputState{
@@ -558,13 +551,13 @@ impl MoveState{
 			=>None,
 		}
 	}
-	fn next_move_instruction(&self,strafe:&Option<gameplay_style::StrafeSettings>,time:Time)->Option<TimedInstruction<PhysicsInternalInstruction,TimeInner>>{
+	fn next_move_instruction(&self,strafe:&Option<gameplay_style::StrafeSettings>,time:Time)->Option<TimedInstruction<InternalInstruction,TimeInner>>{
 		//check if you have a valid walk state and create an instruction
 		match self{
 			MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>match &walk_state.target{
 				&TransientAcceleration::Reachable{acceleration:_,time}=>Some(TimedInstruction{
 					time,
-					instruction:PhysicsInternalInstruction::ReachWalkTargetVelocity
+					instruction:InternalInstruction::ReachWalkTargetVelocity
 				}),
 				TransientAcceleration::Unreachable{acceleration:_}
 				|TransientAcceleration::Reached
@@ -574,7 +567,7 @@ impl MoveState{
 				TimedInstruction{
 					time:strafe.next_tick(time),
 					//only poll the physics if there is a before and after mouse event
-					instruction:PhysicsInternalInstruction::StrafeTick
+					instruction:InternalInstruction::StrafeTick
 				}
 			}),
 			MoveState::Water=>None,//TODO
@@ -786,7 +779,7 @@ impl TouchingState{
 		}).collect();
 		*acceleration=crate::push_solve::push_solve(&contacts,*acceleration);
 	}
-	fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<PhysicsInternalInstruction,TimeInner>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,time:Time){
+	fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<InternalInstruction,TimeInner>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,time:Time){
 		let relative_body=crate::body::VirtualBody::relative(&Body::ZERO,body).body(time);
 		for contact in &self.contacts{
 			//detect face slide off
@@ -795,7 +788,7 @@ impl TouchingState{
 			collector.collect(minkowski.predict_collision_face_out(&relative_body,collector.time(),contact.face_id).map(|(_face,time)|{
 				TimedInstruction{
 					time:relative_body.time+time.into(),
-					instruction:PhysicsInternalInstruction::CollisionEnd(
+					instruction:InternalInstruction::CollisionEnd(
 						Collision::Contact(*contact),
 						time
 					),
@@ -809,7 +802,7 @@ impl TouchingState{
 			collector.collect(minkowski.predict_collision_out(&relative_body,collector.time()).map(|(_face,time)|{
 				TimedInstruction{
 					time:relative_body.time+time.into(),
-					instruction:PhysicsInternalInstruction::CollisionEnd(
+					instruction:InternalInstruction::CollisionEnd(
 						Collision::Intersect(*intersect),
 						time
 					),
@@ -882,11 +875,9 @@ impl PhysicsState{
 		self.touching.clear();
 	}
 	fn reset_to_default(&mut self){
-		let mut new_state=Self::default();
-		new_state.camera.sensitivity=self.camera.sensitivity;
-		*self=new_state;
+		*self=Self::default();
 	}
-	fn next_move_instruction(&self)->Option<TimedInstruction<PhysicsInternalInstruction,TimeInner>>{
+	fn next_move_instruction(&self)->Option<TimedInstruction<InternalInstruction,TimeInner>>{
 		self.move_state.next_move_instruction(&self.style.strafe,self.time)
 	}
 	fn cull_velocity(&mut self,data:&PhysicsData,velocity:Planar64Vec3){
@@ -935,24 +926,24 @@ pub struct PhysicsContext{
 	state:PhysicsState,//this captures the entire state of the physics.
 	data:PhysicsData,//data currently loaded into memory which is needded for physics to run, but is not part of the state.
 }
-// the physics consumes both PhysicsInputInstruction and PhysicsInternalInstruction,
+// the physics consumes both Instruction and PhysicsInternalInstruction,
 // but can only emit PhysicsInternalInstruction
-impl InstructionConsumer<PhysicsInternalInstruction> for PhysicsContext{
+impl InstructionConsumer<InternalInstruction> for PhysicsContext{
 	type TimeInner=TimeInner;
-	fn process_instruction(&mut self,ins:TimedInstruction<PhysicsInternalInstruction,TimeInner>){
+	fn process_instruction(&mut self,ins:TimedInstruction<InternalInstruction,TimeInner>){
 		atomic_internal_instruction(&mut self.state,&self.data,ins)
 	}
 }
-impl InstructionConsumer<PhysicsInputInstruction> for PhysicsContext{
+impl InstructionConsumer<Instruction> for PhysicsContext{
 	type TimeInner=TimeInner;
-	fn process_instruction(&mut self,ins:TimedInstruction<PhysicsInputInstruction,TimeInner>){
+	fn process_instruction(&mut self,ins:TimedInstruction<Instruction,TimeInner>){
 		atomic_input_instruction(&mut self.state,&self.data,ins)
 	}
 }
-impl InstructionEmitter<PhysicsInternalInstruction> for PhysicsContext{
+impl InstructionEmitter<InternalInstruction> for PhysicsContext{
 	type TimeInner=TimeInner;
 	//this little next instruction function could cache its return value and invalidate the cached value by watching the State.
-	fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<PhysicsInternalInstruction,TimeInner>>{
+	fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<InternalInstruction,TimeInner>>{
 		next_instruction_internal(&self.state,&self.data,time_limit)
 	}
 }
@@ -1109,14 +1100,14 @@ impl PhysicsContext{
 		println!("Physics Objects: {}",model_count);
 	}
 
-	pub fn run_input_instruction(&mut self,instruction:TimedInstruction<PhysicsInputInstruction,TimeInner>){
+	pub fn run_input_instruction(&mut self,instruction:TimedInstruction<Instruction,TimeInner>){
 		self.process_exhaustive(instruction.time);
 		self.process_instruction(instruction);
 	}
 }
 
 	//this is the one who asks
-	fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option<TimedInstruction<PhysicsInternalInstruction,TimeInner>>{
+	fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option<TimedInstruction<InternalInstruction,TimeInner>>{
 		//JUST POLLING!!! NO MUTATION
 		let mut collector = instruction::InstructionCollector::new(time_limit);
 
@@ -1140,11 +1131,11 @@ impl PhysicsContext{
 				.map_or(None,|(face,dt)|{
 					// this must be rounded to avoid the infinite loop when hitting the start zone
 					let time=relative_body.time+dt.into();
-					if time<=state.time{None}else{Some((time,face,dt))}})
-				.map(|(time,face,dt)|
+					(state.time<time).then_some((time,face,dt))
+				}).map(|(time,face,dt)|
 					TimedInstruction{
 						time,
-						instruction:PhysicsInternalInstruction::CollisionStart(
+						instruction:InternalInstruction::CollisionStart(
 							Collision::new(convex_mesh_id,face),
 							dt
 						)
@@ -1650,13 +1641,13 @@ fn collision_end_intersect(
 		}
 	}
 }
-fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInternalInstruction,TimeInner>){
+fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<InternalInstruction,TimeInner>){
 	state.time=ins.time;
 	let (should_advance_body,goober_time)=match ins.instruction{
-		PhysicsInternalInstruction::CollisionStart(_,dt)
-		|PhysicsInternalInstruction::CollisionEnd(_,dt)=>(true,Some(dt)),
-		PhysicsInternalInstruction::StrafeTick
-		|PhysicsInternalInstruction::ReachWalkTargetVelocity=>(true,None),
+		InternalInstruction::CollisionStart(_,dt)
+		|InternalInstruction::CollisionEnd(_,dt)=>(true,Some(dt)),
+		InternalInstruction::StrafeTick
+		|InternalInstruction::ReachWalkTargetVelocity=>(true,None),
 	};
 	if should_advance_body{
 		match goober_time{
@@ -1665,7 +1656,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
 		}
 	}
 		match ins.instruction{
-			PhysicsInternalInstruction::CollisionStart(collision,_)=>{
+			InternalInstruction::CollisionStart(collision,_)=>{
 				let mode=data.modes.get_mode(state.mode_state.get_mode_id());
 				match collision{
 					Collision::Contact(contact)=>collision_start_contact(
@@ -1686,7 +1677,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
 					),
 				}
 			},
-			PhysicsInternalInstruction::CollisionEnd(collision,_)=>match collision{
+			InternalInstruction::CollisionEnd(collision,_)=>match collision{
 				Collision::Contact(contact)=>collision_end_contact(
 					&mut state.move_state,&mut state.body,&mut state.touching,&data.models,&data.hitbox_mesh,&state.style,&state.camera,&state.input_state,
 					data.models.contact_attr(contact.model_id),
@@ -1701,7 +1692,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
 					state.time
 				),
 			},
-			PhysicsInternalInstruction::StrafeTick=>{
+			InternalInstruction::StrafeTick=>{
 				//TODO make this less huge
 				if let Some(strafe_settings)=&state.style.strafe{
 					let controls=state.input_state.controls;
@@ -1719,7 +1710,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
 					}
 				}
 			}
-			PhysicsInternalInstruction::ReachWalkTargetVelocity=>{
+			InternalInstruction::ReachWalkTargetVelocity=>{
 				match &mut state.move_state{
 					MoveState::Air
 					|MoveState::Water
@@ -1746,27 +1737,18 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
 		}
 	}
 
-fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInputInstruction,TimeInner>){
+fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<Instruction,TimeInner>){
 	state.time=ins.time;
 	let should_advance_body=match ins.instruction{
 		//the body may as well be a quantum wave function
 		//as far as these instruction are concerned (they don't care where it is)
-		PhysicsInputInstruction::SetSensitivity(..)
-		|PhysicsInputInstruction::Reset
-		|PhysicsInputInstruction::Restart
-		|PhysicsInputInstruction::Spawn(..)
-		|PhysicsInputInstruction::SetZoom(..)
-		|PhysicsInputInstruction::Idle=>false,
+		Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::SetSensitivity(..)))
+		|Instruction::Other(OtherInstruction::Mode(_))
+		|Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetZoom(..)))
+		|Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::Idle))=>false,
 		//these controls only update the body if you are on the ground
-		PhysicsInputInstruction::SetNextMouse(..)
-		|PhysicsInputInstruction::ReplaceMouse(..)
-		|PhysicsInputInstruction::SetMoveForward(..)
-		|PhysicsInputInstruction::SetMoveLeft(..)
-		|PhysicsInputInstruction::SetMoveBack(..)
-		|PhysicsInputInstruction::SetMoveRight(..)
-		|PhysicsInputInstruction::SetMoveUp(..)
-		|PhysicsInputInstruction::SetMoveDown(..)
-		|PhysicsInputInstruction::SetJump(..)=>{
+		Instruction::Mouse(_)
+		|Instruction::Other(OtherInstruction::SetControl(_))=>{
 			match &state.move_state{
 				MoveState::Fly
 				|MoveState::Water
@@ -1776,100 +1758,101 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
 			}
 		},
 		//the body must be updated unconditionally
-		PhysicsInputInstruction::PracticeFly=>true,
+		Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::PracticeFly))=>true,
 	};
 	if should_advance_body{
 		state.body.advance_time(state.time);
 	}
-	//TODO: UNTAB
-				let mut b_refresh_walk_target=true;
-				match ins.instruction{
-					PhysicsInputInstruction::SetSensitivity(sensitivity)=>state.camera.sensitivity=sensitivity,
-					PhysicsInputInstruction::SetNextMouse(m)=>{
-						state.camera.move_mouse(state.input_state.mouse_delta());
-						state.input_state.set_next_mouse(m);
-					},
-					PhysicsInputInstruction::ReplaceMouse(m0,m1)=>{
-						state.camera.move_mouse(m0.pos-state.input_state.mouse.pos);
-						state.input_state.replace_mouse(m0,m1);
-					},
-					PhysicsInputInstruction::SetMoveForward(s)=>state.input_state.set_control(Controls::MoveForward,s),
-					PhysicsInputInstruction::SetMoveLeft(s)=>state.input_state.set_control(Controls::MoveLeft,s),
-					PhysicsInputInstruction::SetMoveBack(s)=>state.input_state.set_control(Controls::MoveBackward,s),
-					PhysicsInputInstruction::SetMoveRight(s)=>state.input_state.set_control(Controls::MoveRight,s),
-					PhysicsInputInstruction::SetMoveUp(s)=>state.input_state.set_control(Controls::MoveUp,s),
-					PhysicsInputInstruction::SetMoveDown(s)=>state.input_state.set_control(Controls::MoveDown,s),
-					PhysicsInputInstruction::SetJump(s)=>{
-						state.input_state.set_control(Controls::Jump,s);
-						if let Some(walk_state)=state.move_state.get_walk_state(){
-							if let Some(jump_settings)=&state.style.jump{
-								let jump_dir=walk_state.jump_direction.direction(&data.models,&data.hitbox_mesh,&walk_state.contact);
-								let booster_option=data.models.contact_attr(walk_state.contact.model_id).general.booster.as_ref();
-								let jumped_velocity=jump_settings.jumped_velocity(&state.style,jump_dir,state.body.velocity,booster_option);
-								state.cull_velocity(&data,jumped_velocity);
-							}
-						}
-						b_refresh_walk_target=false;
-					},
-					PhysicsInputInstruction::SetZoom(s)=>{
-						state.input_state.set_control(Controls::Zoom,s);
-						b_refresh_walk_target=false;
-					},
-					PhysicsInputInstruction::Reset=>{
-						//totally reset physics state
-						state.reset_to_default();
-						b_refresh_walk_target=false;
-					},
-					PhysicsInputInstruction::Restart=>{
-						//teleport to start zone
-						let mode=data.modes.get_mode(state.mode_state.get_mode_id());
-						let spawn_point=mode.and_then(|mode|
-							//TODO: spawn at the bottom of the start zone plus the hitbox size
-							//TODO: set camera andles to face the same way as the start zone
-							data.models.get_model_transform(mode.get_start().into()).map(|transform|
-								transform.vertex.translation
-							)
-						).unwrap_or(vec3::ZERO);
-						set_position(spawn_point,&mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state,mode,&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time);
-						set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO);
-						state.set_move_state(data,MoveState::Air);
-						b_refresh_walk_target=false;
-					}
-					PhysicsInputInstruction::Spawn(mode_id,stage_id)=>{
-						//spawn at a particular stage
-						if let Some(mode)=data.modes.get_mode(mode_id){
-							if let Some(stage)=mode.get_stage(stage_id){
-								let _=teleport_to_spawn(
-									stage.spawn(),
-									&mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state,
-									mode,
-									&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time
-								);
-							}
-						}
-						b_refresh_walk_target=false;
-					},
-					PhysicsInputInstruction::PracticeFly=>{
-						match &state.move_state{
-							MoveState::Fly=>{
-								state.set_move_state(data,MoveState::Air);
-							},
-							_=>{
-								state.set_move_state(data,MoveState::Fly);
-							},
-						}
-						b_refresh_walk_target=false;
-					},
-					PhysicsInputInstruction::Idle=>{
-						//literally idle!
-						b_refresh_walk_target=false;
-					},
+
+	let mut b_refresh_walk_target=true;
+	match ins.instruction{
+		Instruction::Mouse(MouseInstruction::SetNextMouse(m))=>{
+			state.camera.move_mouse(state.input_state.mouse_delta());
+			state.input_state.set_next_mouse(m);
+		},
+		Instruction::Mouse(MouseInstruction::ReplaceMouse{m0,m1})=>{
+			state.camera.move_mouse(m0.pos-state.input_state.mouse.pos);
+			state.input_state.replace_mouse(m0,m1);
+		},
+		Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::SetSensitivity(sensitivity)))=>state.camera.sensitivity=sensitivity,
+		Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveForward(s)))=>state.input_state.set_control(Controls::MoveForward,s),
+		Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveLeft(s)))=>state.input_state.set_control(Controls::MoveLeft,s),
+		Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveBack(s)))=>state.input_state.set_control(Controls::MoveBackward,s),
+		Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveRight(s)))=>state.input_state.set_control(Controls::MoveRight,s),
+		Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveUp(s)))=>state.input_state.set_control(Controls::MoveUp,s),
+		Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveDown(s)))=>state.input_state.set_control(Controls::MoveDown,s),
+		Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetJump(s)))=>{
+			state.input_state.set_control(Controls::Jump,s);
+			if let Some(walk_state)=state.move_state.get_walk_state(){
+				if let Some(jump_settings)=&state.style.jump{
+					let jump_dir=walk_state.jump_direction.direction(&data.models,&data.hitbox_mesh,&walk_state.contact);
+					let booster_option=data.models.contact_attr(walk_state.contact.model_id).general.booster.as_ref();
+					let jumped_velocity=jump_settings.jumped_velocity(&state.style,jump_dir,state.body.velocity,booster_option);
+					state.cull_velocity(&data,jumped_velocity);
 				}
-				if b_refresh_walk_target{
-					state.apply_input_and_body(data);
-					state.cull_velocity(data,state.body.velocity);
-					//also check if accelerating away from surface
+			}
+			b_refresh_walk_target=false;
+		},
+		Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetZoom(s)))=>{
+			state.input_state.set_control(Controls::Zoom,s);
+			b_refresh_walk_target=false;
+		},
+		Instruction::Other(OtherInstruction::Mode(ModeInstruction::Reset))=>{
+			//totally reset physics state
+			state.reset_to_default();
+			b_refresh_walk_target=false;
+		},
+		Instruction::Other(OtherInstruction::Mode(ModeInstruction::Restart))=>{
+			//teleport to start zone
+			let mode=data.modes.get_mode(state.mode_state.get_mode_id());
+			let spawn_point=mode.and_then(|mode|
+				//TODO: spawn at the bottom of the start zone plus the hitbox size
+				//TODO: set camera andles to face the same way as the start zone
+				data.models.get_model_transform(mode.get_start().into()).map(|transform|
+					transform.vertex.translation
+				)
+			).unwrap_or(vec3::ZERO);
+			set_position(spawn_point,&mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state,mode,&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time);
+			set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO);
+			state.set_move_state(data,MoveState::Air);
+			b_refresh_walk_target=false;
+		}
+		// Spawn does not necessarily imply reset
+		Instruction::Other(OtherInstruction::Mode(ModeInstruction::Spawn(mode_id,stage_id)))=>{
+			//spawn at a particular stage
+			if let Some(mode)=data.modes.get_mode(mode_id){
+				if let Some(stage)=mode.get_stage(stage_id){
+					let _=teleport_to_spawn(
+						stage.spawn(),
+						&mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state,
+						mode,
+						&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time
+					);
 				}
+			}
+			b_refresh_walk_target=false;
+		},
+		Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::PracticeFly))=>{
+			match &state.move_state{
+				MoveState::Fly=>{
+					state.set_move_state(data,MoveState::Air);
+				},
+				_=>{
+					state.set_move_state(data,MoveState::Fly);
+				},
+			}
+			b_refresh_walk_target=false;
+		},
+		Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::Idle))=>{
+			//literally idle!
+			b_refresh_walk_target=false;
+		},
+	}
+	if b_refresh_walk_target{
+		state.apply_input_and_body(data);
+		state.cull_velocity(data,state.body.velocity);
+		//also check if accelerating away from surface
+	}
 }
 
 #[cfg(test)]
diff --git a/strafe-client/src/physics_worker.rs b/strafe-client/src/physics_worker.rs
index 1e7ffa34..225b07a8 100644
--- a/strafe-client/src/physics_worker.rs
+++ b/strafe-client/src/physics_worker.rs
@@ -1,249 +1,67 @@
-use strafesnet_common::mouse::MouseState;
+use crate::graphics_worker::Instruction as GraphicsInstruction;
+use crate::session::{SessionInputInstruction,Instruction as SessionInstruction,Session,Simulation};
+use strafesnet_common::instruction::{TimedInstruction,InstructionConsumer};
+use strafesnet_common::physics::Time as PhysicsTime;
 use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
-use strafesnet_common::physics::{Time as PhysicsTime,TimeInner as PhysicsTimeInner,Instruction as PhysicsInputInstruction};
-use strafesnet_common::instruction::TimedInstruction;
-use strafesnet_common::timer::{Scaled,Timer,TimerState};
-use mouse_interpolator::MouseInterpolator;
+use strafesnet_common::timer::Timer;
 
-
-pub struct FrameState{
-	pub body:crate::physics::Body,
-	pub camera:crate::physics::PhysicsCamera,
-	pub time:PhysicsTime,
-}
-
-#[derive(Debug)]
-pub enum InputInstruction{
-	MoveMouse(glam::IVec2),
-	MoveRight(bool),
-	MoveUp(bool),
-	MoveBack(bool),
-	MoveLeft(bool),
-	MoveDown(bool),
-	MoveForward(bool),
-	Jump(bool),
-	Zoom(bool),
-	ResetAndRestart,
-	ResetAndSpawn(strafesnet_common::gameplay_modes::ModeId,strafesnet_common::gameplay_modes::StageId),
-	PracticeFly,
-}
 pub enum Instruction{
-	Input(InputInstruction),
+	Input(SessionInputInstruction),
+	SetPaused(bool),
 	Render,
 	Resize(winit::dpi::PhysicalSize<u32>),
 	ChangeMap(strafesnet_common::map::CompleteMap),
-	//SetPaused is not an InputInstruction: the physics doesn't know that it's paused.
-	SetPaused(bool),
-	//Graphics(crate::graphics_worker::Instruction),
 }
-mod mouse_interpolator{
-	use super::*;
-	//TODO: move this or tab
-pub struct MouseInterpolator{
-	//"PlayerController"
-	user_settings:crate::settings::UserSettings,
-	//"MouseInterpolator"
-	timeline:std::collections::VecDeque<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>,
-	last_mouse_time:PhysicsTime,
-	mouse_blocking:bool,
-	//"Simulation"
-	timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
-	physics:crate::physics::PhysicsContext,
 
-}
-impl MouseInterpolator{
-	pub fn new(
-		physics:crate::physics::PhysicsContext,
-		user_settings:crate::settings::UserSettings,
-	)->MouseInterpolator{
-		MouseInterpolator{
-			mouse_blocking:true,
-			last_mouse_time:physics.get_next_mouse().time,
-			timeline:std::collections::VecDeque::new(),
-			timer:Timer::from_state(Scaled::identity(),false),
-			physics,
-			user_settings,
-		}
-	}
-	fn push_mouse_instruction(&mut self,ins:&TimedInstruction<Instruction,SessionTimeInner>,m:glam::IVec2){
-		if self.mouse_blocking{
-			//tell the game state which is living in the past about its future
-			self.timeline.push_front(TimedInstruction{
-				time:self.last_mouse_time,
-				instruction:PhysicsInputInstruction::SetNextMouse(MouseState{time:self.timer.time(ins.time),pos:m}),
-			});
-		}else{
-			//mouse has just started moving again after being still for longer than 10ms.
-			//replace the entire mouse interpolation state to avoid an intermediate state with identical m0.t m1.t timestamps which will divide by zero
-			self.timeline.push_front(TimedInstruction{
-				time:self.last_mouse_time,
-				instruction:PhysicsInputInstruction::ReplaceMouse(
-					MouseState{time:self.last_mouse_time,pos:self.physics.get_next_mouse().pos},
-					MouseState{time:self.timer.time(ins.time),pos:m}
-				),
-			});
-			//delay physics execution until we have an interpolation target
-			self.mouse_blocking=true;
-		}
-		self.last_mouse_time=self.timer.time(ins.time);
-	}
-	fn push(&mut self,time:SessionTime,phys_input:PhysicsInputInstruction){
-		//This is always a non-mouse event
-		self.timeline.push_back(TimedInstruction{
-			time:self.timer.time(time),
-			instruction:phys_input,
-		});
-	}
-	/// returns should_empty_queue
-	/// may or may not mutate internal state XD!
-	fn map_instruction(&mut self,ins:&TimedInstruction<Instruction,SessionTimeInner>)->bool{
-		let mut update_mouse_blocking=true;
-		match &ins.instruction{
-			Instruction::Input(input_instruction)=>match input_instruction{
-				&InputInstruction::MoveMouse(m)=>{
-					if !self.timer.is_paused(){
-						self.push_mouse_instruction(ins,m);
-					}
-					update_mouse_blocking=false;
-				},
-				&InputInstruction::MoveForward(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveForward(s)),
-				&InputInstruction::MoveLeft(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveLeft(s)),
-				&InputInstruction::MoveBack(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveBack(s)),
-				&InputInstruction::MoveRight(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveRight(s)),
-				&InputInstruction::MoveUp(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveUp(s)),
-				&InputInstruction::MoveDown(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveDown(s)),
-				&InputInstruction::Jump(s)=>self.push(ins.time,PhysicsInputInstruction::SetJump(s)),
-				&InputInstruction::Zoom(s)=>self.push(ins.time,PhysicsInputInstruction::SetZoom(s)),
-				&InputInstruction::ResetAndSpawn(mode_id,stage_id)=>{
-					self.push(ins.time,PhysicsInputInstruction::Reset);
-					self.push(ins.time,PhysicsInputInstruction::SetSensitivity(self.user_settings.calculate_sensitivity()));
-					self.push(ins.time,PhysicsInputInstruction::Spawn(mode_id,stage_id));
-				},
-				InputInstruction::ResetAndRestart=>{
-					self.push(ins.time,PhysicsInputInstruction::Reset);
-					self.push(ins.time,PhysicsInputInstruction::SetSensitivity(self.user_settings.calculate_sensitivity()));
-					self.push(ins.time,PhysicsInputInstruction::Restart);
-				},
-				InputInstruction::PracticeFly=>self.push(ins.time,PhysicsInputInstruction::PracticeFly),
-			},
-			//do these really need to idle the physics?
-			//sending None dumps the instruction queue
-			Instruction::ChangeMap(_)=>self.push(ins.time,PhysicsInputInstruction::Idle),
-			Instruction::Resize(_)=>self.push(ins.time,PhysicsInputInstruction::Idle),
-			Instruction::Render=>self.push(ins.time,PhysicsInputInstruction::Idle),
-			&Instruction::SetPaused(paused)=>{
-				if let Err(e)=self.timer.set_paused(ins.time,paused){
-					println!("Cannot SetPaused: {e}");
-				}
-				self.push(ins.time,PhysicsInputInstruction::Idle);
-			},
-		}
-		if update_mouse_blocking{
-			//this returns the bool for us
-			self.update_mouse_blocking(ins.time)
-		}else{
-			//do flush that queue
-			true
-		}
-	}
-	/// must check if self.mouse_blocking==true before calling!
-	fn unblock_mouse(&mut self,time:SessionTime){
-		//push an event to extrapolate no movement from
-		self.timeline.push_front(TimedInstruction{
-			time:self.last_mouse_time,
-			instruction:PhysicsInputInstruction::SetNextMouse(MouseState{time:self.timer.time(time),pos:self.physics.get_next_mouse().pos}),
-		});
-		self.last_mouse_time=self.timer.time(time);
-		//stop blocking. the mouse is not moving so the physics does not need to live in the past and wait for interpolation targets.
-		self.mouse_blocking=false;
-	}
-	fn update_mouse_blocking(&mut self,time:SessionTime)->bool{
-		if self.mouse_blocking{
-			//assume the mouse has stopped moving after 10ms.
-			//shitty mice are 125Hz which is 8ms so this should cover that.
-			//setting this to 100us still doesn't print even though it's 10x lower than the polling rate,
-			//so mouse events are probably not handled separately from drawing and fire right before it :(
-			if PhysicsTime::from_millis(10)<self.timer.time(time)-self.physics.get_next_mouse().time{
-				self.unblock_mouse(time);
-				true
-			}else{
-				false
-			}
-		}else{
-			//keep this up to date so that it can be used as a known-timestamp
-			//that the mouse was not moving when the mouse starts moving again
-			self.last_mouse_time=self.timer.time(time);
-			true
-		}
-	}
-	fn empty_queue(&mut self){
-		while let Some(instruction)=self.timeline.pop_front(){
-			self.physics.run_input_instruction(instruction);
-		}
-	}
-	pub fn handle_instruction(&mut self,ins:&TimedInstruction<Instruction,SessionTimeInner>){
-		let should_empty_queue=self.map_instruction(ins);
-		if should_empty_queue{
-			self.empty_queue();
-		}
-	}
-	pub fn get_frame_state(&self,time:SessionTime)->FrameState{
-		FrameState{
-			body:self.physics.camera_body(),
-			camera:self.physics.camera(),
-			time:self.timer.time(time),
-		}
-	}
-	pub fn change_map(&mut self,time:SessionTime,map:&strafesnet_common::map::CompleteMap){
-		//dump any pending interpolation state
-		if self.mouse_blocking{
-			self.unblock_mouse(time);
-		}
-		self.empty_queue();
-
-		//doing it like this to avoid doing PhysicsInstruction::ChangeMap(Rc<CompleteMap>)
-		self.physics.generate_models(&map);
-
-		//use the standard input interface so the instructions are written out to bots
-		self.handle_instruction(&TimedInstruction{
-			time,
-			instruction:Instruction::Input(InputInstruction::ResetAndSpawn(
-				strafesnet_common::gameplay_modes::ModeId::MAIN,
-				strafesnet_common::gameplay_modes::StageId::FIRST,
-			)),
-		});
-	}
-	pub const fn user_settings(&self)->&crate::settings::UserSettings{
-		&self.user_settings
-	}
-}
-}
+const SESSION_INSTRUCTION_IDLE:SessionInstruction=SessionInstruction::Input(SessionInputInstruction::Other(strafesnet_common::physics::OtherOtherInstruction::Idle));
 
 pub fn new<'a>(
 	mut graphics_worker:crate::compat_worker::INWorker<'a,crate::graphics_worker::Instruction>,
 	user_settings:crate::settings::UserSettings,
 )->crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTimeInner>>{
 	let physics=crate::physics::PhysicsContext::default();
-	let mut interpolator=MouseInterpolator::new(
-		physics,
-		user_settings
+	let timer=Timer::unpaused(SessionTime::ZERO,PhysicsTime::ZERO);
+	let simulation=Simulation::new(timer,physics);
+	let mut session=Session::new(
+		user_settings,
+		simulation,
 	);
 	crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<Instruction,SessionTimeInner>|{
-		interpolator.handle_instruction(&ins);
+		// excruciating pain
+		macro_rules! run_session_instruction{
+			($time:expr,$instruction:expr)=>{
+				session.process_instruction(TimedInstruction{
+					time:$time,
+					instruction:$instruction,
+				});
+			};
+		}
+		macro_rules! run_graphics_worker_instruction{
+			($instruction:expr)=>{
+				graphics_worker.send($instruction).unwrap();
+			};
+		}
 		match ins.instruction{
+			Instruction::Input(unbuffered_instruction)=>{
+				run_session_instruction!(ins.time,SessionInstruction::Input(unbuffered_instruction));
+			},
+			Instruction::SetPaused(paused)=>{
+				run_session_instruction!(ins.time,SessionInstruction::SetPaused(paused));
+			},
 			Instruction::Render=>{
-				let frame_state=interpolator.get_frame_state(ins.time);
-				graphics_worker.send(crate::graphics_worker::Instruction::Render(frame_state)).unwrap();
+				run_session_instruction!(ins.time,SESSION_INSTRUCTION_IDLE);
+				let frame_state=session.get_frame_state(ins.time);
+				run_graphics_worker_instruction!(GraphicsInstruction::Render(frame_state));
 			},
-			Instruction::Resize(size)=>{
-				graphics_worker.send(crate::graphics_worker::Instruction::Resize(size,interpolator.user_settings().clone())).unwrap();
+			Instruction::Resize(physical_size)=>{
+				run_session_instruction!(ins.time,SESSION_INSTRUCTION_IDLE);
+				let user_settings=session.user_settings().clone();
+				run_graphics_worker_instruction!(GraphicsInstruction::Resize(physical_size,user_settings));
 			},
-			Instruction::ChangeMap(map)=>{
-				interpolator.change_map(ins.time,&map);
-				graphics_worker.send(crate::graphics_worker::Instruction::ChangeMap(map)).unwrap();
+			Instruction::ChangeMap(complete_map)=>{
+				run_session_instruction!(ins.time,SessionInstruction::ChangeMap(&complete_map));
+				run_graphics_worker_instruction!(GraphicsInstruction::ChangeMap(complete_map));
 			},
-			Instruction::Input(_)=>(),
-			Instruction::SetPaused(_)=>(),
 		}
 	})
 }
diff --git a/strafe-client/src/session.rs b/strafe-client/src/session.rs
new file mode 100644
index 00000000..fce84394
--- /dev/null
+++ b/strafe-client/src/session.rs
@@ -0,0 +1,193 @@
+use strafesnet_common::gameplay_modes::{ModeId,StageId};
+use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,InstructionFeedback,TimedInstruction};
+// session represents the non-hardware state of the client.
+// Ideally it is a deterministic state which is atomically updated by instructions, same as the simulation state.
+use strafesnet_common::physics::{
+	ModeInstruction,OtherInstruction,OtherOtherInstruction,
+	Instruction as PhysicsInputInstruction,
+	TimeInner as PhysicsTimeInner,
+	Time as PhysicsTime
+};
+use strafesnet_common::timer::{Scaled,Timer};
+use strafesnet_common::session::{TimeInner as SessionTimeInner,Time as SessionTime};
+
+use crate::mouse_interpolator::{MouseInterpolator,StepInstruction,Instruction as MouseInterpolatorInstruction};
+use crate::settings::UserSettings;
+
+pub enum Instruction<'a>{
+	Input(SessionInputInstruction),
+	SetPaused(bool),
+	ChangeMap(&'a strafesnet_common::map::CompleteMap),
+	//Graphics(crate::graphics_worker::Instruction),
+}
+
+pub enum SessionInputInstruction{
+	Mouse(glam::IVec2),
+	SetControl(strafesnet_common::physics::SetControlInstruction),
+	Mode(ImplicitModeInstruction),
+	Other(strafesnet_common::physics::OtherOtherInstruction),
+}
+/// Implicit mode instruction are fed separately to session.
+/// Session generates the explicit mode instructions interlaced with a SetSensitivity instruction
+#[derive(Clone,Debug)]
+pub enum ImplicitModeInstruction{
+	ResetAndRestart,
+	ResetAndSpawn(strafesnet_common::gameplay_modes::ModeId,strafesnet_common::gameplay_modes::StageId),
+}
+
+pub struct FrameState{
+	pub body:crate::physics::Body,
+	pub camera:crate::physics::PhysicsCamera,
+	pub time:PhysicsTime,
+}
+
+pub struct Simulation{
+	timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
+	physics:crate::physics::PhysicsContext,
+}
+impl Simulation{
+	pub const fn new(
+		timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
+		physics:crate::physics::PhysicsContext,
+	)->Self{
+		Self{
+			timer,
+			physics,
+		}
+	}
+	pub fn get_frame_state(&self,time:SessionTime)->FrameState{
+		FrameState{
+			body:self.physics.camera_body(),
+			camera:self.physics.camera(),
+			time:self.timer.time(time),
+		}
+	}
+}
+
+pub struct Replay{
+	last_instruction_id:usize,
+	instructions:Vec<PhysicsInputInstruction>,
+	simulation:Simulation,
+}
+impl Replay{
+	pub const fn new(
+		instructions:Vec<PhysicsInputInstruction>,
+		simulation:Simulation,
+	)->Self{
+		Self{
+			last_instruction_id:0,
+			instructions,
+			simulation,
+		}
+	}
+}
+
+pub struct Session{
+	user_settings:UserSettings,
+	mouse_interpolator:crate::mouse_interpolator::MouseInterpolator,
+	//gui:GuiState
+	simulation:Simulation,
+	replays:Vec<Replay>,
+}
+impl Session{
+	pub fn new(
+		user_settings:UserSettings,
+		simulation:Simulation,
+	)->Self{
+		Self{
+			user_settings,
+			mouse_interpolator:MouseInterpolator::new(),
+			simulation,
+			replays:Vec::new(),
+		}
+	}
+	fn change_map(&mut self,map:&strafesnet_common::map::CompleteMap){
+		self.simulation.physics.generate_models(map);
+	}
+	pub fn get_frame_state(&self,time:SessionTime)->FrameState{
+		self.simulation.get_frame_state(time)
+	}
+	pub fn user_settings(&self)->&UserSettings{
+		&self.user_settings
+	}
+}
+
+// mouseinterpolator consumes RawInputInstruction
+// mouseinterpolator emits PhysicsInputInstruction
+// mouseinterpolator consumes DoStep to move on to the next emitted instruction
+// Session comsumes SessionInstruction -> forwards RawInputInstruction to mouseinterpolator
+// Session consumes DoStep -> forwards DoStep to mouseinterpolator
+// Session emits DoStep
+
+impl InstructionConsumer<Instruction<'_>> for Session{
+	type TimeInner=SessionTimeInner;
+	fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Self::TimeInner>){
+		macro_rules! run_mouse_interpolator_instruction{
+			($instruction:expr)=>{
+				self.mouse_interpolator.process_instruction(TimedInstruction{
+					time:ins.time,
+					instruction:TimedInstruction{
+						time:self.simulation.timer.time(ins.time),
+						instruction:$instruction,
+					},
+				});
+			};
+		}
+		match ins.instruction{
+			// send it down to MouseInterpolator with two timestamps, SessionTime and PhysicsTime
+			Instruction::Input(SessionInputInstruction::Mouse(pos))=>{
+				run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::MoveMouse(pos));
+			},
+			Instruction::Input(SessionInputInstruction::SetControl(set_control_instruction))=>{
+				run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::SetControl(set_control_instruction)));
+			},
+			Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndRestart))=>{
+				run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Reset)));
+				run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Other(OtherOtherInstruction::SetSensitivity(self.user_settings().calculate_sensitivity()))));
+				run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Restart)));
+			},
+			Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndSpawn(mode_id,spawn_id)))=>{
+				run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Reset)));
+				run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Other(OtherOtherInstruction::SetSensitivity(self.user_settings().calculate_sensitivity()))));
+				run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Spawn(mode_id,spawn_id))));
+			},
+			Instruction::Input(SessionInputInstruction::Other(other_other_instruction))=>{
+				run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Other(other_other_instruction)));
+			},
+			Instruction::SetPaused(paused)=>{
+				// don't flush the buffered instructions in the mouse interpolator
+				// until the mouse is confirmed to be not moving at a later time
+				// what if they pause for 5ms lmao
+				_=self.simulation.timer.set_paused(ins.time,paused);
+			}
+			Instruction::ChangeMap(complete_map)=>{
+				self.change_map(complete_map);
+				// ResetAndSpawn
+				run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Reset)));
+				run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Other(OtherOtherInstruction::SetSensitivity(self.user_settings().calculate_sensitivity()))));
+				run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Spawn(ModeId::MAIN,StageId::FIRST))));
+			},
+		};
+		// run all buffered instruction produced
+		self.process_exhaustive(ins.time);
+	}
+}
+impl InstructionConsumer<StepInstruction> for Session{
+	type TimeInner=SessionTimeInner;
+	fn process_instruction(&mut self,ins:TimedInstruction<StepInstruction,Self::TimeInner>){
+		// ins.time ignored???
+		let ins_retimed=TimedInstruction{
+			time:self.simulation.timer.time(ins.time),
+			instruction:ins.instruction,
+		};
+		if let Some(instruction)=self.mouse_interpolator.pop_buffered_instruction(ins_retimed){
+			self.simulation.physics.run_input_instruction(instruction);
+		}
+	}
+}
+impl InstructionEmitter<StepInstruction> for Session{
+	type TimeInner=SessionTimeInner;
+	fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::TimeInner>>{
+		self.mouse_interpolator.next_instruction(time_limit)
+	}
+}
diff --git a/strafe-client/src/setup.rs b/strafe-client/src/setup.rs
index af81dd7e..3b244c21 100644
--- a/strafe-client/src/setup.rs
+++ b/strafe-client/src/setup.rs
@@ -1,4 +1,4 @@
-use crate::window::WindowInstruction;
+use crate::window::Instruction;
 use strafesnet_common::instruction::TimedInstruction;
 use strafesnet_common::integer;
 use strafesnet_common::session::TimeInner as SessionTimeInner;
@@ -224,7 +224,7 @@ pub fn setup_and_start(title:&str){
 		let path=std::path::PathBuf::from(arg);
 		window_thread.send(TimedInstruction{
 			time:integer::Time::ZERO,
-			instruction:WindowInstruction::WindowEvent(winit::event::WindowEvent::DroppedFile(path)),
+			instruction:Instruction::WindowEvent(winit::event::WindowEvent::DroppedFile(path)),
 		}).unwrap();
 	};
 
@@ -235,7 +235,7 @@ pub fn setup_and_start(title:&str){
 
 fn run_event_loop(
 	event_loop:winit::event_loop::EventLoop<()>,
-	mut window_thread:crate::compat_worker::QNWorker<TimedInstruction<WindowInstruction,SessionTimeInner>>,
+	mut window_thread:crate::compat_worker::QNWorker<TimedInstruction<Instruction,SessionTimeInner>>,
 	root_time:std::time::Instant
 	)->Result<(),winit::error::EventLoopError>{
 		event_loop.run(move |event,elwt|{
@@ -247,7 +247,7 @@ fn run_event_loop(
 			// };
 			match event{
 				winit::event::Event::AboutToWait=>{
-					window_thread.send(TimedInstruction{time,instruction:WindowInstruction::RequestRedraw}).unwrap();
+					window_thread.send(TimedInstruction{time,instruction:Instruction::RequestRedraw}).unwrap();
 				}
 				winit::event::Event::WindowEvent {
 					event:
@@ -259,7 +259,7 @@ fn run_event_loop(
 						winit::event::WindowEvent::Resized(size),//ignoring scale factor changed for now because mutex bruh
 					window_id:_,
 				} => {
-					window_thread.send(TimedInstruction{time,instruction:WindowInstruction::Resize(size)}).unwrap();
+					window_thread.send(TimedInstruction{time,instruction:Instruction::Resize(size)}).unwrap();
 				}
 				winit::event::Event::WindowEvent{event,..}=>match event{
 					winit::event::WindowEvent::KeyboardInput{
@@ -275,17 +275,17 @@ fn run_event_loop(
 						elwt.exit();
 					}
 					winit::event::WindowEvent::RedrawRequested=>{
-						window_thread.send(TimedInstruction{time,instruction:WindowInstruction::Render}).unwrap();
+						window_thread.send(TimedInstruction{time,instruction:Instruction::Render}).unwrap();
 					}
 					_=>{
-						window_thread.send(TimedInstruction{time,instruction:WindowInstruction::WindowEvent(event)}).unwrap();
+						window_thread.send(TimedInstruction{time,instruction:Instruction::WindowEvent(event)}).unwrap();
 					}
 				},
 				winit::event::Event::DeviceEvent{
 					event,
 					..
 				} => {
-					window_thread.send(TimedInstruction{time,instruction:WindowInstruction::DeviceEvent(event)}).unwrap();
+					window_thread.send(TimedInstruction{time,instruction:Instruction::DeviceEvent(event)}).unwrap();
 				},
 				_=>{}
 			}
diff --git a/strafe-client/src/window.rs b/strafe-client/src/window.rs
index 20a90279..941eb719 100644
--- a/strafe-client/src/window.rs
+++ b/strafe-client/src/window.rs
@@ -1,9 +1,10 @@
-use crate::physics_worker::InputInstruction;
-use strafesnet_common::integer;
 use strafesnet_common::instruction::TimedInstruction;
 use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
+use strafesnet_common::physics::{OtherInstruction,OtherOtherInstruction,SetControlInstruction};
+use crate::physics_worker::Instruction as PhysicsWorkerInstruction;
+use crate::session::SessionInputInstruction;
 
-pub enum WindowInstruction{
+pub enum Instruction{
 	Resize(winit::dpi::PhysicalSize<u32>),
 	WindowEvent(winit::event::WindowEvent),
 	DeviceEvent(winit::event::DeviceEvent),
@@ -14,10 +15,10 @@ pub enum WindowInstruction{
 //holds thread handles to dispatch to
 struct WindowContext<'a>{
 	manual_mouse_lock:bool,
-	mouse:strafesnet_common::mouse::MouseState<SessionTimeInner>,//std::sync::Arc<std::sync::Mutex<>>
+	mouse_pos:glam::DVec2,
 	screen_size:glam::UVec2,
 	window:&'a winit::window::Window,
-	physics_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<crate::physics_worker::Instruction,SessionTimeInner>>,
+	physics_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<PhysicsWorkerInstruction,SessionTimeInner>>,
 }
 
 impl WindowContext<'_>{
@@ -28,7 +29,7 @@ impl WindowContext<'_>{
 		match event{
 			winit::event::WindowEvent::DroppedFile(path)=>{
 				match crate::file::load(path.as_path()){
-					Ok(map)=>self.physics_thread.send(TimedInstruction{time,instruction:crate::physics_worker::Instruction::ChangeMap(map)}).unwrap(),
+					Ok(map)=>self.physics_thread.send(TimedInstruction{time,instruction:PhysicsWorkerInstruction::ChangeMap(map)}).unwrap(),
 					Err(e)=>println!("Failed to load map: {e}"),
 				}
 			},
@@ -36,7 +37,7 @@ impl WindowContext<'_>{
 				//pause unpause
 				self.physics_thread.send(TimedInstruction{
 					time,
-					instruction:crate::physics_worker::Instruction::SetPaused(!state),
+					instruction:PhysicsWorkerInstruction::SetPaused(!state),
 				}).unwrap();
 				//recalculate pressed keys on focus
 			},
@@ -91,29 +92,29 @@ impl WindowContext<'_>{
 					},
 					(keycode,state)=>{
 						let s=state.is_pressed();
-						if let Some(input_instruction)=match keycode{
-							winit::keyboard::Key::Named(winit::keyboard::NamedKey::Space)=>Some(InputInstruction::Jump(s)),
+						if let Some(session_input_instruction)=match keycode{
+							winit::keyboard::Key::Named(winit::keyboard::NamedKey::Space)=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetJump(s))),
 							winit::keyboard::Key::Character(key)=>match key.as_str(){
-								"W"|"w"=>Some(InputInstruction::MoveForward(s)),
-								"A"|"a"=>Some(InputInstruction::MoveLeft(s)),
-								"S"|"s"=>Some(InputInstruction::MoveBack(s)),
-								"D"|"d"=>Some(InputInstruction::MoveRight(s)),
-								"E"|"e"=>Some(InputInstruction::MoveUp(s)),
-								"Q"|"q"=>Some(InputInstruction::MoveDown(s)),
-								"Z"|"z"=>Some(InputInstruction::Zoom(s)),
-								"R"|"r"=>if s{
+								"W"|"w"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveForward(s))),
+								"A"|"a"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveLeft(s))),
+								"S"|"s"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveBack(s))),
+								"D"|"d"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveRight(s))),
+								"E"|"e"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveUp(s))),
+								"Q"|"q"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveDown(s))),
+								"Z"|"z"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetZoom(s))),
+								"R"|"r"=>s.then(||{
 									//mouse needs to be reset since the position is absolute
-									self.mouse=strafesnet_common::mouse::MouseState::default();
-									Some(InputInstruction::ResetAndRestart)
-								}else{None},
-								"F"|"f"=>if s{Some(InputInstruction::PracticeFly)}else{None},
+									self.mouse_pos=glam::DVec2::ZERO;
+									SessionInputInstruction::Mode(crate::session::ImplicitModeInstruction::ResetAndRestart)
+								}),
+								"F"|"f"=>s.then_some(SessionInputInstruction::Other(OtherOtherInstruction::PracticeFly)),
 								_=>None,
 							},
 							_=>None,
 						}{
 							self.physics_thread.send(TimedInstruction{
 								time,
-								instruction:crate::physics_worker::Instruction::Input(input_instruction),
+								instruction:PhysicsWorkerInstruction::Input(session_input_instruction),
 							}).unwrap();
 						}
 					},
@@ -126,7 +127,7 @@ impl WindowContext<'_>{
 	fn device_event(&mut self,time:SessionTime,event: winit::event::DeviceEvent){
 		match event{
 			winit::event::DeviceEvent::MouseMotion{
-			    delta,//these (f64,f64) are integers on my machine
+			    delta,
 			}=>{
 				if self.manual_mouse_lock{
 					match self.window.set_cursor_position(self.get_middle_of_screen()){
@@ -134,14 +135,10 @@ impl WindowContext<'_>{
 						Err(e)=>println!("Could not set cursor position: {:?}",e),
 					}
 				}
-				//do not step the physics because the mouse polling rate is higher than the physics can run.
-				//essentially the previous input will be overwritten until a true step runs
-				//which is fine because they run all the time.
-				let delta=glam::ivec2(delta.0 as i32,delta.1 as i32);
-				self.mouse.pos+=delta;
+				self.mouse_pos+=glam::dvec2(delta.0,delta.1);
 				self.physics_thread.send(TimedInstruction{
 					time,
-					instruction:crate::physics_worker::Instruction::Input(InputInstruction::MoveMouse(self.mouse.pos)),
+					instruction:PhysicsWorkerInstruction::Input(SessionInputInstruction::Mouse(self.mouse_pos.as_ivec2())),
 				}).unwrap();
 			},
 			winit::event::DeviceEvent::MouseWheel {
@@ -151,7 +148,7 @@ impl WindowContext<'_>{
 				if false{//self.physics.style.use_scroll{
 					self.physics_thread.send(TimedInstruction{
 						time,
-						instruction:crate::physics_worker::Instruction::Input(InputInstruction::Jump(true)),//activates the immediate jump path, but the style modifier prevents controls&CONTROL_JUMP bit from being set to auto jump
+						instruction:PhysicsWorkerInstruction::Input(SessionInputInstruction::SetControl(SetControlInstruction::SetJump(true))),//activates the immediate jump path, but the style modifier prevents controls&CONTROL_JUMP bit from being set to auto jump
 					}).unwrap();
 				}
 			},
@@ -162,7 +159,7 @@ impl WindowContext<'_>{
 pub fn worker<'a>(
 	window:&'a winit::window::Window,
 	setup_context:crate::setup::SetupContext<'a>,
-)->crate::compat_worker::QNWorker<'a,TimedInstruction<WindowInstruction,SessionTimeInner>>{
+)->crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTimeInner>>{
 	// WindowContextSetup::new
 	let user_settings=crate::settings::read_user_settings();
 
@@ -174,7 +171,7 @@ pub fn worker<'a>(
 	let graphics_thread=crate::graphics_worker::new(graphics,setup_context.config,setup_context.surface,setup_context.device,setup_context.queue);
 	let mut window_context=WindowContext{
 		manual_mouse_lock:false,
-		mouse:strafesnet_common::mouse::MouseState::default(),
+		mouse_pos:glam::DVec2::ZERO,
 		//make sure to update this!!!!!
 		screen_size,
 		window,
@@ -185,30 +182,30 @@ pub fn worker<'a>(
 	};
 
 	//WindowContextSetup::into_worker
-	crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<WindowInstruction,SessionTimeInner>|{
+	crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<Instruction,SessionTimeInner>|{
 		match ins.instruction{
-			WindowInstruction::RequestRedraw=>{
+			Instruction::RequestRedraw=>{
 				window_context.window.request_redraw();
 			}
-			WindowInstruction::WindowEvent(window_event)=>{
+			Instruction::WindowEvent(window_event)=>{
 				window_context.window_event(ins.time,window_event);
 			},
-			WindowInstruction::DeviceEvent(device_event)=>{
+			Instruction::DeviceEvent(device_event)=>{
 				window_context.device_event(ins.time,device_event);
 			},
-			WindowInstruction::Resize(size)=>{
+			Instruction::Resize(size)=>{
 				window_context.physics_thread.send(
 					TimedInstruction{
 						time:ins.time,
-						instruction:crate::physics_worker::Instruction::Resize(size)
+						instruction:PhysicsWorkerInstruction::Resize(size)
 					}
 				).unwrap();
 			}
-			WindowInstruction::Render=>{
+			Instruction::Render=>{
 				window_context.physics_thread.send(
 					TimedInstruction{
 						time:ins.time,
-						instruction:crate::physics_worker::Instruction::Render
+						instruction:PhysicsWorkerInstruction::Render
 					}
 				).unwrap();
 			}
diff --git a/strafe-client/src/worker.rs b/strafe-client/src/worker.rs
index ada128a5..52bb3516 100644
--- a/strafe-client/src/worker.rs
+++ b/strafe-client/src/worker.rs
@@ -190,7 +190,7 @@ mod test{
 		for _ in 0..5 {
 			let task = instruction::TimedInstruction{
 				time:strafesnet_common::physics::Time::ZERO,
-				instruction:strafesnet_common::physics::Instruction::Idle,
+				instruction:strafesnet_common::physics::Instruction::IDLE,
 			};
 			worker.send(task).unwrap();
 		}
@@ -204,7 +204,7 @@ mod test{
 		// Send a new task
 		let task = instruction::TimedInstruction{
 			time:integer::Time::ZERO,
-			instruction:strafesnet_common::physics::Instruction::Idle,
+			instruction:strafesnet_common::physics::Instruction::IDLE,
 		};
 		worker.send(task).unwrap();