diff --git a/CMakeLists.txt b/CMakeLists.txt
index a3c43afa8408714259ec920c47f0c70b913604d2..181ae3f6bba89fcf9f6298b7ed6fbd16f89ce774 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -29,6 +29,7 @@ if (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
                 freetype/2.11.1
                 bullet3/3.22a
                 openal/1.21.1
+                yaml-cpp/0.7.0
             GENERATORS 
                 cmake_find_package
                 cmake_find_package_multi
diff --git a/demo/data/rollball.yml b/demo/data/rollball.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0a58e7fdf0dc848b9c31cc057987ed669b6d0279
--- /dev/null
+++ b/demo/data/rollball.yml
@@ -0,0 +1,64 @@
+---
+prefabs:
+  - name: "wallX"
+    components:
+      Transform:
+      StaticMesh:
+        pipeline: phong
+        shape:
+          type: cube
+          scale: [1.0, 5.0, 41]
+      phys::Body:
+        type: static
+        shape:
+          type: box
+          extents: [0.5, 2.5, 20.5]
+  # Wall Z shorter to avoid z-fighting
+  - name: "wallZ"
+    components:
+      Transform:
+      StaticMesh:
+        pipeline: phong
+        shape:
+          type: cube
+          scale: [39, 5, 1]
+      phys::Body:
+        type: static
+        shape:
+          type: box
+          extents: [ 19.5, 2.5, 0.5 ]
+  - name: "floor"
+    components:
+      Transform:
+      StaticMesh:
+        pipeline: phong
+        shape:
+          type: cube # we don't (currently) support planes...
+          scale: [40, 0.5, 40]
+      phys::Body:
+        type: static
+        shape:
+          type: box # we don't (currently) support planes...
+          extents: [20, 0.25, 20]
+  - name: player
+    components:
+      Transform:
+      StaticMesh:
+        pipeline: phong
+        shape:
+          type: sphere
+      phys::Body:
+        shape:
+          type: sphere
+          radius: 1
+  - name: collectable
+    components:
+      Transform:
+      StaticMesh:
+        pipeline: phong
+        shape:
+          type: cube
+      phys::Body:
+        type: kinematic
+        shape:
+          type: box
\ No newline at end of file
diff --git a/demo/demo/rollball.cpp b/demo/demo/rollball.cpp
index 97f56cf8828b5b22546d403a1d2f04e9be074b86..bbf1ae4bcaf914b05c3da6036fb463d8419c2ed8 100644
--- a/demo/demo/rollball.cpp
+++ b/demo/demo/rollball.cpp
@@ -23,6 +23,8 @@
 #include "fggl/data/procedural.hpp"
 #include "fggl/gfx/camera.hpp"
 #include "fggl/input/camera_input.h"
+#include "fggl/util/service.h"
+#include "fggl/ecs3/prototype/loader.hpp"
 
 struct Prefabs {
 	fggl::ecs3::entity_t wallX;
@@ -34,90 +36,22 @@ struct Prefabs {
 
 static void setupPrefabs(fggl::ecs3::World& world, Prefabs& prefabs) {
 
-	{
-		// walls
-		prefabs.wallX = world.create(true);
-		world.add<fggl::math::Transform>(prefabs.wallX);
-
-		fggl::data::StaticMesh meshComponent;
-		meshComponent.pipeline = "phong";
+	auto storage = fggl::util::ServiceLocator::instance().get<fggl::data::Storage>();
+	fggl::ecs3::load_prototype_file(world, *storage, "rollball.yml");
 
-		// create a procedural cube, but scale the model to be a long wall
-		auto transform = glm::scale(fggl::data::OFFSET_NONE, {1.0f, 5.f, 41.0f});
-		fggl::data::make_cube(meshComponent.mesh, transform );
-		world.set<fggl::data::StaticMesh>(prefabs.wallX, &meshComponent);
-
-		auto* rb = world.add<fggl::phys::RigidBody>(prefabs.wallX);
-		rb->mass = fggl::phys::MASS_STATIC; // flag the object as static
-		rb->shape = new fggl::phys::Box({0.5F, 2.5F, 20.5F});
-	}
-
-	{
-		// walls
-		prefabs.wallZ = world.create(true);
-		world.add<fggl::math::Transform>(prefabs.wallZ);
-
-		fggl::data::StaticMesh meshComponent;
-		meshComponent.pipeline = "phong";
-
-		// create a procedural cube, but scale the model to be a long wall
-		auto transform = glm::scale(fggl::data::OFFSET_NONE, {39.f, 5.f, 1.0f});
-		fggl::data::make_cube(meshComponent.mesh, transform );
-		world.set<fggl::data::StaticMesh>(prefabs.wallZ, &meshComponent);
-
-		auto* rb = world.add<fggl::phys::RigidBody>(prefabs.wallZ);
-		rb->mass = fggl::phys::MASS_STATIC; // flag the object as static
-		rb->shape = new fggl::phys::Box({39.0F / 2, 2.5F, 0.5F});
-	}
-
-	{
-		// floor
-		prefabs.floor = world.create(true);
-		world.add<fggl::math::Transform>(prefabs.floor);
-
-		// create a procedural cube, but scale it to be plane-like
-		// TODO proper planes
-		fggl::data::StaticMesh meshComponent;
-		meshComponent.pipeline = "phong";
-		auto transform = glm::scale(fggl::data::OFFSET_NONE, {40.f, 0.5f, 40.0f});
-		fggl::data::make_cube(meshComponent.mesh, transform );
-		world.set<fggl::data::StaticMesh>(prefabs.floor, &meshComponent);
-
-		// physics: a static cube
-		auto* rb = world.add<fggl::phys::RigidBody>(prefabs.floor);
-		rb->mass = fggl::phys::MASS_STATIC; // flag the object as static
-		rb->shape = new fggl::phys::Box({20.0F, 0.5F, 20.0F});
-	}
+	prefabs.wallX = world.findProtoype("wallX");
+	prefabs.wallZ = world.findProtoype("wallZ");
+	prefabs.floor = world.findProtoype("floor");
 
 	{
 		// player (cube because my sphere function doesn't exist yet
-		prefabs.player = world.create(true);
-		world.add<fggl::math::Transform>(prefabs.player);
+		prefabs.player = world.findProtoype("player");
 		world.add<fggl::phys::Dynamics>(prefabs.player);
-
-		auto rb = world.add<fggl::phys::RigidBody>(prefabs.player);
-		rb->shape = new fggl::phys::Sphere(1.0f);
-
-		fggl::data::StaticMesh meshComponent;
-		meshComponent.pipeline = "phong";
-		fggl::data::make_sphere(meshComponent.mesh);
-		world.set<fggl::data::StaticMesh>(prefabs.player, &meshComponent);
 	}
 
 	{
 		// collectable
-		prefabs.collectable = world.create(true);
-		world.add<fggl::math::Transform>(prefabs.collectable);
-
-		fggl::data::StaticMesh meshComponent;
-		meshComponent.pipeline = "phong";
-		fggl::data::make_cube(meshComponent.mesh);
-		world.set<fggl::data::StaticMesh>(prefabs.collectable, &meshComponent);
-
-		// physics stuff
-		auto* rb = world.add<fggl::phys::RigidBody>(prefabs.collectable);
-		rb->shape = new fggl::phys::Box({0.5f, 0.5f, 0.5f});
-		rb->type = fggl::phys::BodyType::KINEMATIC;
+		prefabs.collectable = world.findProtoype("collectable");
 
 		// we need both of these for callbacks to trigger.
 		world.add<fggl::phys::CollisionCallbacks>(prefabs.collectable);
diff --git a/fggl/CMakeLists.txt b/fggl/CMakeLists.txt
index 7305b5b474370d83bb0b468c004e0c758b9c75b0..c9529e9d8bca4f0f182b53f5f22a79e3d8a6006b 100644
--- a/fggl/CMakeLists.txt
+++ b/fggl/CMakeLists.txt
@@ -60,6 +60,10 @@ target_sources(${PROJECT_NAME}
         math/shapes.cpp
         )
 
+# yaml-cpp for configs and storage
+find_package(yaml-cpp)
+target_link_libraries(fggl PUBLIC yaml-cpp)
+
 # spdlog for cleaner logging
 find_package(spdlog)
 target_link_libraries(${PROJECT_NAME} PRIVATE spdlog::spdlog)
diff --git a/include/fggl/ecs/component.hpp b/include/fggl/ecs/component.hpp
index 4e0e4918e2320f26b6863329e924888c587cd04b..f011adcb4df793b7aba2d5adee095b494095c4b9 100644
--- a/include/fggl/ecs/component.hpp
+++ b/include/fggl/ecs/component.hpp
@@ -8,12 +8,19 @@
 #include <string>
 #include <new>
 #include <utility>
+#include <iostream>
 
 #include <vector>
 #include <unordered_map>
+#include "yaml-cpp/yaml.h"
 
 namespace fggl::ecs {
 
+	template<typename T>
+	bool restore_config(T* comp, const YAML::Node& node) {
+		return false;
+	}
+
 	class ComponentBase {
 		public:
 			using data_t = unsigned char;
@@ -28,6 +35,8 @@ namespace fggl::ecs {
 
 			// virtual
 			virtual void *construct() const = 0;
+			virtual void *restore(const YAML::Node& config) const = 0;
+
 			virtual void *copyConstruct(const void *src) = 0;
 
 			virtual const char *name() const = 0;
@@ -56,6 +65,16 @@ namespace fggl::ecs {
 				new(data) C();
 			}
 
+			virtual void* restore(const YAML::Node& config) const override {
+				C* ptr = new C();
+				bool restored = restore_config<C>(ptr, config);
+				if ( !restored ) {
+					std::cerr << "error restoring " << name() << std::endl;
+					assert( false && "failed to restore configuration when loading type!" );
+				}
+				return ptr;
+			}
+
 			void *copyConstruct(const void *src) override {
 				const C *srcPtr = (C *) src;
 				return new C(*srcPtr);
diff --git a/include/fggl/ecs3/prototype/loader.hpp b/include/fggl/ecs3/prototype/loader.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..5086ab61c8afb96f3a68af3ca9d1d558011fc2b1
--- /dev/null
+++ b/include/fggl/ecs3/prototype/loader.hpp
@@ -0,0 +1,213 @@
+/*
+ * ${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 04/06/22.
+//
+
+#ifndef FGGL_ECS3_PROTOTYPE_LOADER_HPP
+#define FGGL_ECS3_PROTOTYPE_LOADER_HPP
+
+#include "yaml-cpp/yaml.h"
+#include "fggl/data/storage.hpp"
+#include "fggl/ecs3/ecs.hpp"
+
+#include "fggl/math/types.hpp"
+
+namespace fggl::ecs3 {
+
+
+	void load_prototype_node( ecs3::World& world, const YAML::Node& node) {
+		auto prototype = world.create(true);
+		auto& typeReg = world.types();
+
+		// store the prototype's name for future use
+		auto* nameComp = world.get<ecs3::EntityMeta>( prototype );
+		nameComp->typeName = node["name"].as<std::string>();
+
+		// grab the components and iterate them
+		if ( node["components"] ) {
+			world.createFromSpec( prototype, node["components"]);
+		}
+	}
+
+	void load_prototype_file( ecs3::World& world, data::Storage& storage, const std::string& name ) {
+		auto path = storage.resolvePath( data::Data, name );
+
+		auto root = YAML::LoadFile( path );
+		for (const auto& node : root["prefabs"] ) {
+			load_prototype_node(world, node);
+		}
+	}
+
+} // namespace fggl::ecs3
+
+namespace YAML {
+
+	template<>
+	struct convert<fggl::math::vec3> {
+		static Node encode(const fggl::math::vec3& rhs){
+			Node node;
+			node.push_back(rhs.x);
+			node.push_back(rhs.y);
+			node.push_back(rhs.z);
+			return node;
+		}
+
+		static bool decode(const Node& node, fggl::math::vec3& rhs) {
+			if ( !node.IsSequence() || node.size() != 3) {
+				return false;
+			}
+
+			rhs.x = node[0].as<float>();
+			rhs.y = node[1].as<float>();
+			rhs.z = node[2].as<float>();
+			return true;
+		}
+	};
+
+	template<>
+	struct convert<fggl::phys::BodyType> {
+		const static std::string TYPE_KINEMATIC;
+		const static std::string TYPE_STATIC;
+		const static std::string TYPE_DYN;
+
+		static Node encode(const fggl::phys::BodyType& rhs) {
+			switch (rhs) {
+				case fggl::phys::BodyType::KINEMATIC:
+					return Node(TYPE_KINEMATIC);
+				case fggl::phys::BodyType::STATIC:
+					return Node(TYPE_STATIC);
+				default:
+				case fggl::phys::BodyType::DYNAMIC:
+					return Node(TYPE_DYN);
+			}
+		}
+		static bool decode(const Node& node, fggl::phys::BodyType& rhs) {
+			const auto value = node.as<std::string>();
+			if ( value == TYPE_KINEMATIC ) {
+				rhs = fggl::phys::BodyType::KINEMATIC;
+			} else if ( value == TYPE_STATIC ) {
+				rhs = fggl::phys::BodyType::STATIC;
+			} else {
+				rhs = fggl::phys::BodyType::DYNAMIC;
+			}
+			return true;
+		}
+	};
+
+	const std::string convert<fggl::phys::BodyType>::TYPE_KINEMATIC = "kinematic";
+	const std::string convert<fggl::phys::BodyType>::TYPE_STATIC = "static";
+	const std::string convert<fggl::phys::BodyType>::TYPE_DYN = "dynamic";
+
+}
+
+namespace fggl::ecs {
+
+	template<>
+	bool restore_config(math::Transform* comp, const YAML::Node& node) {
+		return true;
+	}
+
+	static void process_mesh(data::Mesh& out, const YAML::Node& shape) {
+		auto transform = data::OFFSET_NONE;
+		if ( shape["offset"] ) {
+			auto scaleFactor = shape["offset"].as<math::vec3>();
+			transform = glm::translate(transform,  scaleFactor);
+		}
+
+		if ( shape["scale"] ) {
+			auto scaleFactor = shape["scale"].as<math::vec3>();
+			transform = glm::scale(transform,  scaleFactor);
+		}
+
+		// now the shape itself
+		auto shapeType = shape["type"].as<std::string>();
+		if ( shapeType == "cube" ) {
+			data::make_cube(out, transform);
+		} else if ( shapeType == "sphere" ) {
+			int stacks = shape["stacks"].as<int>(16);
+			int slices = shape["slices"].as<int>(16);
+			data::make_sphere(out, transform, stacks, slices);
+		}
+	}
+
+	template<>
+	bool restore_config(data::StaticMesh* meshComp, const YAML::Node& node) {
+		if ( !node["pipeline"] ) {
+			return false;
+		}
+
+		meshComp->pipeline = node["pipeline"].as<std::string>();
+
+		if ( node["shape"] ) {
+			data::Mesh mesh;
+			if (node["shape"].IsSequence()) {
+				// is a composite shape
+				for (const auto &shapeNode : node["shape"]) {
+					process_mesh(mesh, shapeNode);
+				}
+			} else {
+				// is a basic shape
+				process_mesh(mesh, node["shape"]);
+			}
+
+			// optimise and load
+			mesh.removeDups();
+			meshComp->mesh = mesh;
+		} else {
+			return false;
+		}
+
+		return true;
+	}
+
+
+	template<>
+	bool restore_config(phys::RigidBody* body, const YAML::Node& node) {
+		body->type = node["type"].as<fggl::phys::BodyType>(fggl::phys::BodyType::DYNAMIC);
+		if ( body->type == fggl::phys::BodyType::STATIC) {
+			body->mass = phys::MASS_STATIC;
+		} else {
+			body->mass = node["mass"].as<float>( phys::MASS_DEFAULT );
+		}
+
+		// shape detection
+		if ( node["shape"] ) {
+			auto type = node["shape"]["type"].as< std::string >();
+
+			if (type == "box") {
+				// assume unit box if extents are missing
+				auto extents = node["shape"]["extents"].as<math::vec3>(phys::UNIT_EXTENTS);
+				body->shape = new phys::Box(extents);
+			} else if (type == "sphere") {
+				auto radius = node["shape"]["radius"].as<float>(0.5F);
+				body->shape = new phys::Sphere(radius);
+			} else {
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+}
+
+#endif //FGGL_ECS3_PROTOTYPE_LOADER_HPP
diff --git a/include/fggl/ecs3/prototype/world.h b/include/fggl/ecs3/prototype/world.h
index 5a178f5d7f28e2a7222130e36a680f592b82a280..697f00304608996072bbacf5227b4cbdad0f93f3 100644
--- a/include/fggl/ecs3/prototype/world.h
+++ b/include/fggl/ecs3/prototype/world.h
@@ -10,6 +10,7 @@
 #include <unordered_set>
 
 #include <fggl/ecs3/types.hpp>
+#include <yaml-cpp/yaml.h>
 
 /**
  * A component based implementation of a game world.
@@ -45,6 +46,12 @@ namespace fggl::ecs3::prototype {
 				return ptr;
 			}
 
+			void* add(const std::shared_ptr<ComponentBase>& compMeta, const YAML::Node& config) {
+				void* ptr = compMeta->restore(config);
+				m_components[ compMeta->id() ] = ptr;
+				return ptr;
+			}
+
 			template<typename C>
 			C *set(const C *ptr) {
 				C *newPtr = new C(*ptr);
@@ -111,6 +118,7 @@ namespace fggl::ecs3::prototype {
 				auto *meta = entity.add<ecs3::EntityMeta>();
 				meta->id = nextID;
 				meta->abstract = abstract;
+				meta->typeName = "";
 
 				return nextID;
 			}
@@ -127,6 +135,27 @@ namespace fggl::ecs3::prototype {
 				return clone;
 			}
 
+			void addFromConfig(entity_t entity, component_type_t type, const YAML::Node& node) {
+				auto meta = m_types.meta(type);
+
+				auto& entityObj = m_entities.at(entity);
+				entityObj.add(meta, node);
+
+				m_types.fireAdd(this, entity, meta->id());
+			}
+
+			void createFromSpec(entity_t entity, const YAML::Node& compConfig) {
+				if ( compConfig ) {
+					for (const auto& it : compConfig ) {
+						const auto name = it.first.as<std::string>();
+						auto& config = it.second;
+
+						auto compType = m_types.find( name.c_str() );
+						addFromConfig(entity, compType, config);
+					}
+				}
+			}
+
 			bool alive(entity_t entity) const {
 				if (entity == NULL_ENTITY) {
 					// tis a silly question, but can be asked by accident.
@@ -302,6 +331,21 @@ namespace fggl::ecs3::prototype {
 				m_deathListeners.emplace_back(callback);
 			}
 
+			entity_t findProtoype(const std::string& name) const {
+				for ( const auto& [entity, obj] : m_entities ) {
+					if ( !obj.m_abstract ){
+						continue;
+					}
+
+					auto* metaData = obj.get<EntityMeta>();
+					if ( metaData->typeName == name){
+						return entity;
+					}
+				}
+
+				return NULL_ENTITY;
+			}
+
 		private:
 			std::vector< entity_cb > m_deathListeners;
 			TypeRegistry &m_types;
diff --git a/include/fggl/ecs3/types.hpp b/include/fggl/ecs3/types.hpp
index 6c011d3602a100117bfe143c10fbad3327b94639..906cbe7f94a6ee5b1819a5ee1656cb506b377d2e 100644
--- a/include/fggl/ecs3/types.hpp
+++ b/include/fggl/ecs3/types.hpp
@@ -38,6 +38,7 @@ namespace fggl::ecs3 {
 		constexpr static const char name[] = "meta";
 		entity_t id;
 		bool abstract;
+		std::string typeName;
 	};
 
 	struct RecordIdentifier {
@@ -186,6 +187,7 @@ namespace fggl::ecs3 {
 
 			inline component_type_t find(const char *name) const {
 				for (auto &[type, meta] : m_types) {
+					std::cerr << meta->name() << std::endl;
 					if (std::strcmp(name, meta->name()) == 0) {
 						return type;
 					}
diff --git a/include/fggl/math/types.hpp b/include/fggl/math/types.hpp
index 6125c37223b421329c547550b1b5d6d78e4619ef..4b635c1eb242b918992f6eb0efe52b0a745c9f9f 100644
--- a/include/fggl/math/types.hpp
+++ b/include/fggl/math/types.hpp
@@ -166,7 +166,6 @@ namespace fggl::math {
 
 }
 
-
 // feels a bit strange to be doing this...
 namespace glm {
 	inline bool operator<(const vec3 &lhs, const vec3 &rhs) {
diff --git a/include/fggl/phys/types.hpp b/include/fggl/phys/types.hpp
index 3b15ec6fe7e10346232459aebaf285d0e78b9f91..c3b68de411c6975473b06ee8dc47533cbd7ead2b 100644
--- a/include/fggl/phys/types.hpp
+++ b/include/fggl/phys/types.hpp
@@ -30,6 +30,7 @@
 namespace fggl::phys {
 	constexpr float MASS_STATIC = 0.0F;
 	constexpr float MASS_DEFAULT = 1.0F;
+	constexpr math::vec3 UNIT_EXTENTS{0.5F, 0.5F, 0.5F};
 
 	enum class ShapeType {
 			UNSET,