diff --git a/build.sh b/build.sh
index 7fafd562084fbe0192f9a0dba47fed74bc298fc2..3f75f29674e9eb5bf125c48d4ac7b4f292f39241 100755
--- a/build.sh
+++ b/build.sh
@@ -4,11 +4,13 @@ CACHE=/tmp/fggl/
 LOG=$CACHE/demo.log
 EXE="../builds/cli/demo/FgglDemo"
 
+# check build directory exists
 if [[ ! -d "builds/cli/" ]]
 then
 	mkdir -p builds/cli
 fi
 
+# check cmake exists
 if [ ! -x "$(command -v cmake)" ]; then
 	sudo dnf install -y cmake extra-cmake-modules
 	sudo dnf install -y wayland-devel libxkbcommon-devel wayland-protocols-devel
@@ -23,7 +25,18 @@ rm -rf $CACHE
 #
 pushd builds/cli
 cmake ../..
+if [ $? -ne 0 ]
+then
+    echo "[!] Cmake failed"
+    exit 1
+fi
+
 make
+if [ $? -ne 0 ]
+then
+    echo "[!] make failed"
+    exit 1
+fi
 popd
 
 #
diff --git a/fggl/CMakeLists.txt b/fggl/CMakeLists.txt
index 95c97ae9920451608b3cbbd16e2800b5c43b274b..1b0a56bc2965fd10572a66aef9eb6dd20b25d353 100644
--- a/fggl/CMakeLists.txt
+++ b/fggl/CMakeLists.txt
@@ -1,5 +1,6 @@
 configure_file(FgglConfig.h.in FgglConfig.h)
 
+# clang tidy
 find_program(CLANG_TIDY_FOUND clang-tidy)
 if ( CLANG_TIDY_FOUND )
 	set(CMAKE_CXX_CLANG_TIDY clang-tidy -checks=*,-llvmlibc-*,-fuchsia-*,-cppcoreguidelines-*,-android-*,-llvm-*,-altera-*,-modernize-use-trailing-return-type)
@@ -14,7 +15,13 @@ add_library(fggl fggl.cpp
 	ecs3/prototype/world.cpp
 	scenes/Scene.cpp
 	ecs3/module/module.cpp
-	input/camera_input.cpp data/heightmap.cpp)
+	input/camera_input.cpp
+    data/heightmap.cpp
+)
+
+# spdlog for cleaner logging
+find_package(spdlog REQUIRED)
+target_link_libraries(fggl spdlog::spdlog)
 
 target_include_directories(fggl PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../)
 
diff --git a/fggl/data/storage.hpp b/fggl/data/storage.hpp
index 9e2ba223128143086546dc552e91e9cfc04d08e9..c1cea22e1f5e49098a48012ea60e6cc39f518c00 100644
--- a/fggl/data/storage.hpp
+++ b/fggl/data/storage.hpp
@@ -5,6 +5,8 @@
 #include <string>
 #include <filesystem>
 
