diff --git a/game_core/src/debug/mod.rs b/game_core/src/debug/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..81957b18619cec9a4b05c7057c7c52e2d6a9a016
--- /dev/null
+++ b/game_core/src/debug/mod.rs
@@ -0,0 +1,20 @@
+mod _plugin {
+	use crate::system::AppState;
+	use bevy::prelude::*;
+	use bevy_rapier2d::prelude::*;
+
+	pub struct DebugPlugin;
+	impl Plugin for DebugPlugin {
+		fn build(&self, app: &mut App) {
+			app.add_systems(OnEnter(AppState::InGame), |mut commands: Commands| {
+				commands.spawn((
+					TransformBundle::from_transform(Transform::from_xyz(0.0, -400.0, 0.0)),
+					RigidBody::Fixed,
+					Collider::cuboid(200.0, 50.0),
+				));
+			});
+		}
+	}
+}
+
+pub use _plugin::DebugPlugin;
diff --git a/game_core/src/entities/mod.rs b/game_core/src/entities/mod.rs
index a25fe93b22ad8b77f7d89c34f4ee3c0e85024026..e48802a3597b3dc6e68860ceaea9ad93df362263 100644
--- a/game_core/src/entities/mod.rs
+++ b/game_core/src/entities/mod.rs
@@ -1,3 +1,4 @@
+mod physics;
 mod player;
 
 mod _plugin {
@@ -7,9 +8,12 @@ mod _plugin {
 	pub struct EntityPluginSet;
 	impl PluginGroup for EntityPluginSet {
 		fn build(self) -> PluginGroupBuilder {
-			PluginGroupBuilder::start::<Self>().add(super::player::PlayerSetupPlugin)
+			PluginGroupBuilder::start::<Self>()
+				.add(super::player::PlayerSetupPlugin)
+				.add(super::physics::PhysicsPlugin)
 		}
 	}
 }
 
 pub use _plugin::EntityPluginSet;
+pub use physics::{Acceleration, PhysicsLimits, PhysicsSet, Velocity, GRAVITY_ACC, GRAVITY_VEC};
diff --git a/game_core/src/entities/physics.rs b/game_core/src/entities/physics.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c862b46e1dca3c87e8c35d5f32bd449ad22abff1
--- /dev/null
+++ b/game_core/src/entities/physics.rs
@@ -0,0 +1,83 @@
+use crate::system::run_in_game;
+use bevy::prelude::*;
+use bevy_rapier2d::prelude::KinematicCharacterController;
+
+pub const GRAVITY_ACC: f32 = -384.0;
+pub const GRAVITY_VEC: Vec2 = Vec2::new(0.0, GRAVITY_ACC);
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, SystemSet)]
+pub struct PhysicsSet;
+
+#[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut)]
+pub struct Velocity(Vec2);
+
+#[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut)]
+pub struct Acceleration(Vec2);
+
+#[derive(Component, Clone, Copy, Debug, Default)]
+pub struct PhysicsLimits {
+	pub velocity: (Option<Vec2>, Option<Vec2>),
+	pub acceleration: (Option<Vec2>, Option<Vec2>),
+}
+
+impl PhysicsLimits {
+	pub fn limit_velocity(&self, velocity: Vec2) -> Vec2 {
+		match self.velocity {
+			(None, None) => velocity,
+			(Some(min), None) => velocity.max(min),
+			(None, Some(max)) => velocity.min(max),
+			(Some(min), Some(max)) => velocity.min(max).max(min),
+		}
+	}
+	pub fn limit_acceleration(&self, acceleration: Vec2) -> Vec2 {
+		match self.acceleration {
+			(None, None) => acceleration,
+			(Some(min), None) => acceleration.max(min),
+			(None, Some(max)) => acceleration.min(max),
+			(Some(min), Some(max)) => acceleration.min(max).max(min),
+		}
+	}
+}
+
+impl Acceleration {
+	pub fn gravity() -> Self {
+		Acceleration(GRAVITY_VEC)
+	}
+}
+
+fn apply_acceleration(
+	time: Res<Time>,
+	mut query: Query<(&mut Velocity, &Acceleration, Option<&PhysicsLimits>)>,
+) {
+	let dt = time.delta_seconds();
+	for (mut velocity, acceleration, limits) in &mut query {
+		**velocity += **acceleration * dt;
+		**velocity = limits
+			.copied()
+			.unwrap_or_default()
+			.limit_velocity(**velocity);
+	}
+}
+
+fn apply_velocity_to_kinematics(
+	time: Res<Time>,
+	mut query: Query<(&mut KinematicCharacterController, &Velocity)>,
+) {
+	let dt = time.delta_seconds();
+	for (mut controller, velocity) in &mut query {
+		controller.translation = Some(**velocity * dt);
+	}
+}
+
+pub struct PhysicsPlugin;
+impl Plugin for PhysicsPlugin {
+	fn build(&self, app: &mut App) {
+		app.add_systems(
+			Update,
+			(apply_acceleration, apply_velocity_to_kinematics)
+				.chain()
+				.in_set(PhysicsSet)
+				.run_if(run_in_game),
+		);
+	}
+}
diff --git a/game_core/src/entities/player.rs b/game_core/src/entities/player.rs
index 0d81e7daed8d47398cbf8b79d00df82e72a61644..3a32b2d6eff8bc1607b2336f0c12eafee60e6250 100644
--- a/game_core/src/entities/player.rs
+++ b/game_core/src/entities/player.rs
@@ -2,25 +2,41 @@ use crate::assets::AssetHandles;
 use crate::system::AppState;
 use bevy::prelude::*;
 
