diff --git a/demo/data/rollball.yml b/demo/data/rollball.yml
index f9895a675c2d8e3b044d18aa0553a2adfb26420e..5c562bc6250db16a173357b163cdc5d221d21bd0 100644
--- a/demo/data/rollball.yml
+++ b/demo/data/rollball.yml
@@ -71,6 +71,8 @@ prefabs:
           type: sphere
           radius: 1
   - name: rb_collectable
+    tags:
+      - "collectable"
     components:
       Transform:
       StaticMesh:
@@ -119,6 +121,8 @@ scene:
   - prefab: rb_collectable
     components:
       Transform:
-        origin: [6, -0.5, 15]
+        origin: [6, -0.5, -15]
+  - prefab: rb_player
+    name: "player"
 scripts:
   - "rollball.lua"
\ No newline at end of file
diff --git a/demo/demo/rollball.cpp b/demo/demo/rollball.cpp
index 39fb33b6d53488a28991e89e6c2de8ba3e65e178..0838fffb1816f267fae5abf80e81beec42b718ae 100644
--- a/demo/demo/rollball.cpp
+++ b/demo/demo/rollball.cpp
@@ -49,57 +49,12 @@ static void setup_camera(fggl::entity::EntityManager& world) {
 }
 
 static fggl::entity::EntityID setup_environment(fggl::entity::EntityManager& world, fggl::entity::EntityFactory* factory, const fggl::math::vec2& size, demo::RollState& state) {
-	factory->create(WallNPrefab, world, [size](auto& manager, const auto& entity) {
-			auto& transform = manager.template get<fggl::math::Transform>(entity);
-			transform.origin({size.x/2, 0.0F, 0.0F});
-		});
-
-	factory->create(WallNPrefab, world, [size](auto& manager, const auto& entity) {
-		auto& transform = manager.template get<fggl::math::Transform>(entity);
-		transform.origin({-size.x/2, 0.0F, 0.0F});
-	});
-
-	factory->create(WallEPrefab, world, [size](auto& manager, const auto& entity) {
-		auto& transform = manager.template get<fggl::math::Transform>(entity);
-		transform.origin({0.0F, 0.0F, -size.y/2});
-	});
-
-	factory->create(WallEPrefab, world, [size](auto& manager, const auto& entity) {
-		auto& transform = manager.template get<fggl::math::Transform>(entity);
-		transform.origin({0.0F, 0.0F, size.y/2});
-	});
-
-	factory->create(floorPrefab, world, [](auto& manager, const auto& entity) {
-		auto& transform = manager.template get<fggl::math::Transform>(entity);
-		transform.origin({0.0F, -2.5F, 0.0F});
-	});
-
-	// the player requires no special setup
-	state.player = factory->create(playerPrefab, world);
-
-	{
-		// collectables
-		std::array<fggl::math::vec3, 3> collectPos {{
-			{-5.0f, -0.5f, 12.0f},
-			{15.0f, -0.5f, 0.5f},
-			{6.0f, -0.5f, -15.0f}
-		}};
-
-		// build the collectables
-		int collectCount = 0;
-		for (auto& pos : collectPos) {
-			auto collectable = factory->create(collectablePrefab, world, [pos](auto& manager, const auto& entity) {
-				auto& transform = manager.template get<fggl::math::Transform>(entity);
-				transform.origin(pos);
-			});
-			state.collectables[collectCount++] = collectable;
-		}
-	}
-
 	// ensure the state is clean
 	state.closestPickup = fggl::entity::INVALID;
 	state.mode = demo::DebugMode::NORMAL;
 	state.time = 0.0F;
+	state.player = world.findByName("player");
+	state.collectables = world.findByTag("collectable");
 
 	return state.player;
 }