+#include <spdlog/spdlog.h>
+
 namespace fggl::data {
 
 	template<typename T>
@@ -21,7 +23,7 @@ namespace fggl::data {
 			bool load(StorageType pool, const std::string& name, T* out) {
 				auto path = resolvePath(pool, name);
 				if ( !std::filesystem::exists(path) ) {
-					std::cerr << "path " << path << " does not exist! " << std::endl;
+                    spdlog::warn("Path {} does not exist!", path.c_str());
 					return false;
 				}
 				return fggl_deserialize<T>(path, out);
diff --git a/fggl/ecs3/module/module.h b/fggl/ecs3/module/module.h
index 68801fbfd3da369976bb721da04fa23819a503f5..f07828b1edb6cddbe3cfe3c3e9dc0d7c46928f6c 100644
--- a/fggl/ecs3/module/module.h
+++ b/fggl/ecs3/module/module.h
@@ -8,6 +8,7 @@
 #include <string>
 #include <map>
 #include <memory>
+#include <spdlog/spdlog.h>
 
 #include <fggl/ecs3/types.hpp>
 
@@ -33,7 +34,7 @@ namespace fggl::ecs3 {
                 m_modules[ptr->name()] = ptr;
 
                 ptr->onLoad(*this, m_types);
-                std::cerr << "module loaded: " << ptr->name() << std::endl;
+                spdlog::info("loaded ECS module: {}", ptr->name());
                 return ptr;
             }
 
diff --git a/fggl/ecs3/prototype/world.h b/fggl/ecs3/prototype/world.h
index 5111928fb674e4b1642a1ca30c996be18ce19ff7..8a4266de4426e3abf745d5d75b4bea3a79d0bdbe 100644
--- a/fggl/ecs3/prototype/world.h
+++ b/fggl/ecs3/prototype/world.h
@@ -22,7 +22,7 @@ namespace fggl::ecs3::prototype {
 
             explicit Entity(entity_t id) : m_id(id), m_abstract(false) {};
             Entity(const Entity& entity) : m_id(entity.m_id), m_components(entity.m_components) {
-                std::cerr << "someone be doing one of them copy things: " << m_id << std::endl;
+                spdlog::info("entity created fro copy: {}", m_id);
             }
             ~Entity() = default;
 
@@ -163,7 +163,7 @@ namespace fggl::ecs3::prototype {
 
             template<typename C>
             C* add(entity_t entity_id) {
-                std::cerr << "[>>] adding comp " << C::name << " to " << entity_id << std::endl;
+                spdlog::info("component '{}' added to '{}'", C::name, entity_id);
                 auto& entity = m_entities.at(entity_id);
                 auto comp = entity.template add<C>();
 
@@ -182,7 +182,7 @@ namespace fggl::ecs3::prototype {
 
             template<typename C>
             C* set(entity_t entity_id, const C* ptr) {
-                std::cerr << "[>>] setting comp " << C::name << " to " << entity_id << std::endl;
+                spdlog::info("component '{}' set on '{}'", C::name, entity_id);
                 auto& entity = m_entities.at(entity_id);
                 auto comp = entity.set<C>(ptr);
 
@@ -219,4 +219,4 @@ namespace fggl::ecs3::prototype {
 
 }
 
-#endif //FGGL_ECS3_PROTOTYPE_WORLD_H
\ No newline at end of file
+#endif //FGGL_ECS3_PROTOTYPE_WORLD_H
diff --git a/fggl/ecs3/types.hpp b/fggl/ecs3/types.hpp
index fb0fe4f2cc8f076ea00cb4325f29771d48f51a83..50e5465008e95832948ac88ff2c5d3f9686efca8 100644
--- a/fggl/ecs3/types.hpp
+++ b/fggl/ecs3/types.hpp
@@ -12,6 +12,7 @@
 #include <algorithm>
 #include <map>
 #include <unordered_map>
+#include <spdlog/spdlog.h>
 
 namespace fggl::ecs3 {
 
@@ -202,7 +203,7 @@ namespace fggl::ecs3 {
                         callback(world, entity);
                     }
                 } catch ( std::out_of_range& e) {
-                    std::cerr << "no callbacks for: " << m_types[type]->name() << std::endl;
+                    spdlog::debug("no callbacks for {}", m_types[type]->name());
                 }
             }
 
diff --git a/fggl/gfx/ogl/backend.cpp b/fggl/gfx/ogl/backend.cpp
index 3a864ade4bab0200eefb2f9a8b660eaaaa0eb4f2..96a9c97988357c78d69cbf0dc7dffa4a6e67e440 100644
--- a/fggl/gfx/ogl/backend.cpp
+++ b/fggl/gfx/ogl/backend.cpp
@@ -1,11 +1,12 @@
 #include <fggl/gfx/ogl/backend.hpp>
 
 #include <stdexcept>
+#include <spdlog/spdlog.h>
 
 using namespace fggl::gfx;
 
 GlGraphics::GlGraphics(const std::shared_ptr<Window> window) : m_window(window) {
-    std::cerr << "[OGL] attaching window context" << std::endl;
+    spdlog::debug("[OGL] attaching window context");
 	window->activate();
 	GLenum err = glewInit();
 	if ( GLEW_OK != err ) {
@@ -13,11 +14,11 @@ GlGraphics::GlGraphics(const std::shared_ptr<Window> window) : m_window(window)
 	}
 
 	glViewport(0, 0, 1920, 1080);
-    std::cerr << "[OGL] window ready!" << std::endl;
+    spdlog::debug("[OGL] window context ready");
 }
 
 GlGraphics::~GlGraphics() {
-    std::cerr << "[GLFW] gl context killed!" << std::endl;
+    spdlog::debug("[OGL] gl window context killed!");
 }
 
 void GlGraphics::clear() {
diff --git a/fggl/gfx/ogl/compat.hpp b/fggl/gfx/ogl/compat.hpp
index 6108dadf6a4e102e08f17567a920ccf14e3ae587..0fd4100e2acb24ae744f24ef5cef96fa476072dd 100644
--- a/fggl/gfx/ogl/compat.hpp
+++ b/fggl/gfx/ogl/compat.hpp
@@ -107,7 +107,7 @@ namespace fggl::gfx {
 	// fake module/callbacks - our ECS doesn't have module/callback support yet.
 	// 
 	void onStaticMeshAdded(ecs3::World& ecs, ecs::entity_t entity, OglModule& mod) {
-        std::cerr << "[CALLBACK] static mesh added, renderable?" << std::endl;
+        spdlog::info("[CALLBACK] static mesh added, renderable?");
         /*
 		auto meshData = ecs.get<gfx::StaticMesh>(entity);
 		auto pipeline = mod->cache.get(meshData->pipeline);
diff --git a/fggl/gfx/ogl/renderer.cpp b/fggl/gfx/ogl/renderer.cpp
index d869a6698cc214b7751287a05f1341fefb5f7252..f35a1a87a061fdb960769f23ed51b6fb98e325a4 100644
--- a/fggl/gfx/ogl/renderer.cpp
+++ b/fggl/gfx/ogl/renderer.cpp
@@ -1,4 +1,6 @@
 
+#include <spdlog/spdlog.h>
+
 #include <fggl/gfx/ogl/renderer.hpp>
 #include <fggl/data/model.hpp>
 
@@ -72,13 +74,13 @@ constexpr glm::vec3 DEFAULT_LIGHTPOS = glm::vec3(20.0F, 20.0F, 15.0F);
 
 void MeshRenderer::render(fggl::ecs3::World& ecs, ecs3::entity_t camera, float dt) {
     if ( camera == ecs::NULL_ENTITY ){
-        std::cerr << "no camera" << std::endl;
+        spdlog::warn("tried to render a scene, but no camera exists!");
         return;
     }
 
     auto entities = ecs.findMatching<GlRenderToken>();
     if ( entities.empty() ) {
-        std::cerr << "no entities with render tokens" << std::endl;
+        spdlog::warn("asked to render, but no entities are renderable");
         return;
     }
 
diff --git a/fggl/gfx/ogl/shader.cpp b/fggl/gfx/ogl/shader.cpp
index 3450b6350a1918aa1bf55b2ec44847d7c4c8ef20..0e922943feac0590d13126d598b1184020b47a8f 100644
--- a/fggl/gfx/ogl/shader.cpp
+++ b/fggl/gfx/ogl/shader.cpp
@@ -3,6 +3,7 @@
 
 #include <iostream>
 #include <vector>
+#include <spdlog/spdlog.h>
 
 using namespace fggl::gfx;
 
@@ -10,7 +11,7 @@ bool ShaderCache::compileShader(const std::string& fname, GLuint sid) {
 	std::string source;
 	bool result = m_storage->load(fggl::data::Data, fname, &source);
 	if ( !result ) {
-        std::cerr << ">> Error loading file: " << fname << std::endl;
+        spdlog::warn("could not load shader source from disk: {}", fname.c_str());
 		return false;
 	}
 
@@ -29,7 +30,7 @@ bool ShaderCache::compileShader(const std::string& fname, GLuint sid) {
 		char* infoLog = new char[maxLength];
 		glGetShaderInfoLog(sid, maxLength, &maxLength, infoLog);
 
-		std::cerr << "error compiling shader: " << infoLog << std::endl;
+        spdlog::warn("could not compile shader, '{}': {}", fname, infoLog);
 		delete[] infoLog;
 
 		return false;
@@ -41,7 +42,7 @@ bool ShaderCache::compileShader(const std::string& fname, GLuint sid) {
 ShaderCache::ShaderCache(std::shared_ptr<fggl::data::Storage> storage) : m_storage(storage), m_shaders(), m_binary(true) {
 
 	if ( !GLEW_ARB_get_program_binary ) {
-		std::cerr << "shader caching disabled, no card support" << std::endl;
+        spdlog::warn("the graphics card doesn't support shader caching, disabling");
 		m_binary = false;
 	}
 
@@ -54,7 +55,7 @@ bool ShaderCache::loadFromDisk(GLuint pid, const ShaderConfig& config) {
 	bool status = m_storage->load( fggl::data::Cache, fname, &cache );
 
 	if ( !status ) {
-		std::cerr << "loading shader from disk failed" << std::endl;
+        spdlog::info("cached shader '{}' could not be loaded from disk", config.name);
 		return false;
 	}
 
@@ -86,7 +87,7 @@ GLuint ShaderCache::get(const std::string& name) {
 
 GLuint ShaderCache::load(const ShaderConfig& config) {
 
-    std::cerr << "Starting program generation: " << config.name << std::endl;
+    spdlog::debug("starting shader program generation for {}", config.name);
 
 	GLuint pid = glCreateProgram();
 
@@ -98,7 +99,7 @@ GLuint ShaderCache::load(const ShaderConfig& config) {
 			return pid;
 		}
 
-		std::cerr << ">> could not load cached shader, taking the long route" << std::endl;
+        spdlog::debug("could not use cached shader for '{}', doing full compile.", config.name);
 	}
 
 	// TODO actual shader loading
@@ -128,12 +129,11 @@ GLuint ShaderCache::load(const ShaderConfig& config) {
 	GLint linked = GL_FALSE;
 	glGetProgramiv(pid, GL_LINK_STATUS, &linked);
 	if ( linked == GL_FALSE ) {
-		std::cerr << "linking failed" << std::endl;
 
 		// get the error
 		std::array<char, 512> infoLog;
 		glGetProgramInfoLog(pid, infoLog.size(), nullptr, infoLog.data());
-		std::cerr << infoLog.data() << std::endl;
+        spdlog::warn("linking shader program '{}' failed: {}", config.name, infoLog.data());
 
 		// cleanup
 		glDeleteProgram( pid );
@@ -181,13 +181,13 @@ template<>
 bool fggl::data::fggl_deserialize(std::filesystem::path &data, fggl::gfx::BinaryCache *out) {
 	auto f = std::fopen( data.c_str(), "r");
 	if ( f == nullptr ) {
-		std::cerr << "fp was null..." << std::endl;
+        spdlog::warn("could not load cached shader, fp was null");
 		return false;
 	}
 
 	auto rsize = std::fread( &out->format, sizeof(GLenum), 1, f);
 	if ( rsize != 1 ) {
-		std::cerr << "invalid read of type";
+        spdlog::warn("could not load cached shader: type read failed");
 		std::fclose(f);
 		return false;
 	}
@@ -195,7 +195,7 @@ bool fggl::data::fggl_deserialize(std::filesystem::path &data, fggl::gfx::Binary
 	out->size = 0;
 	rsize = std::fread( &out->size, sizeof(GLsizei), 1, f);
 	if ( rsize != 1 ) {
-		std::cerr << "invalid read of size" << std::endl;
+        spdlog::warn("could not load cached shader: size read failed");
 		std::fclose(f);
 		return false;
 	}
@@ -205,7 +205,7 @@ bool fggl::data::fggl_deserialize(std::filesystem::path &data, fggl::gfx::Binary
 
 	auto result = true;
 	if ( readSize != 1 ) {
-		std::cerr << "invalid read of data" << std::endl;
+        spdlog::warn("could not load cached shader: reading failed!");
 		std::free( out->data );
 		result = false;
 	}
diff --git a/fggl/gfx/window.cpp b/fggl/gfx/window.cpp
index ffd8d4c7649fcec458097eb54e3220db7a755a60..d62ff14477b6a31e24e620fcd89271beac83ecf5 100644
--- a/fggl/gfx/window.cpp
+++ b/fggl/gfx/window.cpp
@@ -5,6 +5,7 @@
 #include <string>
 #include <stdexcept>
 #include <utility>
+#include <spdlog/spdlog.h>
 
 using namespace fggl::gfx;
 
@@ -19,44 +20,44 @@ void APIENTRY glDebugOutput(GLenum source,
     // ignore non-significant error/warning codes
     if(id == 131169 || id == 131185 || id == 131218 || id == 131204) return;
 
-    std::cerr << "---------------" << std::endl;
-    std::cout << "Debug message (" << id << "): " <<  message << std::endl;
+    std::string fmt = "[GL] {}, {}: [{}]: {}";
+    std::string sourceStr = "unknown";
+    std::string typeStr = "unknown";
 
     switch (source)
     {
-        case GL_DEBUG_SOURCE_API:             std::cout << "Source: API"; break;
-        case GL_DEBUG_SOURCE_WINDOW_SYSTEM:   std::cout << "Source: Window System"; break;
-        case GL_DEBUG_SOURCE_SHADER_COMPILER: std::cout << "Source: Shader Compiler"; break;
-        case GL_DEBUG_SOURCE_THIRD_PARTY:     std::cout << "Source: Third Party"; break;
-        case GL_DEBUG_SOURCE_APPLICATION:     std::cout << "Source: Application"; break;
-        case GL_DEBUG_SOURCE_OTHER:           std::cout << "Source: Other"; break;
-    } std::cout << std::endl;
+        case GL_DEBUG_SOURCE_API:             sourceStr = "API"; break;
+        case GL_DEBUG_SOURCE_WINDOW_SYSTEM:   sourceStr = "Window System"; break;
+        case GL_DEBUG_SOURCE_SHADER_COMPILER: sourceStr = "Shader Compiler"; break;
+        case GL_DEBUG_SOURCE_THIRD_PARTY:     sourceStr = "Third Party"; break;
+        case GL_DEBUG_SOURCE_APPLICATION:     sourceStr = "Application"; break;
+        case GL_DEBUG_SOURCE_OTHER:           sourceStr = "Other"; break;
+    }
 
     switch (type)
     {
-        case GL_DEBUG_TYPE_ERROR:               std::cout << "Type: Error"; break;
-        case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: std::cout << "Type: Deprecated Behaviour"; break;
-        case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:  std::cout << "Type: Undefined Behaviour"; break;
-        case GL_DEBUG_TYPE_PORTABILITY:         std::cout << "Type: Portability"; break;
-        case GL_DEBUG_TYPE_PERFORMANCE:         std::cout << "Type: Performance"; break;
-        case GL_DEBUG_TYPE_MARKER:              std::cout << "Type: Marker"; break;
-        case GL_DEBUG_TYPE_PUSH_GROUP:          std::cout << "Type: Push Group"; break;
-        case GL_DEBUG_TYPE_POP_GROUP:           std::cout << "Type: Pop Group"; break;
-        case GL_DEBUG_TYPE_OTHER:               std::cout << "Type: Other"; break;
-    } std::cout << std::endl;
+        case GL_DEBUG_TYPE_ERROR:               typeStr = "Error"; break;
+        case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: typeStr = "Deprecated Behaviour"; break;
+        case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:  typeStr = "Undefined Behaviour"; break;
+        case GL_DEBUG_TYPE_PORTABILITY:         typeStr = "Portability"; break;
+        case GL_DEBUG_TYPE_PERFORMANCE:         typeStr = "Performance"; break;
+        case GL_DEBUG_TYPE_MARKER:              typeStr = "Marker"; break;
+        case GL_DEBUG_TYPE_PUSH_GROUP:          typeStr = "Push Group"; break;
+        case GL_DEBUG_TYPE_POP_GROUP:           typeStr = "Pop Group"; break;
+        case GL_DEBUG_TYPE_OTHER:               typeStr = "Other"; break;
+    }
 
     switch (severity)
     {
-        case GL_DEBUG_SEVERITY_HIGH:         std::cout << "Severity: high"; break;
-        case GL_DEBUG_SEVERITY_MEDIUM:       std::cout << "Severity: medium"; break;
-        case GL_DEBUG_SEVERITY_LOW:          std::cout << "Severity: low"; break;
-        case GL_DEBUG_SEVERITY_NOTIFICATION: std::cout << "Severity: notification"; break;
-    } std::cout << std::endl;
-    std::cout << std::endl;
+        case GL_DEBUG_SEVERITY_HIGH:         spdlog::error(fmt, sourceStr, typeStr, id, message); break;
+        case GL_DEBUG_SEVERITY_MEDIUM:       spdlog::warn(fmt, sourceStr, typeStr, id, message); break;
+        case GL_DEBUG_SEVERITY_LOW:          spdlog::info(fmt, sourceStr, typeStr, id, message); break;
+        case GL_DEBUG_SEVERITY_NOTIFICATION: spdlog::debug(fmt, sourceStr, typeStr, id, message); break;
+    }
 }
 
 static void glfw_error(int code, const char* description) {
-	std::cerr << "[GLFW] " << code << " " << description << std::endl;
+    spdlog::warn("[GLFW error] {}: {}", code, description);
 }
 
 static void framebuffer_resize(GLFWwindow* window, int width, int height) {
@@ -155,7 +156,7 @@ static void fggl_joystick(int jid, int state) {
 }
 
 GlfwContext::GlfwContext( std::shared_ptr<fggl::input::Input> input ) {
-    std::cerr << "[GLFW] glfw init started" << std::endl;
+    spdlog::debug("[glfw] context creation stated");
 	auto& glfwCallbacks = GlfwInputManager::instance();
 	glfwCallbacks.setup(std::move(input) );
 
@@ -172,12 +173,12 @@ GlfwContext::GlfwContext( std::shared_ptr<fggl::input::Input> input ) {
 	// joysticks are global
 	fggl_joystick_poll();
 	glfwSetJoystickCallback(fggl_joystick);
-    std::cerr << "[GLFW] glfw init complete" << std::endl;
+    spdlog::debug("[glfw] context creation complete");
 }
 
 GlfwContext::~GlfwContext() {
 	glfwTerminate();
-    std::cerr << "[GLFW] glfw context killed!" << std::endl;
+    spdlog::debug("[glfw] context terminated");
 }
 
 void GlfwContext::pollEvents() {
@@ -186,7 +187,7 @@ void GlfwContext::pollEvents() {
 }
 
 Window::Window() : m_window(nullptr), m_framesize() {
-	std::cerr << "creating window" << std::endl;
+    spdlog::debug("[glfw] creating window");
 	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
 	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
 	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
@@ -199,7 +200,7 @@ Window::Window() : m_window(nullptr), m_framesize() {
 
 	activate();
 	auto result = glewInit();
-	std::cerr << "glew result: " << (result == GLEW_OK) << std::endl;
+    spdlog::debug("[glfw] glew result: {}", result == GLEW_OK);
 
 	m_framesize = glm::vec2(1920, 1080);
 	glfwSetWindowUserPointer(m_window, this);
@@ -218,7 +219,8 @@ Window::Window() : m_window(nullptr), m_framesize() {
 		glDebugMessageCallback(glDebugOutput, "DEBUG MODE");
 		glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
 	}
-	std::cerr << "window creation complete" << std::endl;
+
+    spdlog::debug("[glfw] window creation complete");
 }
 
 Window::~Window() {