diff --git a/CMakeLists.txt b/CMakeLists.txt
index 18f5917437ddfec06e7054e91747fd66e8ef8757..e1527a8acd015982bf54426d5818d02f8bef4972 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -27,6 +27,7 @@ if (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
                 glm/0.9.9.8
                 spdlog/1.10.0
                 freetype/2.11.1
+                bullet3/3.22a
             GENERATORS 
                 cmake_find_package
                 cmake_find_package_multi
diff --git a/demo/demo/main.cpp b/demo/demo/main.cpp
index 8d025d887e738569ada36e6bd9b4e755ef2c34a8..45846b301cff1f443d9ba2641711d985ea8a16f5 100644
--- a/demo/demo/main.cpp
+++ b/demo/demo/main.cpp
@@ -33,6 +33,7 @@
 #include "fggl/util/service.h"
 
 #include "fggl/ecs3/types.hpp"
+#include "fggl/phys/bullet/bullet.hpp"
 
 #include "fggl/scenes/menu.hpp"
 
@@ -98,6 +99,7 @@ int main(int argc, const char* argv[]) {
 	// load a bunch of modules to provide game functionality
 	//app.use<fggl::ecs3::ecsTypes>();
 	app.use<fggl::gfx::SceneUtils>();
+	app.use<fggl::phys::Bullet3>();
 
 	test_atlas_api();
 
diff --git a/demo/demo/rollball.cpp b/demo/demo/rollball.cpp
index 49396f66f75cf008b3edee10b8f666c4cefa13b5..4146ccd4d077fdb432a07d6a78c8376e6c9cba35 100644
--- a/demo/demo/rollball.cpp
+++ b/demo/demo/rollball.cpp
@@ -67,6 +67,9 @@ static void setupPrefabs(fggl::ecs3::World& world, Prefabs& prefabs) {
 		prefabs.floor = world.create(true);
 		world.add<fggl::math::Transform>(prefabs.floor);
 
+		auto* rb = world.add<fggl::phys::RigidBody>(prefabs.floor);
+		rb->mass = fggl::phys::STATIC_MASS;
+
 		fggl::data::StaticMesh meshComponent;
 		meshComponent.pipeline = "phong";
 
@@ -82,6 +85,7 @@ static void setupPrefabs(fggl::ecs3::World& world, Prefabs& prefabs) {
 		// player (cube because my sphere function doesn't exist yet
 		prefabs.player = world.create(true);
 		world.add<fggl::math::Transform>(prefabs.player);
+		world.add<fggl::phys::RigidBody>(prefabs.player);
 
 		fggl::data::StaticMesh meshComponent;
 		meshComponent.pipeline = "phong";
diff --git a/fggl/CMakeLists.txt b/fggl/CMakeLists.txt
index 486ce219868e8072b58cd78e7df3d4eb3e32de97..0d344924a1dfc983a20ac90e5d35f6a7c5df987b 100644
--- a/fggl/CMakeLists.txt
+++ b/fggl/CMakeLists.txt
@@ -70,6 +70,9 @@ target_link_libraries(${PROJECT_NAME} PUBLIC freetype)
 # Graphics backend
 add_subdirectory(gfx)
 
+# physics integration
+add_subdirectory(phys/bullet)
+
 # Debug backend
 add_subdirectory(debug)
 
diff --git a/fggl/data/heightmap.cpp b/fggl/data/heightmap.cpp
index 72b9b7833c532679eaf893711b56eaf77295278a..525e886d02a9e6996e16ad27bdd1db6a93dc7c9a 100644
--- a/fggl/data/heightmap.cpp
+++ b/fggl/data/heightmap.cpp
@@ -99,7 +99,7 @@ namespace fggl::data {
         for (std::size_t x = 0; x < data::heightMaxX - 1; x++) {
             for (std::size_t z = 0; z < data::heightMaxZ; z++) {
                 for (int k=0; k < 2; k++) {
-                    int idx = (x+k) * data::heightMaxZ + z;
+                    auto idx = (x+k) * data::heightMaxZ + z;
                     mesh.pushIndex(idx);
                 }
             }
diff --git a/fggl/phys/bullet/CMakeLists.txt b/fggl/phys/bullet/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..54d35e509bfac7bcc27633afad4d22c2e8317f14
--- /dev/null
+++ b/fggl/phys/bullet/CMakeLists.txt
@@ -0,0 +1,11 @@
+# bullet integration support
+
+find_package( Bullet REQUIRED )
+target_link_libraries(fggl PUBLIC Bullet::Bullet )
+
+#
+target_sources(fggl
+        PRIVATE
+            simulation.cpp
+)
+
diff --git a/fggl/phys/bullet/simulation.cpp b/fggl/phys/bullet/simulation.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bfd1f1e97ee2ab0ae09048f415349be58503e060
--- /dev/null
+++ b/fggl/phys/bullet/simulation.cpp
@@ -0,0 +1,121 @@
+/*
+ * ${license.title}
+ * Copyright (C) 2022 ${license.owner}
+ * ${license.mailto}
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "fggl/phys/bullet/types.hpp"
+
+namespace fggl::phys::bullet {
+
+	BulletPhysicsEngine::BulletPhysicsEngine(ecs3::World* world) : m_ecs(world), m_config(), m_world(nullptr) {
+		m_config.broadphase = new btDbvtBroadphase();
+		m_config.collisionConfiguration = new btDefaultCollisionConfiguration();
+		m_config.dispatcher = new btCollisionDispatcher(m_config.collisionConfiguration);
+		m_config.solver = new btSequentialImpulseConstraintSolver();
+
+		m_world = new btDiscreteDynamicsWorld(
+			m_config.dispatcher,
+			m_config.broadphase,
+			m_config.solver,
+			m_config.collisionConfiguration);
+	}
+
+	void BulletPhysicsEngine::step() {
+		checkForPhys();
+		m_world->stepSimulation(60.0f);
+		syncToECS();
+	}
+
+	static void build_motation_state(math::Transform* myState, BulletBody* btState) {
+		auto myPos = myState->origin();
+		btVector3 position{myPos.x, myPos.y, myPos.z};
+
+		auto myRot = myState->euler();
+		btQuaternion rotation;
+		rotation.setEulerZYX(myRot.x, myRot.y, myRot.z);
+		btState->motion = new btDefaultMotionState(btTransform(rotation, position));
+	}
+
+	void BulletPhysicsEngine::checkForPhys() {
+		// FIXME without reactive-based approaches this is very slow
+		auto entsWantPhys = m_ecs->findMatching<phys::RigidBody>();
+		for (auto ent : entsWantPhys) {
+			auto* btComp = m_ecs->get<BulletBody>(ent);
+			if ( btComp != nullptr ) {
+				// they are already in the simluation
+				continue;
+			}
+
+			btComp = m_ecs->add<BulletBody>(ent);
+			auto fgBody = m_ecs->get<phys::RigidBody>(ent);
+
+			// setup the starting motion state
+			auto* transform = m_ecs->get<math::Transform>(ent);
+			build_motation_state(transform, btComp);
+
+			// setup the construction information
+			// FIXME the world shouldn't be made only of unit spheres...
+			btSphereShape* shape = new btSphereShape(0.5f);
+			btRigidBody::btRigidBodyConstructionInfo bodyCI(
+				fgBody->mass,
+				btComp->motion,
+				shape
+			);
+			btComp->body = new btRigidBody(bodyCI);
+			m_world->addRigidBody(btComp->body);
+		}
+	}
+
+	void BulletPhysicsEngine::syncToECS() {
+		auto physEnts = m_ecs->findMatching<BulletBody>();
+		for (auto& ent : physEnts) {
+			auto* transform = m_ecs->get<math::Transform>(ent);
+			auto* physBody = m_ecs->get<BulletBody>(ent);
+
+			btTransform bTransform;
+			physBody->motion->getWorldTransform(bTransform);
+
+			// set calculate and set ecs position
+			auto& btPos = bTransform.getOrigin();
+			math::vec3 pos{btPos.x(), btPos.y(), btPos.z()};
+			transform->origin( pos );
+		}
+	}
+
+	BulletPhysicsEngine::~BulletPhysicsEngine() {
+		// clean up the rigid bodies
+		auto entRB = m_ecs->findMatching<BulletBody>();
+		for (auto& ent : entRB) {
+			auto* bulletBody = m_ecs->get<BulletBody>(ent);
+			delete bulletBody->motion;
+			m_world->removeCollisionObject(bulletBody->body);
+			delete bulletBody->body;
+			m_ecs->remove<BulletBody>(ent);
+		}
+
+		// delete the world
+		delete m_world;
+
+		// cleanup the configuration object
+		delete m_config.solver;
+		delete m_config.broadphase;
+		delete m_config.dispatcher;
+		delete m_config.collisionConfiguration;
+	}
+
+} // namespace fggl::phys::bullet
\ No newline at end of file
diff --git a/fggl/scenes/game.cpp b/fggl/scenes/game.cpp
index 623247d5c785fc571712b8b0d73d4c9cc56c5d64..0cf1717dc07eaba662488b51af3f3c515327400c 100644
--- a/fggl/scenes/game.cpp
+++ b/fggl/scenes/game.cpp
@@ -24,6 +24,7 @@
 
 #include "fggl/scenes/game.hpp"
 #include "fggl/util/service.h"
+#include "fggl/phys/bullet/types.hpp"
 
 namespace fggl::scenes {
 
@@ -36,10 +37,12 @@ namespace fggl::scenes {
 		fggl::AppState::activate();
 
 		// setup the scene
-		m_world = std::make_unique<fggl::ecs3::World>(*m_owner.registry());
+		m_world = std::make_unique<ecs3::World>(*m_owner.registry());
+		m_phys = std::make_unique<phys::bullet::BulletPhysicsEngine>(m_world.get());
 	}
 
 	void Game::deactivate() {
+		m_phys.reset();
 		m_world.reset();
 	}
 
@@ -50,6 +53,10 @@ namespace fggl::scenes {
 				m_owner.change_state(m_previous);
 			}
 		}
+
+		if ( m_phys != nullptr ) {
+			m_phys->step();
+		}
 	}
 
 	void Game::render(fggl::gfx::Graphics &gfx) {
diff --git a/include/fggl/ecs3/prototype/world.h b/include/fggl/ecs3/prototype/world.h
index 26dba7ae9bebfafeec6a73b14f1e5da8f6ffd863..75969c3b6500a4247cd495d6fead468226e552dc 100644
--- a/include/fggl/ecs3/prototype/world.h
+++ b/include/fggl/ecs3/prototype/world.h
@@ -59,6 +59,11 @@ namespace fggl::ecs3::prototype {
 				return (C *) ptr;
 			}
 
+			template<typename C>
+			void remove(){
+				m_components.erase(Component<C>::typeID());
+			}
+
 			inline void *get(component_type_t t) {
 				return m_components.at(t);
 			}
@@ -215,6 +220,20 @@ namespace fggl::ecs3::prototype {
 				}
 			}
 
+			template<typename C>
+			void remove(entity_t entity_id) {
+				try {
+					auto &entity = m_entities.at(entity_id);
+					try {
+						return entity.remove<C>();
+					} catch ( std::out_of_range& e ) {
+						std::cerr << "entity " << entity_id << " does not have component "<< C::name << std::endl;
+					}
+				} catch ( std::out_of_range& e) {
+					std::cerr << "tried to delete component on entity that didn't exist, entity was: " << entity_id << std::endl;
+				}
+			}
+
 			void *get(entity_t entity_id, component_type_t t) {
 				auto &entity = m_entities.at(entity_id);
 				return entity.get(t);
diff --git a/include/fggl/ecs3/types.hpp b/include/fggl/ecs3/types.hpp
index 35b02f0112581818fa19c7e371b36b2d02af27eb..4a7ffc33c819382ee4a9ce7f6c79f3bc008995be 100644
--- a/include/fggl/ecs3/types.hpp
+++ b/include/fggl/ecs3/types.hpp
@@ -176,7 +176,12 @@ namespace fggl::ecs3 {
 			}
 
 			inline std::shared_ptr<fggl::ecs::ComponentBase> meta(component_type_t type_id) const {
-				return m_types.at(type_id);
+				try {
+					return m_types.at(type_id);
+				} catch (std::out_of_range& err) {
+					std::cerr << "asked for metadata on type " << type_id << " but no such type is in the type system" << std::endl;
+					return nullptr;
+				}
 			}
 
 			inline component_type_t find(const char *name) const {
diff --git a/include/fggl/phys/bullet/bullet.hpp b/include/fggl/phys/bullet/bullet.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..e5d301f4b2282dfeaec043d3f703623e42303ec1
--- /dev/null
+++ b/include/fggl/phys/bullet/bullet.hpp
@@ -0,0 +1,70 @@
+/*
+ * ${license.title}
+ * Copyright (C) 2022 ${license.owner}
+ * ${license.mailto}
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+//
+// Created by webpigeon on 24/04/22.
+//
+
+#ifndef FGGL_PHYS_BULLET_BULLET_HPP
+#define FGGL_PHYS_BULLET_BULLET_HPP
+
+#include "fggl/ecs3/module/module.h"
+
+#include "fggl/phys/types.hpp"
+#include "fggl/phys/bullet/types.hpp"
+
+namespace fggl::phys::bullet {
+
+	/**
+	 * Bullet integration module.
+	 *
+	 * This provides the ability for FGGL to use bullet as a physics backened. Bullet is mature, stable physics
+	 * engine which makes it suitable for most use cases. For use with FGGL there is a reasonable amount of copying
+	 * back and forth to keep the two states in sync.
+	 */
+	struct BulletModule : ecs3::Module {
+		public:
+			BulletModule() = default;
+
+			[[nodiscard]]
+			std::string name() const override {
+				return "phys::Bullet3";
+			}
+
+			void onLoad(ecs3::ModuleManager &manager, ecs3::TypeRegistry &types) override {
+				// dependencies
+				types.make<phys::RigidBody>();
+
+				// my types
+				types.make<phys::bullet::BulletBody>();
+			}
+
+	};
+
+} // namespace fggl::phys::bullet
+
+namespace fggl::phys {
+
+	// allows using fggl::phys::Bullet3 as the module name
+	using Bullet3 = bullet::BulletModule;
+
+} // namespace fggl::phys
+
+#endif //FGGL_PHYS_BULLET_BULLET_HPP
diff --git a/include/fggl/phys/bullet/types.hpp b/include/fggl/phys/bullet/types.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..31d9a94491aaf872497590862d62065763d57727
--- /dev/null
+++ b/include/fggl/phys/bullet/types.hpp
@@ -0,0 +1,82 @@
+/*
+ * ${license.title}
+ * Copyright (C) 2022 ${license.owner}
+ * ${license.mailto}
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+//
+// Created by webpigeon on 24/04/22.
+//
+
+#ifndef FGGL_PHYS_BULLET_TYPES_HPP
+#define FGGL_PHYS_BULLET_TYPES_HPP
+
+#include "fggl/ecs3/ecs.hpp"
+#include "fggl/phys/types.hpp"
+
+#include <bullet/btBulletDynamicsCommon.h>
+#include <bullet/btBulletCollisionCommon.h>
+
+namespace fggl::phys::bullet {
+
+	struct BulletConfiguration {
+		btBroadphaseInterface* broadphase;
+		btCollisionConfiguration* collisionConfiguration;
+		btDispatcher* dispatcher;
+		btConstraintSolver* solver;
+	};
+
+	/**
+	 * Bullet component.
+	 */
+	struct BulletBody {
+		constexpr static const char* name = "phys::bullet::body";
+		btMotionState* motion;
+		btRigidBody* body;
+	};
+
+	/**
+	 * Bullet physics engine implementation.
+	 *
+	 * This wraps the bullet physics world and provides the systems needed for integrating it into our ECS. The state
+	 * is responsible for creating and manging this object.
+	 */
+	class BulletPhysicsEngine : public PhysicsEngine {
+		public:
+			explicit BulletPhysicsEngine(ecs3::World* world);
+			~BulletPhysicsEngine() override;
+
+			void step() override;
+		private:
+			ecs3::World* m_ecs;
+			BulletConfiguration m_config;
+			btDiscreteDynamicsWorld* m_world;
+
+			/**
+			 * Check for ECS components which aren't in the physics world and add them.
+			 */
+			void checkForPhys();
+
+			/**
+			 * Sync the bullet world state back to the ECS.
+			 */
+			void syncToECS();
+	};
+
+} // namespace fggl::phys::bullet
+
+#endif //FGGL_PHYS_BULLET_TYPES_HPP
diff --git a/include/fggl/phys/types.hpp b/include/fggl/phys/types.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..8ae73b688b7219e8b5f69c6a12bf547d7654f681
--- /dev/null
+++ b/include/fggl/phys/types.hpp
@@ -0,0 +1,52 @@
+/*
+ * ${license.title}
+ * Copyright (C) 2022 ${license.owner}
+ * ${license.mailto}
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+//
+// Created by webpigeon on 24/04/22.
+//
+
+#ifndef FGGL_PHYS_TYPES_HPP
+#define FGGL_PHYS_TYPES_HPP
+
+namespace fggl::phys {
+	constexpr float STATIC_MASS = 0.0F;
+
+	struct RigidBody {
+		constexpr static const char* name = "phys::Body";
+		float mass = 1.0F;
+	};
+
+	class PhysicsEngine {
+		public:
+			PhysicsEngine() = default;
+			virtual ~PhysicsEngine() = default;
+
+			// no copy and no move
+			PhysicsEngine(PhysicsEngine&) = delete;
+			PhysicsEngine(PhysicsEngine&&) = delete;
+			PhysicsEngine& operator=(PhysicsEngine&) = delete;
+			PhysicsEngine& operator=(PhysicsEngine&&) = delete;
+
+			virtual void step() = 0;
+	};
+
+} // namespace fggl::phys
+
+#endif //FGGL_PHYS_TYPES_HPP
diff --git a/include/fggl/scenes/game.hpp b/include/fggl/scenes/game.hpp
index fa9cafce48ffa191766c3bc8cfa96e47d8ef9930..509388f6fa88aae0d4914e7d256aa565789986fd 100644
--- a/include/fggl/scenes/game.hpp
+++ b/include/fggl/scenes/game.hpp
@@ -26,6 +26,7 @@
 #define FGGL_SCENES_GAME_HPP
 
 #include "fggl/app.hpp"
+#include "fggl/phys/types.hpp"
 
 namespace fggl::scenes {
 
@@ -52,6 +53,7 @@ namespace fggl::scenes {
 		private:
 			std::shared_ptr<input::Input> m_input;
 			std::unique_ptr<ecs3::World> m_world;
+			std::unique_ptr<phys::PhysicsEngine> m_phys;
 			std::string m_previous = "menu";
 	};