@@ -131,6 +86,7 @@ namespace demo {
 		// asset loader
 		auto* assetLoader = m_owner.service<fggl::assets::Loader>();
 		assetLoader->load("rollball.yml", fggl::entity::PROTOTYPE_ASSET);
+		assetLoader->load("rollball.yml", fggl::entity::SCENE, this);
 
 		// collectable callbacks
 		/*auto* collectableCallbacks = world().get<fggl::phys::CollisionCallbacks>(prefabs.collectable);
diff --git a/demo/include/rollball.hpp b/demo/include/rollball.hpp
index a470bfceb9dfb41dca65c45a4fd331ef361a70c3..ef757f10269f9c1a1be32c88015f0d493e8a76a3 100644
--- a/demo/include/rollball.hpp
+++ b/demo/include/rollball.hpp
@@ -41,11 +41,7 @@ namespace demo {
 		fggl::entity::EntityID player = fggl::entity::INVALID;
 		fggl::entity::EntityID closestPickup;
 		DebugMode mode = DebugMode::NORMAL;
-
-		std::array<fggl::entity::EntityID, 3> collectables {
-			fggl::entity::INVALID,
-			fggl::entity::INVALID,
-			fggl::entity::INVALID };
+		std::vector<fggl::entity::EntityID> collectables;
 		float time = 0.0f;
 	};
 
diff --git a/fggl/entity/loader/loader.cpp b/fggl/entity/loader/loader.cpp
index c38715bb9acc7f01ac8f10399b0bd384bee0787c..4baa79016a395cb6a797a2fa1ca8d9775e8fa8b7 100644
--- a/fggl/entity/loader/loader.cpp
+++ b/fggl/entity/loader/loader.cpp
@@ -18,9 +18,65 @@
 
 #include "fggl/entity/loader/loader.hpp"
 #include "fggl/debug/logging.hpp"
+#include "fggl/scenes/game.hpp"
 
 namespace fggl::entity {
 
+	assets::AssetRefRaw load_scene(assets::Loader* loader, const assets::AssetGUID& asset, assets::AssetData data, void* ptr) {
+		auto *filePath = std::get<assets::AssetPath *>(data);
+		scenes::Game* gamePtr = (scenes::Game*)ptr;
+
+		// load assets
+		auto* entityFactory = gamePtr->owner().service<EntityFactory>();
+
+		auto nodes = YAML::LoadAllFromFile(filePath->c_str());
+		for (const auto& node : nodes) {
+			auto scene = node["scene"];
+			if ( !scene ) {
+				debug::warning("no scene node in YAML file...");
+				return nullptr;
+			}
+
+			// create and personalize
+			for (const auto& item : scene) {
+
+				// only safe if personalize is called BEFORE end of loop itr
+				auto personalize = [&entityFactory, &item, gamePtr](EntityManager& manager, const EntityID eid) {
+
+					for ( const auto& compConfig : item["components"]) {
+						auto compName = compConfig.first.as<fggl::util::GUID>();
+						auto& compInfo = entityFactory->getInfo(compName);
+
+						// setup config data
+						ComponentSpec spec;
+						spec.config = compConfig.second;
+
+						// re-run the factory with the modified arguments (might need to support a 'patch' function...)
+						compInfo.factory( spec, manager, eid, gamePtr->owner().services());
+					}
+				};
+
+				// finally, load the entity and trigger personalize
+				auto prefab = item["prefab"].as<fggl::util::GUID>();
+				auto entity = entityFactory->create(prefab, gamePtr->world(), personalize);
+
+				// metadata
+				if ( item["name"].IsDefined() ) {
+					gamePtr->world().setName(item["name"].as<std::string>(), entity);
+				}
+
+				if ( item["tags"].IsDefined() ) {
+					for ( auto tag : item["tags"] ) {
+						auto tagGuid = tag.as<fggl::util::GUID>();
+						gamePtr->world().addTag(entity, tagGuid);
+					}
+				}
+			}
+		}
+
+		return nullptr;
+	}
+
 	assets::AssetRefRaw load_prototype(assets::Loader* loader, EntityFactory *factory, const assets::AssetGUID &guid, assets::AssetData data) {
 		auto *filePath = std::get<assets::AssetPath *>(data);
 
@@ -48,6 +104,12 @@ namespace fggl::entity {
 					debug::trace("prefab {} has component {}", name, compId);
 				}
 
+				if ( prefab["tags"].IsDefined() ) {
+					for ( auto& tagNode : prefab["tags"] ) {
+						entity.tags.push_back( tagNode.as< util::GUID >() );
+					}
+				}
+
 				factory->define(name, entity);
 			}
 		}
diff --git a/fggl/entity/module.cpp b/fggl/entity/module.cpp
index 752b108862750eb54798a3f3ff571b2f2514e659..d6447e42c7f68ffe32f9bacff60f3ea11415951e 100644
--- a/fggl/entity/module.cpp
+++ b/fggl/entity/module.cpp
@@ -18,11 +18,14 @@
 
 #include "fggl/entity/module.hpp"
 #include "fggl/math/types.hpp"
+#include "fggl/scenes/game.hpp"
 
 namespace fggl::entity {
 
 	void make_transform(const entity::ComponentSpec &spec, EntityManager &manager, const entity::EntityID entity, modules::Services &/*svc*/) {
 		auto &transform = manager.add<math::Transform>(entity);
+
+		//FIXME won't work for patching!
 		transform.origin(spec.get<math::vec3>("origin", math::VEC3_ZERO));
 		transform.euler(spec.get<math::vec3>("rotation", math::VEC3_ZERO));
 		transform.scale(spec.get<math::vec3>("scale", math::VEC3_ONES));
@@ -41,9 +44,10 @@ namespace fggl::entity {
 
 			// we are responsible for prefabs...
 			auto *assetLoader = services.get<assets::Loader>();
-			assetLoader->setFactory(PROTOTYPE_ASSET, [factory](assets::Loader* loader, const assets::AssetGUID &a, assets::AssetData b) {
+			assetLoader->setFactory(PROTOTYPE_ASSET, [factory](assets::Loader* loader, const assets::AssetGUID &a, assets::AssetData b, void* ptr) {
 				return load_prototype(loader, factory, a, b);
 			}, assets::LoadType::PATH);
+			assetLoader->setFactory(SCENE, load_scene, assets::LoadType::PATH);
 
 			return true;
 		}
diff --git a/include/fggl/app.hpp b/include/fggl/app.hpp
index a6c6a90dd3be9991952eb4c9e6779575a0bc1ada..b50ada7b808218b90d629487666a0f987d5185d9 100644
--- a/include/fggl/app.hpp
+++ b/include/fggl/app.hpp
@@ -76,6 +76,10 @@ namespace fggl {
 
 			virtual void deactivate() {}
 
+			inline App& owner() {
+				return m_owner;
+			}
+
 		protected:
 			App &m_owner;
 	};
@@ -128,6 +132,10 @@ namespace fggl {
 				}
 			}
 
+			inline modules::Services& services() {
+				return m_subsystems->services();
+			}
+
 			inline bool running() const {
 				return m_running;
 			}
diff --git a/include/fggl/assets/loader.hpp b/include/fggl/assets/loader.hpp
index 48fd85321afeb97edb14ac0931327292121255e3..b58f506002576ea725d04913ea5a3ecf3f4f7267 100644
--- a/include/fggl/assets/loader.hpp
+++ b/include/fggl/assets/loader.hpp
@@ -68,21 +68,28 @@ namespace fggl::assets {
 				m_requests.push(ResourceRequest{guid, type});
 			}
 
-			void load(const AssetGUID &guid, const AssetType &type) {
+			void load(const AssetGUID &guid, const AssetType &type, void* userPtr = nullptr) {
 				auto path = m_storage->resolvePath(data::StorageType::Data, guid);
 
-				auto &config = m_factories.at(type);
-				switch (config.second) {
-				case LoadType::DIRECT:
-					// TODO we load the data into main memory and give a pointer to it.
-					debug::log(debug::Level::error, "Tried to load direct asset - no one wrote that yet!");
-					break;
-				case LoadType::STAGED:
-					// TODO we load the data into temp memory and give a pointer to it.
-					debug::log(debug::Level::error, "Tried to load staged asset - no one wrote that yet!");
-					break;
-				case LoadType::PATH: config.first(this, guid, AssetData(&path));
-					break;
+				auto factoryItr = m_factories.find(type);
+				if ( factoryItr == m_factories.end() ) {
+					debug::error("attempted to load asset with unknown type: {}", type.get());
+					return;
+				}
+
+				const auto& [callback, callbackType] = factoryItr->second;
+				switch (callbackType) {
+					case LoadType::DIRECT:
+						// TODO we load the data into main memory and give a pointer to it.
+						debug::log(debug::Level::error, "Tried to load direct asset - no one wrote that yet!");
+						break;
+					case LoadType::STAGED:
+						// TODO we load the data into temp memory and give a pointer to it.
+						debug::log(debug::Level::error, "Tried to load staged asset - no one wrote that yet!");
+						break;
+					case LoadType::PATH:
+						callback(this, guid, AssetData(&path), userPtr);
+						break;
 				}
 			}
 
diff --git a/include/fggl/assets/types.hpp b/include/fggl/assets/types.hpp
index 7d84ddfc7f938555c2d87f6b2ae705a28dc259ce..4b93825aee0042a3cbf81d599c93f2f2cef0ac0b 100644
--- a/include/fggl/assets/types.hpp
+++ b/include/fggl/assets/types.hpp
@@ -48,7 +48,7 @@ namespace fggl::assets {
 
 	class Loader;
 	using AssetData = std::variant<MemoryBlock, AssetPath *, FILE *>;
-	using Checkin = std::function<AssetRefRaw(Loader* loader, const AssetGUID &, const AssetData &)>;
+	using Checkin = std::function<AssetRefRaw(Loader* loader, const AssetGUID &, const AssetData &, void* userPtr)>;
 }
 
 #endif //FGGL_ASSETS_TYPES_HPP
diff --git a/include/fggl/entity/entity.hpp b/include/fggl/entity/entity.hpp
index 0843299fc79be9dcbf7ba16c72bedebf7b9a6f60..cb1527e30ca8bd87129cf3a0e1f6e3d1db7d3050 100644
--- a/include/fggl/entity/entity.hpp
+++ b/include/fggl/entity/entity.hpp
@@ -20,6 +20,8 @@
 #define FGGL_ENTITY_ENTITY_HPP
 
 #include <cstdint>
+#include <map>
+#include <vector>
 
 #include "fggl/debug/logging.hpp"
 #include "fggl/vendor/entt.hpp"
@@ -41,7 +43,7 @@ namespace fggl::entity {
 
 			template<typename Component, typename... Args>
 			inline Component &add(EntityID entity, Args &&... args) {
-				return m_registry.emplace<Component>(entity, std::forward<Args>(args)...);
+				return m_registry.get_or_emplace<Component>(entity, std::forward<Args>(args)...);
 			}
 
 			template<typename Component>
@@ -75,21 +77,74 @@ namespace fggl::entity {
 				return m_registry.view<Components...>();
 			}
 
+			EntityID findByName(const std::string& name) const {
+				auto itr = m_names.find(name);
+				if ( itr == m_names.end() ){
+					return INVALID;
+				}
+				return m_registry.valid(itr->second) ? itr->second : INVALID;
+			}
+
+			inline void setName(const std::string& name, EntityID eid ) {
+				if ( eid == INVALID ) {
+					m_names.erase(name);
+				} else {
+					assert(m_registry.valid(eid));
+					m_names[name] = eid;
+				}
+			}
+
 			template<typename ...Components>
 			bool has(EntityID idx) const {
 				return m_registry.template all_of<Components...>(idx);
 			}
 
-			inline bool exists(EntityID idx) {
+			bool hasTag(EntityID entity, fggl::util::GUID tag) {
+				const auto mapItr = m_tags.find( tag );
+				if ( mapItr == m_tags.end() || !m_registry.valid(entity) ) {
+					return false;
+				}
+				return std::find(mapItr->second.begin(), mapItr->second.end(), entity) != mapItr->second.end();
+			}
+
+			void addTag(const EntityID entity, const fggl::util::GUID tag) {
+				assert( m_registry.valid(entity) );
+				auto& tagged = m_tags[ tag ];
+				tagged.push_back( entity );
+			}
+
+			void removeTag(const EntityID entity, const fggl::util::GUID tag) {
+				auto mapItr = m_tags.find(tag);
+				if ( mapItr == m_tags.end() ) {
+					return;
+				}
+				std::remove_if( mapItr->second.begin(), mapItr->second.end(), [entity](auto other) { return other == entity;} );
+			}
+
+			inline std::vector<EntityID> findByTag(const char* tag) {
+				return findByTag( util::make_guid_rt(tag) );
+			}
+
+			std::vector<EntityID> findByTag(const fggl::util::GUID tag){
+				auto mapItr = m_tags.find(tag);
+				if ( mapItr == m_tags.end() ) {
+					return {};
+				}
+				return mapItr->second;
+			}
+
+			inline bool exists(EntityID idx) const {
 				return m_registry.valid(idx);
 			}
 
-			inline bool alive(EntityID idx) {
+			inline bool alive(EntityID idx) const {
 				return m_registry.valid(idx);
 			}
 
 		private:
 			entt::registry m_registry;
+			std::map<std::string, EntityID> m_names;
+			std::map<fggl::util::GUID, std::vector<EntityID>> m_tags;
 	};
 
 	struct Entity {
diff --git a/include/fggl/entity/loader/loader.hpp b/include/fggl/entity/loader/loader.hpp
index a23493619b97beba9bfe05a2db3a48d7b5169d77..9a816dda7acd1869f15a7dfdb0362638683c2c23 100644
--- a/include/fggl/entity/loader/loader.hpp
+++ b/include/fggl/entity/loader/loader.hpp
@@ -32,6 +32,8 @@
 namespace fggl::entity {
 
 	constexpr auto PROTOTYPE_ASSET = assets::AssetType::make("entity_prototype");
+	constexpr auto SCENE = assets::AssetType::make("entity_scene");
+
 	using FactoryFunc = std::function<void(const ComponentSpec &config, EntityManager &, const EntityID &, modules::Services &svc)>;
 	using CustomiseFunc = std::function<void(EntityManager &, const EntityID &)>;
 
@@ -66,12 +68,22 @@ namespace fggl::entity {
 					finisher( manager, entity );
 				}
 
+				// use metadata to finalise
+				processTags(manager, entity, spec);
+
 				return entity;
 			}
 
+			void processTags(EntityManager& manager, EntityID id, EntityType spec) {
+				auto type = m_prototypes.at(spec);
+				for ( auto& tag : type.tags ) {
+					manager.addTag(id, tag);
+				}
+			}
+
 			void log_known_types() const {
 				debug::debug("dumping known types:");
-				for(auto& [k,v] : m_factories) {
+				for(const auto& [k,v] : m_factories) {
 					debug::debug("\ttype: {}", k);
 				}
 			}
@@ -90,6 +102,10 @@ namespace fggl::entity {
 				m_factories.erase(configNode);
 			}
 
+			inline FactoryInfo& getInfo(ComponentID comp) {
+				return m_factories.at(comp);
+			}
+
 		private:
 			modules::Services m_services;
 			std::map<ComponentID, FactoryInfo> m_factories;
@@ -158,6 +174,7 @@ namespace fggl::entity {
 	};
 
 	assets::AssetRefRaw load_prototype(assets::Loader* loader, EntityFactory *factory, const assets::AssetGUID &guid, assets::AssetData data);
+	assets::AssetRefRaw load_scene(assets::Loader* loader, const assets::AssetGUID& asset, assets::AssetData data, void* ptr);
 
 } // namespace fggl::entity
 
diff --git a/include/fggl/entity/loader/spec.hpp b/include/fggl/entity/loader/spec.hpp
index 749fa4cc2585ea3ad5e676613dc14f455912098c..08190b97dc50ea3fa4faf2b92a23d8a9b0ad254b 100644
--- a/include/fggl/entity/loader/spec.hpp
+++ b/include/fggl/entity/loader/spec.hpp
@@ -51,6 +51,7 @@ namespace fggl::entity {
 
 	struct EntitySpec {
 		EntityType parent = NO_PARENT;
+		std::vector<util::GUID> tags;
 		std::vector<ComponentID> ordering;
 		std::map<ComponentID, ComponentSpec> components;
 
diff --git a/include/fggl/modules/manager.hpp b/include/fggl/modules/manager.hpp
index deb5f14a418d766437781137b42471a1227e8948..61fa6e05f496db3c726ce1b64e364fb1ea777efb 100644
--- a/include/fggl/modules/manager.hpp
+++ b/include/fggl/modules/manager.hpp
@@ -235,6 +235,10 @@ namespace fggl::modules {
 				m_locked = true;
 			}
 
+			inline Services& services() {
+				return m_services;
+			}
+
 		private:
 			bool m_locked = false;
 			Services m_services;
diff --git a/include/fggl/scenes/game.hpp b/include/fggl/scenes/game.hpp
index 1a451d3f35f0ec522784182cd081cfffd77531ae..21f35cfddab44e6b75e03a4aae94321d6c2f20c3 100644
--- a/include/fggl/scenes/game.hpp
+++ b/include/fggl/scenes/game.hpp
@@ -58,11 +58,12 @@ namespace fggl::scenes {
 			void update(float dt) override;
 			void render(fggl::gfx::Graphics &gfx) override;
 
-		protected:
 			inline auto world() -> entity::EntityManager & {
 				return *m_world;
 			}
 
+
+		protected:
 			bool hasPhys() const {
 				return m_phys != nullptr;
 			}
diff --git a/include/fggl/util/guid.hpp b/include/fggl/util/guid.hpp
index 2b6556058c44e592518bd5d45a96fa317c9ce20a..03b52b5e69056e49df462f581b76b40836fea3a4 100644
--- a/include/fggl/util/guid.hpp
+++ b/include/fggl/util/guid.hpp
@@ -77,14 +77,18 @@ namespace fggl::util {
 		return GUID(hash_fnv1a_64(str));
 	}
 
-	inline GUID make_guid_rt(const std::string &str) {
+	inline GUID make_guid_rt(const char* str) {
 		#ifndef NDEBUG
-		return internString(str.c_str());
+		return internString(str);
 		#else
-		return make_guid(str.c_str());
+		return make_guid(str);
 		#endif
 	}
 
+	inline GUID make_guid_rt(const std::string &str) {
+		return make_guid_rt(str.c_str());
+	}
+
 } // namespace fggl::util
 
 // formatter