diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt
index f3e4756e4cabce8894b39bd629b6aae7b5b2c7d3..e2efa8dad686c63c6d8ba985a602d44589df2970 100644
--- a/demo/CMakeLists.txt
+++ b/demo/CMakeLists.txt
@@ -1,5 +1,8 @@
 # Executable
-add_executable(FgglDemo main.cpp)
+add_executable(FgglDemo
+        main.cpp
+        GameScene.cpp
+        )
 
 target_link_libraries(FgglDemo fggl)
 #target_include_directories(FgglDemo PUBLIC ${PROJECT_BINARY_DIR})
diff --git a/demo/GameScene.cpp b/demo/GameScene.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e2b176e72d902e796005b403baf94dfc41f87375
--- /dev/null
+++ b/demo/GameScene.cpp
@@ -0,0 +1,203 @@
+/*
+ * ${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 22/04/22.
+//
+
+#include "GameScene.h"
+
+camera_type cam_mode = cam_free;
+
+static void placeObject(fggl::ecs3::World& world, fggl::ecs::entity_t parent, fggl::ecs::entity_t prototype, glm::vec3 targetPos) {
+	auto obj = world.copy(prototype);
+	auto result = world.get<fggl::math::Transform>(obj);
+
+	int xPos = (int)targetPos.x;
+	int zPos = (int)targetPos.z * -1;
+
+	// figure out the floor height
+	auto heightMap = world.get<fggl::data::HeightMap>(parent);
+	targetPos.y = heightMap->getValue(xPos, zPos); // TODO should really be the gradient at the required point
+
+	result->origin( targetPos );
+}
+
+static void process_camera(fggl::ecs3::World& ecs, const std::shared_ptr<fggl::input::Input>& input) {
+	auto cameras = ecs.findMatching<fggl::gfx::Camera>();
+	fggl::ecs3::entity_t cam = cameras[0];
+
+	auto camTransform = ecs.get<fggl::math::Transform>(cam);
+	auto camComp = ecs.get<fggl::gfx::Camera>(cam);
+
+	const glm::vec3 dir = ( camTransform->origin() - camComp->target );
+	const glm::vec3 forward = glm::normalize( dir );
+
+	// scroll wheel
+	glm::vec3 motion(0.0f);
+	float delta = input->mouse.axis( fggl::input::MouseAxis::SCROLL_Y );
+	if ( (glm::length( dir ) < 25.0f && delta < 0.0f) || (glm::length( dir ) > 2.5f && delta > 0.0f) )
+		motion -= (forward * delta);
+	camTransform->origin( camTransform->origin() + motion );
+
+	if ( cam_mode == cam_arcball || input->mouse.down( fggl::input::MouseButton::MIDDLE ) ) {
+		fggl::input::process_arcball(ecs, *input, cam);
+	} else if ( cam_mode == cam_free ) {
+		fggl::input::process_freecam(ecs, *input, cam);
+	}
+	fggl::input::process_edgescroll( ecs, *input, cam );
+}
+
+void GameScene::setup() {
+	m_canvas.size( fggl::math::vec2(0,0), fggl::math::vec2(100, 100));
+
+	auto types = m_world->types();
+
+	// create camera using strings
+	{
+		auto prototype = m_world->create(false);
+		m_world->add(prototype, types.find(fggl::math::Transform::name));
+		m_world->add(prototype, types.find(fggl::gfx::Camera::name));
+		m_world->add(prototype, types.find(fggl::input::FreeCamKeys::name));
+
+		auto camTf = m_world->get<fggl::math::Transform>(prototype);
+		if ( camTf != nullptr) {
+			camTf->origin(glm::vec3(10.0f, 3.0f, 10.0f));
+		}
+
+		auto cameraKeys = m_world->get<fggl::input::FreeCamKeys>(prototype);
+		if ( cameraKeys != nullptr ) {
+			cameraKeys->forward = glfwGetKeyScancode(GLFW_KEY_W);
+			cameraKeys->backward = glfwGetKeyScancode(GLFW_KEY_S);
+			cameraKeys->left = glfwGetKeyScancode(GLFW_KEY_A);
+			cameraKeys->right = glfwGetKeyScancode(GLFW_KEY_D);
+			cameraKeys->rotate_cw = glfwGetKeyScancode(GLFW_KEY_Q);
+			cameraKeys->rotate_ccw = glfwGetKeyScancode(GLFW_KEY_E);
+		}
+	}
+
+	fggl::ecs3::entity_t terrain;
+	{
+		terrain = m_world->create(false);
+		m_world->add(terrain, types.find(fggl::math::Transform::name));
+
+		auto camTf = m_world->get<fggl::math::Transform>(terrain);
+		camTf->origin( glm::vec3(0.0f, 0.0f, 0.0f) );
+
+		//auto terrainData = m_world.get<fggl::data::HeightMap>(terrain);
+		fggl::data::HeightMap terrainData{};
+		terrainData.clear();
+
+		const siv::PerlinNoise::seed_type seed = 123456u;
+		const siv::PerlinNoise perlin{ seed };
+
+		for (int y = 0; y < 255; ++y) {
+			for (int x = 0; x < 255; ++x) {
+				const double noise = perlin.octave2D_11( (x * 0.01), (y * 0.01) , 4) * 10.f;
+				terrainData.heightValues[x * 255 +y] = (float)noise;
+			}
+		}
+		m_world->set<fggl::data::HeightMap>(terrain, &terrainData);
+	}
+
+	// create foundation object
+	fggl::ecs3::entity_t foundation;
+	{
+		foundation = m_world->create(true);
+		m_world->add(foundation, types.find(fggl::math::Transform::name));
+
+		// plot rendering
+		fggl::data::Mesh mesh;
+		fggl::data::make_cube(mesh);
+		mesh.removeDups();
+
+		// add mesh as a component
+		constexpr char shader[] = "phong";
+		fggl::data::StaticMesh staticMesh{mesh, shader};
+		m_world->set<fggl::data::StaticMesh>(foundation, &staticMesh);
+	}
+
+	// create building prototype
+	fggl::ecs3::entity_t bunker;
+	{
+		bunker = m_world->create(true);
+		m_world->add(bunker, types.find(fggl::math::Transform::name));
+
+		// mesh
+		int nSections = 2;
+		constexpr float HALF_PI = M_PI / 2.0f;
+		constexpr char shader[] = "phong";
+
+		fggl::data::Mesh mesh;
+		for (int j=-(nSections/2); j<=nSections/2; j++) {
+			const auto shapeOffset = glm::vec3( 0.0f, 0.5f, (float)j * 1.0f );
+
+			const auto cubeMat = glm::translate( fggl::math::mat4( 1.0f ) , shapeOffset );
+			const auto leftSlope = fggl::math::modelMatrix(
+				glm::vec3(-1.0f, 0.0f, 0.0f) + shapeOffset,
+				glm::vec3( 0.0f, -HALF_PI, 0.0f) );
+			const auto rightSlope = fggl::math::modelMatrix(
+				glm::vec3( 1.0f, 0.0f, 0.0f) + shapeOffset,
+				glm::vec3( 0.0f, HALF_PI, 0.0f) );
+
+			fggl::data::make_cube( mesh, cubeMat );
+			fggl::data::make_slope( mesh, leftSlope );
+			fggl::data::make_slope( mesh, rightSlope );
+		}
+		mesh.removeDups();
+
+		fggl::data::StaticMesh staticMesh{mesh, shader};
+		m_world->set<fggl::data::StaticMesh>(bunker, &staticMesh);
+	}
+
+	for (int i=0; i<3; ++i) {
+		glm::vec3 location(i * 6.5f + 1.0f, 0.0f, -7.0f);
+		placeObject(*m_world, terrain, foundation, location);
+	}
+
+	int nCubes = 3;
+	for ( int i=0; i<nCubes; i++ ) {
+		glm::vec3 location;
+		location.x = i * 6.f + 1.0f;
+		location.z = -5.0f + 1.0f;
+		placeObject(*m_world, terrain, bunker, location);
+	}
+}
+
+void GameScene::update() {
+process_camera(*m_world, m_inputs);
+}
+
+void GameScene::render(fggl::gfx::Graphics &gfx) {
+
+		// render the 3D scene
+		if ( m_world != nullptr ) {
+			gfx.drawScene( *m_world );
+		}
+
+		const fggl::math::vec2 panelSize { 250.0F, 250.0F };
+		const auto canvasY = gfx.canvasBounds().bottom - panelSize.y;
+		m_canvas.size( {0.0F, canvasY}, panelSize);
+
+		// now the 2D scene
+		fggl::gfx::Paint paint;
+		m_canvas.render(paint);
+		gfx.draw2D(paint);
+}
\ No newline at end of file
diff --git a/demo/GameScene.h b/demo/GameScene.h
new file mode 100644
index 0000000000000000000000000000000000000000..90bf636c8727cf7cfe768b69e11f44a7e5920c98
--- /dev/null
+++ b/demo/GameScene.h
@@ -0,0 +1,77 @@
+/*
+ * ${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 22/04/22.
+//
+
+#ifndef FGGL_DEMO_GAMESCENE_H
+#define FGGL_DEMO_GAMESCENE_H
+
+#include <fggl/app.hpp>
+#include <fggl/gui/gui.hpp>
+
+#include <fggl/data/heightmap.h>
+#include <fggl/data/procedural.hpp>
+
+#include <fggl/gfx/camera.hpp>
+#include <fggl/input/input.hpp>
+#include <fggl/input/camera_input.h>
+
+#include <fggl/util/service.h>
+#include <fggl/util/chrono.hpp>
+
+#include <PerlinNoise.hpp>
+
+enum camera_type { cam_free, cam_arcball };
+
+class GameScene : public fggl::AppState {
+	public:
+		explicit GameScene(fggl::App& app) : fggl::AppState(app), m_world(nullptr), m_sceneTime(nullptr), m_inputs(nullptr) { };
+		//~GameScene() override = default;
+
+		void activate() override {
+			auto& locator = fggl::util::ServiceLocator::instance();
+			m_inputs = locator.providePtr<fggl::input::Input>();
+
+			// setup the world using the global type registry
+			// FIXME: type registry probably doesn't need to be application-global - will make savegame/mod support complicated
+			m_world = std::make_unique<fggl::ecs3::World>( *m_owner.registry() );
+
+			m_sceneTime = std::make_unique<fggl::util::Timer>();
+			m_sceneTime->frequency( glfwGetTimerFrequency() );
+			m_sceneTime->setup( glfwGetTimerValue() );
+
+			setup();
+		}
+
+		void setup();
+		void update() override;
+		void render(fggl::gfx::Graphics& gfx) override;
+
+	private:
+		std::unique_ptr<fggl::ecs3::World> m_world;
+		std::unique_ptr<fggl::util::Timer> m_sceneTime;
+		std::shared_ptr<fggl::input::Input> m_inputs;
+		fggl::gui::Panel m_canvas;
+};
+
+
+#endif //FGGL_DEMO_GAMESCENE_H
diff --git a/demo/main.cpp b/demo/main.cpp
index 9bc3aaee0d188f0b4c9375e2aac4928edcc4e270..589f45e8d124f5dca1b47f66af2ce348df3e5440 100644
--- a/demo/main.cpp
+++ b/demo/main.cpp
@@ -1,44 +1,27 @@
 #include <filesystem>
 #include <iostream>
 #include <memory>
-#include <utility>
 
 #include <fggl/app.hpp>
-#include <fggl/scenes/menu.hpp>
 
 #include <fggl/gfx/atlas.hpp>
 #include <fggl/gfx/window.hpp>
-#include <fggl/gfx/camera.hpp>
-
-#include "fggl/gfx/ogl/renderer.hpp"
-#include "fggl/gfx/ogl/compat.hpp"
 #include "fggl/gfx/compat.hpp"
-#include "fggl/gui/containers.hpp"
+#include "fggl/gfx/ogl/compat.hpp"
 
-#include <fggl/data/heightmap.h>
-#include <fggl/data/procedural.hpp>
 #include <fggl/data/storage.hpp>
-#include <fggl/util/chrono.hpp>
 #include <fggl/util/service.h>
 
 #include <fggl/ecs3/types.hpp>
-#include <fggl/ecs3/ecs.hpp>
-
-#include <fggl/input/camera_input.h>
-
-#include <fggl/debug/debug.h>
 
-// FIXME: imgui and perlinNoise shouldn't form part of our public API >.<
-#include <imgui.h>
-#include <PerlinNoise.hpp>
-
-constexpr bool showNormals = false;
+#include "fggl/scenes/menu.hpp"
+#include "GameScene.h"
 
 // prototype of resource discovery
 void discover(const std::filesystem::path& base) {
 
 	std::vector< std::filesystem::path > contentPacks;
-	for ( auto& item : std::filesystem::directory_iterator(base) ) {
+	for ( const auto& item : std::filesystem::directory_iterator(base) ) {
 
 		// content pack detection
 		if ( std::filesystem::is_directory( item ) ) {
@@ -58,287 +41,10 @@ void discover(const std::filesystem::path& base) {
 
 }
 
-enum camera_type { cam_free, cam_arcball };
-camera_type cam_mode = cam_free;
-
-//TODO proper input system
-using InputManager = std::shared_ptr<fggl::input::Input>;
-
-void process_camera(fggl::ecs3::World& ecs, const InputManager& input) {
-    auto cameras = ecs.findMatching<fggl::gfx::Camera>();
-    fggl::ecs3::entity_t cam = cameras[0];
-
-	auto camTransform = ecs.get<fggl::math::Transform>(cam);
-	auto camComp = ecs.get<fggl::gfx::Camera>(cam);
-
-	const glm::vec3 dir = ( camTransform->origin() - camComp->target );
-	const glm::vec3 forward = glm::normalize( dir );
-
-	// scroll wheel
-	glm::vec3 motion(0.0f);
-	float delta = input->mouse.axis( fggl::input::MouseAxis::SCROLL_Y );
-	if ( (glm::length( dir ) < 25.0f && delta < 0.0f) || (glm::length( dir ) > 2.5f && delta > 0.0f) )
-		motion -= (forward * delta);
-	camTransform->origin( camTransform->origin() + motion );
-
-	if ( cam_mode == cam_arcball || input->mouse.down( fggl::input::MouseButton::MIDDLE ) ) { 
-		fggl::input::process_arcball(ecs, *input, cam);
-	} else if ( cam_mode == cam_free ) {
-		fggl::input::process_freecam(ecs, *input, cam);
-	}
-	fggl::input::process_edgescroll( ecs, *input, cam );
-}
-
-void placeObject(fggl::ecs3::World& world, fggl::ecs::entity_t parent, fggl::ecs::entity_t prototype, glm::vec3 targetPos) {
-    auto obj = world.copy(prototype);
-    auto result = world.get<fggl::math::Transform>(obj);
-
-    int xPos = (int)targetPos.x;
-    int zPos = (int)targetPos.z * -1;
-
-    // figure out the floor height
-    auto heightMap = world.get<fggl::data::HeightMap>(parent);
-    targetPos.y = heightMap->getValue(xPos, zPos); // TODO should really be the gradient at the required point
-
-    result->origin( targetPos );
-}
-
-
-class GameScene : public fggl::AppState {
-public:
-    explicit GameScene(fggl::App& app) : fggl::AppState(app), m_world(nullptr), m_sceneTime(nullptr), m_inputs(nullptr) { };
-    //~GameScene() override = default;
-
-    void activate() override {
-        auto& locator = fggl::util::ServiceLocator::instance();
-        m_inputs = locator.providePtr<fggl::input::Input>();
-
-		// setup the world using the global type registry
-		// FIXME: type registry probably doesn't need to be application-global - will make savegame/mod support complicated
-        m_world = std::make_unique<fggl::ecs3::World>( *m_owner.registry() );
-
-        m_sceneTime = std::make_unique<fggl::util::Timer>();
-        m_sceneTime->frequency( glfwGetTimerFrequency() );
-        m_sceneTime->setup( glfwGetTimerValue() );
-
-        setup();
-    }
-
-    void setup() {
-		m_canvas.size( fggl::math::vec2(0,0), fggl::math::vec2(100, 100));
-
-        auto types = m_world->types();
-
-        // create camera using strings
-        {
-            auto prototype = m_world->create(false);
-            m_world->add(prototype, types.find(fggl::math::Transform::name));
-            m_world->add(prototype, types.find(fggl::gfx::Camera::name));
-            m_world->add(prototype, types.find(fggl::input::FreeCamKeys::name));
-
-            auto camTf = m_world->get<fggl::math::Transform>(prototype);
-			if ( camTf != nullptr) {
-				camTf->origin(glm::vec3(10.0f, 3.0f, 10.0f));
-			}
-
-            auto cameraKeys = m_world->get<fggl::input::FreeCamKeys>(prototype);
-			if ( cameraKeys != nullptr ) {
-				cameraKeys->forward = glfwGetKeyScancode(GLFW_KEY_W);
-				cameraKeys->backward = glfwGetKeyScancode(GLFW_KEY_S);
-				cameraKeys->left = glfwGetKeyScancode(GLFW_KEY_A);
-				cameraKeys->right = glfwGetKeyScancode(GLFW_KEY_D);
-				cameraKeys->rotate_cw = glfwGetKeyScancode(GLFW_KEY_Q);
-				cameraKeys->rotate_ccw = glfwGetKeyScancode(GLFW_KEY_E);
-			}
-        }
-
-        fggl::ecs3::entity_t terrain;
-        {
-            terrain = m_world->create(false);
-            m_world->add(terrain, types.find(fggl::math::Transform::name));
-
-            auto camTf = m_world->get<fggl::math::Transform>(terrain);
-            camTf->origin( glm::vec3(0.0f, 0.0f, 0.0f) );
-
-            //auto terrainData = m_world.get<fggl::data::HeightMap>(terrain);
-            fggl::data::HeightMap terrainData{};
-            terrainData.clear();
-
-            const siv::PerlinNoise::seed_type seed = 123456u;
-            const siv::PerlinNoise perlin{ seed };
-
-            for (int y = 0; y < 255; ++y) {
-                for (int x = 0; x < 255; ++x) {
-                    const double noise = perlin.octave2D_11( (x * 0.01), (y * 0.01) , 4) * 10.f;
-                    terrainData.heightValues[x * 255 +y] = (float)noise;
-                }
-            }
-            m_world->set<fggl::data::HeightMap>(terrain, &terrainData);
-        }
-
-        // create foundation object
-        fggl::ecs3::entity_t foundation;
-        {
-            foundation = m_world->create(true);
-            m_world->add(foundation, types.find(fggl::math::Transform::name));
-
-            // plot rendering
-            fggl::data::Mesh mesh;
-            fggl::data::make_cube(mesh);
-            mesh.removeDups();
-
-            // add mesh as a component
-            constexpr char shader[] = "phong";
-            fggl::data::StaticMesh staticMesh{mesh, shader};
-            m_world->set<fggl::data::StaticMesh>(foundation, &staticMesh);
-        }
-
-        // create building prototype
-        fggl::ecs3::entity_t bunker;
-        {
-            bunker = m_world->create(true);
-            m_world->add(bunker, types.find(fggl::math::Transform::name));
-
-            // mesh
-            int nSections = 2;
-            constexpr float HALF_PI = M_PI / 2.0f;
-            constexpr char shader[] = "phong";
-
-            fggl::data::Mesh mesh;
-            for (int j=-(nSections/2); j<=nSections/2; j++) {
-                const auto shapeOffset = glm::vec3( 0.0f, 0.5f, (float)j * 1.0f );
-
-                const auto cubeMat = glm::translate( fggl::math::mat4( 1.0f ) , shapeOffset );
-                const auto leftSlope = fggl::math::modelMatrix(
-                        glm::vec3(-1.0f, 0.0f, 0.0f) + shapeOffset,
-                        glm::vec3( 0.0f, -HALF_PI, 0.0f) );
-                const auto rightSlope = fggl::math::modelMatrix(
-                        glm::vec3( 1.0f, 0.0f, 0.0f) + shapeOffset,
-                        glm::vec3( 0.0f, HALF_PI, 0.0f) );
-
-                fggl::data::make_cube( mesh, cubeMat );
-                fggl::data::make_slope( mesh, leftSlope );
-                fggl::data::make_slope( mesh, rightSlope );
-            }
-            mesh.removeDups();
-
-            fggl::data::StaticMesh staticMesh{mesh, shader};
-            m_world->set<fggl::data::StaticMesh>(bunker, &staticMesh);
-        }
-
-        for (int i=0; i<3; ++i) {
-            glm::vec3 location(i * 6.5f + 1.0f, 0.0f, -7.0f);
-            placeObject(*m_world, terrain, foundation, location);
-        }
-
-        int nCubes = 3;
-        for ( int i=0; i<nCubes; i++ ) {
-            glm::vec3 location;
-            location.x = i * 6.f + 1.0f;
-            location.z = -5.0f + 1.0f;
-            placeObject(*m_world, terrain, bunker, location);
-        }
-    }
-
-    void deactivate() override {
-    }
-
-    void update() override {
-        process_camera(*m_world, m_inputs);
-
-        // JWR - this doesn't really seem like it belongs in the game scene...
-        if ( m_inputs->keyboard.pressed(glfwGetKeyScancode(GLFW_KEY_F10)) ) {
-            auto dbgui = fggl::util::ServiceLocator::instance().providePtr<fggl::debug::DebugUI>();
-            dbgui->visible( !dbgui->visible() );
-        }
-    }
-
-    void render(fggl::gfx::Graphics& gfx) override {
-		// render the 3D scene
-		if ( m_world != nullptr ) {
-			gfx.drawScene( *m_world );
-		}
-
-		const fggl::math::vec2 panelSize { 250.0F, 250.0F };
-		const auto canvasY = gfx.canvasBounds().bottom - panelSize.y;
-		m_canvas.size( {0.0F, canvasY}, panelSize);
-
-		// now the 2D scene
-		fggl::gfx::Paint paint;
-		m_canvas.render(paint);
-		gfx.draw2D(paint);
-    }
-
-    // entity inspector
-    void debugInspector() {
-        auto types = m_world->types();
-
-        ImGui::Begin("Entities");
-        auto entityItr = m_world->all();
-        for (auto& entity : entityItr) {
-            std::string label = "entity-" + std::to_string(entity);
-            if ( ImGui::TreeNode(label.c_str()) ){
-                auto entComp = m_world->getComponents(entity);
-                for ( auto comp : entComp ){
-                    auto meta = types.meta(comp);
-                    ImGui::Text("%s (%d) - %lu bytes", meta->name(), comp, meta->size());
-                }
-                ImGui::TreePop();
-            }
-        }
-        ImGui::End();
-    };
-
-private:
-    std::unique_ptr<fggl::ecs3::World> m_world;
-    std::unique_ptr<fggl::util::Timer> m_sceneTime;
-    InputManager m_inputs;
-	fggl::gui::Panel m_canvas;
-};
-
-void gamepadDebug(bool* visible) {
-    auto inputs = fggl::util::ServiceLocator::instance().providePtr<fggl::input::Input>();
-        auto &gamepads = inputs->gamepads;
-        ImGui::Begin("GamePad", visible);
-        for (int i = 0; i < 16; i++) {
-            std::string title = gamepads.name(i);
-
-            bool present = gamepads.present(i);
-            if (ImGui::TreeNode(title.c_str())) {
-                ImGui::Text("present: %s", present ? "yes" : "no");
-
-                if (present) {
-
-                    if (ImGui::TreeNode("buttons##2")) {
-                        for (auto &btn: fggl::input::GamepadButtonsMicrosoft) {
-                            ImGui::Text("%s: %i %i %i", btn.name,
-                                        gamepads.button(i, btn.id),
-                                        gamepads.buttonPressed(i, btn.id),
-                                        gamepads.buttonReleased(i, btn.id)
-                            );
-                        }
-                        ImGui::TreePop();
-                    }
-
-                    if (ImGui::TreeNode("axes##2")) {
-                        for (auto &axis: fggl::input::GamepadAxes) {
-                            ImGui::Text("%s: %f %f", axis.name,
-                                        gamepads.axis(i, axis.id),
-                                        gamepads.axisDelta(i, axis.id)
-                            );
-
-                        }
-                        ImGui::TreePop();
-                    }
-
-                }
-
-                ImGui::TreePop();
-                ImGui::Separator();
-            }
-
-        }
-        ImGui::End();
+static void test_atlas_api() {
+	// atlas testing
+	std::vector< fggl::gfx::ImageAtlas<char>::SubImage > images;
+	auto *atlas = fggl::gfx::ImageAtlas<char>::pack(images);
 }
 
 int main(int argc, const char* argv[]) {
@@ -365,18 +71,15 @@ int main(int argc, const char* argv[]) {
 	//app.use<fggl::ecs3::ecsTypes>();
 	app.use<fggl::gfx::SceneUtils>();
 
-	// atlas testing
-	std::vector< fggl::gfx::ImageAtlas<char>::SubImage > images;
-	auto *atlas = fggl::gfx::ImageAtlas<char>::pack(images);
+	test_atlas_api();
 
-	// and now our states
+	// Add a basic main menu
     auto *menu = app.add_state<fggl::scenes::BasicMenu>("menu");
-
     menu->add("start", [&app]() { app.change_state("game"); });
 	menu->add("options", [&app]() { app.change_state("game"); });
 	menu->add("quit", [&app]() { app.running(false); });
 
-	// game state
+	// the game state itself
     app.add_state<GameScene>("game");
 
 	return app.run(argc, argv);
diff --git a/include/fggl/data/procedural.hpp b/include/fggl/data/procedural.hpp
index d8431a95fe1dd89901201c87b299282a5df2a68a..f05a859fbb7f693717a6f114b4ec793c5bb0717e 100644
--- a/include/fggl/data/procedural.hpp
+++ b/include/fggl/data/procedural.hpp
@@ -1,3 +1,5 @@
+#ifndef FGGL_DATA_PROCEDURAL_HPP
+#define FGGL_DATA_PROCEDURAL_HPP
 
 #include "model.hpp"
 
@@ -37,3 +39,5 @@ namespace fggl::data {
 		return make_point(mesh, OFFSET_NONE);
 	}
 }
+
+#endif
\ No newline at end of file