diff --git a/.gitignore b/.gitignore
index 637196074c032c32bda22ecab4ca7d27c6efd7db..8eebd05a1fa7a95850b5cbdf309a1cbf3ab78f2a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,9 @@ imgui.ini
 .flatpak-builder/
 compile_commands.json
 
+# Engine data
+demo/data/*.bin
+
 # dotfiles (IDE)
 .idea/
 .cache/
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b5a980e3a0569c693e50f6e37c7cf4531788e621..6e7c22b6e640ffc12851571f53138e143eb77e73 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -129,4 +129,4 @@ export(EXPORT "${PROJECT_NAME}Targets"
 
 # 3rd party integrations
 add_subdirectory( integrations/bullet )
-
+add_subdirectory( tools/pack )
diff --git a/demo/data/rollball.yml b/demo/data/rollball.yml
index 015c22605f03d8895b67d13da92d582e9ffa0416..966d1f863d6e3c0a9fdfbcb06ae3a13efc4e1201 100644
--- a/demo/data/rollball.yml
+++ b/demo/data/rollball.yml
@@ -1,19 +1,19 @@
 ---
 prefabs:
-  - name: "environment"
+  - name: "rb_environment"
     components:
       gfx::material:
         ambient: [0.0215, 0.1754, 0.0215]
         diffuse: [1, 1, 1]
         specular: [0.0633, 0.727811, 0.633]
         shininess: 16
-  - name: "wallX"
-    parent: "environment"
+  - name: "rb_wallX"
+    parent: "rb_environment"
     components:
       Transform:
       StaticMesh:
         pipeline: redbook/debug
-        shape_id: "rb_wall_x"
+        shape_id: "mesh_rb_wall_x"
         shape:
           type: box
           scale: [1.0, 5.0, 41]
@@ -23,13 +23,13 @@ prefabs:
           type: box
           extents: [0.5, 2.5, 20.5]
   # Wall Z shorter to avoid z-fighting
-  - name: "wallZ"
-    parent: "environment"
+  - name: "rb_wallZ"
+    parent: "rb_environment"
     components:
       Transform:
       StaticMesh:
         pipeline: redbook/debug
-        shape_id: "rb_wall_z"
+        shape_id: "mesh_rb_wall_z"
         shape:
           type: box
           scale: [39, 5, 1]
@@ -38,13 +38,13 @@ prefabs:
         shape:
           type: box
           extents: [ 19.5, 2.5, 0.5 ]
-  - name: "floor"
-    parent: "environment"
+  - name: "rb_floor"
+    parent: "rb_environment"
     components:
       Transform:
       StaticMesh:
         pipeline: redbook/debug
-        shape_id: "rb_floor"
+        shape_id: "mesh_rb_floor"
         shape:
           type: box # we don't (currently) support planes...
           scale: [39, 0.5, 39]
@@ -53,12 +53,12 @@ prefabs:
         shape:
           type: box # we don't (currently) support planes...
           extents: [19.5, 0.25, 19.5]
-  - name: player
+  - name: rb_player
     components:
       Transform:
       StaticMesh:
         pipeline: redbook/debug
-        shape_id: "rb_player"
+        shape_id: "mesh_rb_player"
         shape:
           type: sphere
       gfx::material:
@@ -70,12 +70,12 @@ prefabs:
         shape:
           type: sphere
           radius: 1
-  - name: collectable
+  - name: rb_collectable
     components:
       Transform:
       StaticMesh:
         pipeline: redbook/debug
-        shape_id: "rb_collect"
+        shape_id: "mesh_rb_collect"
         shape:
           type: box
       gfx::material:
@@ -86,4 +86,37 @@ prefabs:
       phys::Body:
         type: kinematic
         shape:
-          type: box
\ No newline at end of file
+          type: box
+scene:
+  - prefab: rb_wallX
+    components:
+      Transform:
+        origin: [20, 0, 0]
+  - prefab: rb_wallX
+    components:
+      Transform:
+        origin: [-20, 0, 0]
+  - prefab: rb_wallZ
+    components:
+      Transform:
+        origin: [0, 0, -20]
+  - prefab: rb_wallZ
+    components:
+      Transform:
+        origin: [0, 0, 20]
+  - prefab: rb_floor
+    components:
+      Transform:
+        origin: [0, -2.5, 0]
+  - prefab: rb_collectable
+    components:
+      Transform:
+        origin: [-5, -0.5, 12]
+  - prefab: rb_collectable
+    components:
+      Transform:
+        origin: [15, -0.5, 0.5]
+  - prefab: rb_collectable
+    components:
+      Transform:
+        origin: [6, -0.5, 15]
diff --git a/demo/demo/rollball.cpp b/demo/demo/rollball.cpp
index 75439bb41c6e8ad8b20f2ff76703b08873c2cc90..cccdac0f7932bca3d4f0aae0b28559b3267f2104 100644
--- a/demo/demo/rollball.cpp
+++ b/demo/demo/rollball.cpp
@@ -31,11 +31,11 @@
 
 #include <array>
 
-static const fggl::util::GUID playerPrefab = "player"_fid;
-static const fggl::util::GUID collectablePrefab = "collectable"_fid;
-static const fggl::util::GUID WallNPrefab = "wallX"_fid;
-static const fggl::util::GUID WallEPrefab = "wallZ"_fid;
-static const fggl::util::GUID floorPrefab = "floor"_fid;
+static const fggl::util::GUID playerPrefab = "rb_player"_fid;
+static const fggl::util::GUID collectablePrefab = "rb_collectable"_fid;
+static const fggl::util::GUID WallNPrefab = "rb_wallX"_fid;
+static const fggl::util::GUID WallEPrefab = "rb_wallZ"_fid;
+static const fggl::util::GUID floorPrefab = "rb_floor"_fid;
 
 static void setup_camera(fggl::entity::EntityManager& world) {
 	auto prototype = world.create();
diff --git a/include/fggl/data/model.hpp b/include/fggl/data/model.hpp
index 61cc4b582df4d58b1c79836435c9956da2d4c7a6..b042590ef43be92c07c3a75198934735b872a94a 100644
--- a/include/fggl/data/model.hpp
+++ b/include/fggl/data/model.hpp
@@ -167,8 +167,7 @@ namespace fggl::data {
 		data::Mesh mesh;
 		std::string pipeline;
 
-		inline StaticMesh() : mesh(), pipeline() {}
-
+		inline StaticMesh() = default;
 		inline StaticMesh(const data::Mesh &aMesh, std::string aPipeline) :
 			mesh(aMesh), pipeline(std::move(aPipeline)) {}
 	};
diff --git a/include/fggl/entity/loader/loader.hpp b/include/fggl/entity/loader/loader.hpp
index 2ba077519b4175f8c63d20abe57e69547b460a50..a23493619b97beba9bfe05a2db3a48d7b5169d77 100644
--- a/include/fggl/entity/loader/loader.hpp
+++ b/include/fggl/entity/loader/loader.hpp
@@ -47,34 +47,26 @@ namespace fggl::entity {
 			inline EntityFactory(modules::Services &services) : m_services(services) {}
 
 			EntityID create(const EntityType &spec, EntityManager &manager, const CustomiseFunc &customise = nullptr) {
-				debug::warning("creating {}", spec);
-				assert( m_prototypes.contains(spec) && "asked to create undefined prototype!" );
+				std::vector<CustomiseFunc> finishers;
 
-				try {
-					std::vector<CustomiseFunc> finishers;
-
-					// set up the components for the entity
-					auto entity = setupComponents(spec, manager, finishers);
-					if (entity == entity::INVALID) {
-						debug::error("EntityFactory: failed to build from prototype {}", std::to_string(spec.get()));
-						return entity::INVALID;
-					}
-
-					// allow the caller to perform any setup needed
-					if (customise != nullptr) {
-						customise(manager, entity);
-					}
+				// build the setup
+				auto entity = setupComponents(spec, manager, finishers);
+				if ( entity == entity::INVALID ) {
+					debug::error("EntityFactory: failed to build from prototype {}", std::to_string(spec.get()));
+					return entity::INVALID;
+				}
 
-					// finally, we run any cleanup/init setups required by the component factories
-					for (auto &finisher : finishers) {
-						finisher(manager, entity);
-					}
+				// if requested, allow the user to customize the creation
+				if ( customise != nullptr ) {
+					customise(manager, entity);
+				}
 
-					return entity;
-				} catch (std::out_of_range &ex) {
-					debug::error("EntityFactory: Unknown entity type '{}'", spec);
-					return fggl::entity::INVALID;
+				// run finishers for components
+				for ( auto& finisher : finishers ) {
+					finisher( manager, entity );
 				}
+
+				return entity;
 			}
 
 			void log_known_types() const {
@@ -113,7 +105,13 @@ namespace fggl::entity {
 
 				auto currentType = entityType;
 				while (currentType != NO_PARENT) {
-					auto &entitySpec = m_prototypes.at(currentType);
+					const auto& specEntry = m_prototypes.find( currentType );
+					if ( specEntry == m_prototypes.end() ) {
+						debug::warning("Asked to setup {}, for {} but was not a known prototype", specEntry->first, entityType);
+						return entity::INVALID;
+					}
+
+					auto entitySpec = specEntry->second;
 					debug::debug("constructing {} for {} ({} comps)", currentType, entityType, entitySpec.components.size());
 					assert( entitySpec.ordering.size() == entitySpec.components.size() && "ordering incorrect size, bad things happend!" );
 
diff --git a/include/fggl/platform/fallback/file.hpp b/include/fggl/platform/fallback/file.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..8d2b855a1ea73c67a4a14af8f21fb36230a2f8ce
--- /dev/null
+++ b/include/fggl/platform/fallback/file.hpp
@@ -0,0 +1,63 @@
+/*
+ * This file is part of FGGL.
+ *
+ * FGGL 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.
+ *
+ * FGGL 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 FGGL.
+ * If not, see <https://www.gnu.org/licenses/>.
+ */
+
+//
+// Created by webpigeon on 19/09/22.
+//
+
+#ifndef FGGL_PLATFORM_FALLBACK_FILE_HPP
+#define FGGL_PLATFORM_FALLBACK_FILE_HPP
+
+#include <cstdio>
+#include <cassert>
+
+namespace fggl::platform {
+
+	class File {
+		public:
+			inline File(FILE* filePtr) : m_handle(filePtr) {
+				assert(filePtr != nullptr);
+			}
+
+			inline ~File() {
+				release();
+			}
+
+			template<typename T>
+			inline void write(const T* dataPtr) {
+				assert( m_handle != nullptr );
+				int status = fwrite(dataPtr, sizeof(T), 1, m_handle );
+				assert( status == 1);
+			}
+
+			template<typename T>
+			inline void writeArr(const T* dataPtr, std::size_t numElms) {
+				assert( m_handle != nullptr);
+				int status = fwrite(dataPtr, sizeof(T), numElms, m_handle );
+				assert( status == 1);
+			}
+
+		private:
+			std::FILE* m_handle;
+
+			inline void release() {
+				if ( m_handle != NULL) {
+					fclose(m_handle);
+				}
+			}
+	};
+
+} // namespace fggl::platform
+
+#endif //FGGL_PLATFORM_FALLBACK_FILE_HPP
diff --git a/tools/pack/CMakeLists.txt b/tools/pack/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..88c5dc565b7558dba0bcf3f5416b29eb784b374e
--- /dev/null
+++ b/tools/pack/CMakeLists.txt
@@ -0,0 +1,8 @@
+add_executable(fgpak)
+
+target_link_libraries(fgpak fggl)
+
+target_sources(fgpak
+  PRIVATE
+    src/main.cpp
+)
\ No newline at end of file
diff --git a/tools/pack/src/binary.hpp b/tools/pack/src/binary.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..8c52fdcf8ad0ec80b33c123bc784526b19b8e2f5
--- /dev/null
+++ b/tools/pack/src/binary.hpp
@@ -0,0 +1,135 @@
+/*
+ * This file is part of FGGL.
+ *
+ * FGGL 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.
+ *
+ * FGGL 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 FGGL.
+ * If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This file is part of FGGL.
+ *
+ * FGGL 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.
+ *
+ * FGGL 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 FGGL.
+ * If not, see <https://www.gnu.org/licenses/>.
+ */
+
+//
+// Created by webpigeon on 11/09/22.
+//
+
+#ifndef FGGL_TOOLS_PACK_INCLUDES_BINARY_HPP
+#define FGGL_TOOLS_PACK_INCLUDES_BINARY_HPP
+
+#include <cstdint>
+#include <iostream>
+#include <cstdio>
+
+#include "fggl/data/model.hpp"
+
+namespace fggl::data {
+
+	enum class ModelType {
+			OPENGL
+	};
+
+	struct ModelHeader {
+		unsigned long guid;
+		std::size_t size;
+		ModelType type;
+	};
+
+	void write_mesh(FILE* file, const Mesh& mesh) {
+		static_assert( std::is_standard_layout_v<Vertex> );
+		static_assert( std::is_standard_layout_v<Mesh::IndexType> );
+
+		// write vertex data
+		std::size_t vertexCount = mesh.vertexCount();
+		fwrite( &vertexCount, sizeof(vertexCount), 1, file );
+		fwrite( mesh.vertexList().data(), sizeof(Vertex), vertexCount, file );
+
+		std::size_t indexCount = mesh.indexCount();
+		fwrite( &indexCount, sizeof(indexCount), 1, file);
+		fwrite( mesh.indexList().data(), sizeof(Mesh::IndexType), indexCount, file);
+	}
+
+	void write_model(FILE* file, const ModelHeader& header, const Mesh& mesh) {
+		assert( header.type == ModelType::OPENGL );
+		fwrite( &header , sizeof(ModelHeader), 1, file);
+		write_mesh(file, mesh);
+	}
+
+	void read_mesh(FILE* fin, data::Mesh& mesh) {
+		static_assert( std::is_standard_layout_v<Vertex> );
+		static_assert( std::is_standard_layout_v<Mesh::IndexType> );
+		std::size_t readCount;
+
+		// vertex data
+		std::size_t vertexCount = 0;
+		readCount = fread( &vertexCount, sizeof(vertexCount), 1, fin );
+		assert(ferror(fin) == 0);
+		if (readCount != 1) {
+			std::cerr << "failed to read vertex count" << std::endl;
+			return;
+		}
+
+		// push vertex data into mesh
+		auto* vertexData = new data::Vertex[vertexCount];
+		readCount = fread( vertexData, sizeof(Vertex), vertexCount, fin );
+		assert(ferror(fin) == 0);
+		if ( readCount != vertexCount ) {
+			std::cerr << "failed to read vertex data" << std::endl;
+			return;
+		}
+		for ( std::size_t i = 0; i < vertexCount; ++i) {
+			mesh.pushVertex(vertexData[i]);
+		}
+		delete[] vertexData;
+
+		// read index size
+		std::size_t indexCount = 0;
+		readCount = fread( &indexCount, sizeof(indexCount), 1, fin);
+		assert(ferror(fin) == 0);
+		if (readCount != 1 ) {
+			std::cerr << "failed to read index count" << std::endl;
+			return;
+		}
+
+		// read index data
+		auto* idxData = new Mesh::IndexType[indexCount];
+		readCount = fread( idxData, sizeof(Mesh::IndexType), indexCount, fin);
+		assert(ferror(fin) == 0);
+
+		if (readCount != indexCount) {
+			std::cerr << "failed to read index data, expected: " << indexCount << ", got: " << readCount  << std::endl;
+			return;
+		}
+
+		for (int i=0; i < indexCount; ++i) {
+			mesh.pushIndex( idxData[i] );
+		}
+		delete[] idxData;
+	}
+
+	void read_model(FILE* file, ModelHeader& header, Mesh& mesh) {
+		fread( &header, sizeof(ModelHeader), 1, file);
+		if ( header.type == ModelType::OPENGL ) {
+			read_mesh(file, mesh);
+		}
+	}
+
+}
+
+#endif //FGGL_TOOLS_PACK_INCLUDES_BINARY_HPP
diff --git a/tools/pack/src/main.cpp b/tools/pack/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..afc26fdb4ab6ccba13b8b3af07e27cb5fca38104
--- /dev/null
+++ b/tools/pack/src/main.cpp
@@ -0,0 +1,158 @@
+/*
+ * This file is part of FGGL.
+ *
+ * FGGL 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.
+ *
+ * FGGL 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 FGGL.
+ * If not, see <https://www.gnu.org/licenses/>.
+ */
+
+//
+// Created by webpigeon on 11/09/22.
+//
+
+#include <cstdlib>
+#include <iostream>
+#include <fstream>
+#include <filesystem>
+#include <map>
+
+#include <yaml-cpp/yaml.h>
+
+#include "fggl/data/model.hpp"
+#include "fggl/data/procedural.hpp"
+#include "fggl/entity/loader/loader.hpp"
+
+#include "binary.hpp"
+
+constexpr const char* YAML_PREFAB = "prefabs";
+constexpr const char* YAML_COMPONENT = "components";
+
+constexpr uint32_t DEFAULT_STACKS = 16;
+constexpr uint32_t DEFAULT_SLICES = 16;
+constexpr const char *SHAPE_SPHERE{"sphere"};
+constexpr const char *SHAPE_BOX{"box"};
+
+namespace fggl {
+	static void process_shape(const YAML::Node &node, data::Mesh &mesh) {
+		auto transform = data::OFFSET_NONE;
+
+		auto offset = node["offset"].as<math::vec3>(math::VEC3_ZERO);
+		transform = glm::translate(transform, offset);
+
+		auto scale = node["scale"].as<math::vec3>(math::VEC3_ONES);
+		transform = glm::scale(transform, scale);
+		debug::debug("scale: {}, {}, {}", scale.x, scale.y, scale.z);
+
+		// now the shape itself
+		auto type = node["type"].as<std::string>();
+		if (type == SHAPE_BOX) {
+			data::make_cube(mesh, transform);
+		} else if (type == SHAPE_SPHERE) {
+			auto stacks = node["stacks"].as<uint32_t>(DEFAULT_STACKS);
+			auto slices = node["slices"].as<uint32_t>(DEFAULT_SLICES);
+			data::make_sphere(mesh, transform, stacks, slices);
+		} else {
+			debug::log(debug::Level::warning, "unknown shape type requested: {}", type);
+		}
+	}
+
+	static void process_mesh(const YAML::Node& spec, fggl::data::Mesh& mesh) {
+		// process shape structure
+		if (spec["shape"].IsSequence()) {
+			for (const auto &node : spec["shape"]) {
+				process_shape(node, mesh);
+			}
+		} else {
+			process_shape(spec["shape"], mesh);
+		}
+		mesh.removeDups();
+	}
+}
+
+void write_comp_static_model(FILE* fout, const YAML::Node& config) {
+	// calculate mesh
+	fggl::data::Mesh mesh;
+	fggl::process_mesh( config, mesh);
+
+	// calculate correct name
+	auto meshName = config["shape_id"];
+	auto meshNameStr = meshName.as<std::string>();
+	auto meshGuid = fggl::util::make_guid_rt(meshNameStr);
+
+	// header data
+	fggl::data::ModelHeader header{
+		.guid = meshGuid.get(),
+		.type = fggl::data::ModelType::OPENGL,
+	};
+
+	// write the mesh to disk
+	fggl::data::write_model( fout, header, mesh );
+}
+
+int main(int argc, char* argv[]) {
+	auto* packName = argv[1];
+	std::cout << "generating " << packName << std::endl;
+
+	std::map< fggl::util::GUID, std::string > guids;
+
+	// rollball
+	auto rollPath = std::filesystem::current_path() / "rollball.yml";
+	std::cout << rollPath << std::endl;
+
+	YAML::Node root = YAML::LoadFile(rollPath);
+	if ( !root ){
+		return EXIT_FAILURE;
+	}
+
+	//std::cout << root[ YAML_PREFAB ] << std::endl;
+
+	std::map<std::string, std::function<void(FILE* fout, const YAML::Node&)>> converters;
+	converters["StaticMesh"] = write_comp_static_model;
+
+	std::string meshFile = "rollball_models.bin";
+	FILE* fout = fopen(meshFile.c_str(), "w");
+
+	// pack prefabs
+	for (const auto& prefab : root[YAML_PREFAB]) {
+		// name
+		auto name = prefab["name"].as<std::string>();
+		auto nameRef = fggl::util::make_guid(name.c_str());
+		guids[nameRef] = name;
+		std::cout << name << std::endl;
+
+		// parent
+		//auto parentName = prefab["parent"].as<std::string>();
+		//auto parentGuid = fggl::util::make_guid(parentName.c_str());
+
+		// components
+		for( const auto& key : prefab[YAML_COMPONENT] ) {
+			auto compStr = key.first.as<std::string>();
+			auto compGuid = fggl::util::make_guid_rt(compStr);
+			guids[compGuid] = compStr;
+
+			// figure out the type
+			auto compWrite = converters.find(compStr);
+			if ( compWrite != converters.end()) {
+				compWrite->second(fout, key.second);
+			}
+		}
+
+	}
+
+	fclose(fout);
+
+	// guid table
+	std::cerr << "GUID Table" << std::endl;
+	std::cerr << "guid,str" << std::endl;
+	for (auto& [guid,str] : guids) {
+		std::cerr << guid.get() << "," << str << std::endl;
+	}
+
+	return EXIT_SUCCESS;
+}
\ No newline at end of file