+use crate::entities::{Acceleration, PhysicsLimits, Velocity, GRAVITY_ACC};
 use bevy_rapier2d::prelude::*;
 
+#[derive(Component, Clone, Copy, Debug)]
+pub struct Player;
+
 pub fn spawn_player(mut commands: Commands, assets: Res<AssetHandles>) {
-	commands
-		.spawn((SpriteSheetBundle {
+	commands.spawn((
+		Player,
+		SpriteSheetBundle {
 			sprite: TextureAtlasSprite::new(3),
 			texture_atlas: assets.atlas("beige_blob"),
 			..Default::default()
-		},))
-		.with_children(|builder| {
-			builder.spawn((
-				TransformBundle::from_transform(Transform::from_xyz(0.0, -32.0, 0.0)),
+		},
+		RigidBody::KinematicPositionBased,
+		KinematicCharacterController {
+			custom_shape: Some((
 				Collider::capsule(Vec2::new(0.0, 8.0), Vec2::new(0.0, -48.0), 48.0),
-				KinematicCharacterController {
-					offset: CharacterLength::Absolute(0.01),
-					..Default::default()
-				},
-			));
-		});
+				Vec2::new(0.0, -32.0),
+				0.0,
+			)),
+			snap_to_ground: Some(CharacterLength::Absolute(0.5)),
+			offset: CharacterLength::Absolute(0.03),
+			..Default::default()
+		},
+		Acceleration::gravity(),
+		Velocity::default(),
+		PhysicsLimits {
+			velocity: (
+				Some(Vec2::new(f32::NEG_INFINITY, GRAVITY_ACC)),
+				Some(Vec2::new(f32::INFINITY, (-GRAVITY_ACC) * 2.0)),
+			),
+			acceleration: Default::default(),
+		},
+	));
 }
 
 pub struct PlayerSetupPlugin;
diff --git a/game_core/src/lib.rs b/game_core/src/lib.rs
index 371b2e70e463b79c32995cdb7f32695a4f3fe76a..24db2312511538727a30c6c224c78a429b18b228 100644
--- a/game_core/src/lib.rs
+++ b/game_core/src/lib.rs
@@ -1,3 +1,4 @@
 pub mod assets;
+pub mod debug;
 pub mod entities;
 pub mod system;
diff --git a/game_core/src/main.rs b/game_core/src/main.rs
index ef41de509ec2a84276b781447bf48a85c3a0162e..acc48ff9faf53843b0732ccb07ba957d2bd57a67 100644
--- a/game_core/src/main.rs
+++ b/game_core/src/main.rs
@@ -5,6 +5,7 @@ fn main() {
 		.add_plugins(game_core::assets::AssetLoadingPlugin)
 		.add_plugins(game_core::system::SystemPluginSet)
 		.add_plugins(game_core::entities::EntityPluginSet)
+		.add_plugins(game_core::debug::DebugPlugin)
 		.add_plugins(bevy_rapier2d::plugin::RapierPhysicsPlugin::<()>::pixels_per_meter(128.0))
 		.add_plugins(micro_banimate::BanimatePluginGroup)
 		.add_plugins(micro_musicbox::CombinedAudioPlugins::<
diff --git a/game_core/src/system/mod.rs b/game_core/src/system/mod.rs
index f8823a1a35381351ddc35538371c36968fb300c2..0cf6bb6dd0f72488ba6b0fb99d5326e15e074747 100644
--- a/game_core/src/system/mod.rs
+++ b/game_core/src/system/mod.rs
@@ -17,6 +17,7 @@ mod _plugin {
 		pub struct PhysDebugPlugin;
 		impl Plugin for PhysDebugPlugin {
 			fn build(&self, app: &mut App) {
+				info!("Including Debug Physics Plugin");
 				app.add_plugins(bevy_rapier2d::render::RapierDebugRenderPlugin {
 					mode: DebugRenderMode::all(),
 					style: DebugRenderStyle::default(),
@@ -36,6 +37,7 @@ mod _plugin {
 
 			#[cfg(feature = "phys-debug")]
 			{
+				info!("Rendering physics debug information");
 				plugins.add(phys_debug::PhysDebugPlugin)
 			}