diff --git a/.vscode/settings.json b/.vscode/settings.json
index 994b4040..1754528f 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -2,6 +2,7 @@
     "git.detectSubmodules": false,
     "cSpell.words": [
         "msaa",
+        "raylib",
         "repr",
         "vsync"
     ],
diff --git a/automation/anim_stitcher/stitcher.py b/automation/anim_stitcher/stitcher.py
index e65b0b4b..f22f55ee 100644
--- a/automation/anim_stitcher/stitcher.py
+++ b/automation/anim_stitcher/stitcher.py
@@ -33,7 +33,7 @@ def check_sprite_exists(sprite_type: str, sprite_name: str) -> bool:
     return os.path.isdir(sprite_path)
 
 
-def stitch_images_and_write_to_disk(sprite_type: str, sprite_name: str, images: List[str], quantize: bool) -> None:
+def stitch_images_and_write_to_disk(sprite_type: str, sprite_name: str, images: List[str], quantize: bool, fps: float) -> None:
 
     # Load all the images
     images_to_stitch = []
diff --git a/automation/anim_stitcher/ui.py b/automation/anim_stitcher/ui.py
index 6dc06ad6..43cfa7e4 100644
--- a/automation/anim_stitcher/ui.py
+++ b/automation/anim_stitcher/ui.py
@@ -85,6 +85,16 @@ class AnimStitcherWindow(QtWidgets.QWidget):
         self.optimization_layout.addWidget(self.optimization_dropdown)
         self.layout().addLayout(self.optimization_layout)
 
+        # Add a number input for the target FPS
+        self.fps_layout = QtWidgets.QHBoxLayout()
+        self.fps_label = QtWidgets.QLabel("Target FPS")
+        self.fps_layout.addWidget(self.fps_label)
+        self.fps_input = QtWidgets.QLineEdit()
+        self.fps_input.setText("24")
+        self.fps_input.setEnabled(False)
+        self.fps_layout.addWidget(self.fps_input)
+        self.layout().addLayout(self.fps_layout)
+
         # Add a seperator
         self.layout().addWidget(qt_lines.QHLine())
 
@@ -122,6 +132,7 @@ class AnimStitcherWindow(QtWidgets.QWidget):
             self.sprite_name_input.setEnabled(True)
             self.stitch_button.setEnabled(True)
             self.optimization_dropdown.setEnabled(True)
+            self.fps_input.setEnabled(True)
 
             # Save the selected files
             self.selected_files = file_dialog.selectedFiles()
@@ -167,9 +178,23 @@ class AnimStitcherWindow(QtWidgets.QWidget):
             if warning_dialog.exec_() == QtWidgets.QMessageBox.No:
                 return
 
+
+        # Pop up an error if the inputted FPS is not a number
+        try:
+            fps = float(self.fps_input.text())
+        except ValueError:
+            warning_dialog = QtWidgets.QMessageBox()
+            warning_dialog.setIcon(QtWidgets.QMessageBox.Warning)
+            warning_dialog.setText("Invalid FPS")
+            warning_dialog.setInformativeText(
+                "The FPS must be a number")
+            warning_dialog.setWindowTitle("Invalid FPS")
+            warning_dialog.exec_()
+            return
+
         # Perform the actual stitching action
         stitcher.stitch_images_and_write_to_disk(
-            sprite_type, sprite_name, self.selected_files, self.optimization_dropdown.currentText() == "Size")
+            sprite_type, sprite_name, self.selected_files, self.optimization_dropdown.currentText() == "Size", float(self.fps_input.text()))
 
         # Close the window
         self.close()
diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md
index b11cbabd..91e925ec 100644
--- a/docs/SUMMARY.md
+++ b/docs/SUMMARY.md
@@ -3,7 +3,4 @@
  1. [Introduction](introduction.md)
  2. [Getting Started](getting-started.md)
     1. [Development Environment](development-environment.md)
-    2. [Artist Information](artist-information.md)
- 3. [Infrastructure Overview](infrastructure-overview.md)
- 4. [Software Design](software-design.md)
-    1. [Asset Manager](design-asset-manager.md)
\ No newline at end of file
+ 3. [Using `anim_stitcher`](anim-stitcher.md)
\ No newline at end of file
diff --git a/docs/anim-stitcher.md b/docs/anim-stitcher.md
new file mode 100644
index 00000000..24523981
--- /dev/null
+++ b/docs/anim-stitcher.md
@@ -0,0 +1,19 @@
+# Using anim_stitcher
+
+`anim_stitcher` is a Python utility designed to allow artists to automatically convert their frames into sprite sheets with metadata.
+
+## Usage
+
+## Technical information
+
+`anim_stitcher` exports spritesheets to `game/dist/assets/anm/...`. Each spritesheet also has a metadata JSON file beside it. The filepaths are automatically chosen based on input in the GUI.
+
+An example output would be for an asset named `testFox` with the `Character` type.
+
+```text
+...
+game/dist/assets/anm/chr/chr_testFox:
+     - chr_testFox.png
+     - chr_testFox.anim_meta.json
+...
+```
diff --git a/docs/artist-information.md b/docs/artist-information.md
deleted file mode 100644
index dc01fcc8..00000000
--- a/docs/artist-information.md
+++ /dev/null
@@ -1,2 +0,0 @@
-# Artist Information
-
diff --git a/docs/development-environment.md b/docs/development-environment.md
index 144b42ba..b4f52585 100644
--- a/docs/development-environment.md
+++ b/docs/development-environment.md
@@ -2,7 +2,7 @@
 
 ## Prerequisite Tooling
 
-On all systems, you must have [Rust](https://www.rust-lang.org/tools/install), [git](https://git-scm.com/), and [cmake](https://cmake.org/download/) installed.
+On all systems, you must have [Rust](https://www.rust-lang.org/tools/install), [git](https://git-scm.com/), [Python 3](https://www.python.org/) (with `pip`), and [cmake](https://cmake.org/download/) installed.
 
 ### Additional Dependencies for Linux
 
@@ -30,6 +30,9 @@ If you are cloning via the CLI, you will need an additional step to ensure our f
 git clone https://github.com/Ewpratten/ludum-dare-50
 cd ludum-dare-50
 git submodule update --init --recursive
+
+# Optionally, pull in the dependencies for the artist tools
+python3 -m pip install -r requirements.txt
 ```
 
 ## First Build
diff --git a/docs/infrastructure-overview.md b/docs/infrastructure-overview.md
deleted file mode 100644
index 526e7b15..00000000
--- a/docs/infrastructure-overview.md
+++ /dev/null
@@ -1 +0,0 @@
-# Infrastructure Overview
diff --git a/game/game_logic/src/rendering/event_loop.rs b/game/game_logic/src/rendering/event_loop.rs
index bef7c185..b067b6db 100644
--- a/game/game_logic/src/rendering/event_loop.rs
+++ b/game/game_logic/src/rendering/event_loop.rs
@@ -13,7 +13,7 @@ use crate::discord::DiscordChannel;
 use crate::project_constants::ProjectConstants;
 use crate::rendering::core_renderer_sm::{PreloadState, RenderBackendStates};
 use crate::rendering::screens::sm_failure_screen;
-use crate::scenes::process_ingame_frame;
+use crate::scenes::SceneRenderDelegate;
 use raylib::RaylibBuilder;
 
 /// Will begin rendering graphics. Returns when the window closes
@@ -44,6 +44,9 @@ pub async fn handle_graphics_blocking<ConfigBuilder>(
     let mut loading_screen = crate::rendering::screens::loading_screen::LoadingScreen::new();
     let mut sm_failure_screen = sm_failure_screen::SmFailureScreen::new();
 
+    // Set up the main render delegate
+    let mut render_delegate = SceneRenderDelegate::on_game_start();
+
     // Handle loading the resources and rendering the loading screen
     log::trace!("Running event loop");
     while !raylib_handle.window_should_close() {
@@ -93,7 +96,7 @@ pub async fn handle_graphics_blocking<ConfigBuilder>(
                     .await;
             }
             RenderBackendStates::RenderGame(ref m) => {
-                process_ingame_frame(
+                render_delegate.process_ingame_frame(
                     &mut raylib_handle,
                     &raylib_thread,
                     &discord_signaling,
diff --git a/game/game_logic/src/rendering/utilities/anim_texture.rs b/game/game_logic/src/rendering/utilities/anim_texture.rs
new file mode 100644
index 00000000..42bb4e79
--- /dev/null
+++ b/game/game_logic/src/rendering/utilities/anim_texture.rs
@@ -0,0 +1,23 @@
+//! This module handles the code for rendering framerate-locked animations from textures
+
+use raylib::texture::Texture2D;
+
+#[derive(Debug)]
+pub struct AnimatedTexture {
+    texture: Texture2D,
+    target_fps: f32,
+}
+
+impl AnimatedTexture {
+    /// Construct a new `AnimatedTexture`
+    pub fn new(texture: Texture2D, target_frames_per_second: f32) -> Self {
+        Self {
+            texture,
+            target_fps: target_frames_per_second,
+        }
+    }
+
+    pub fn render_frame_by_index(&self, index: usize) {
+
+    }
+}
diff --git a/game/game_logic/src/rendering/utilities/mod.rs b/game/game_logic/src/rendering/utilities/mod.rs
index 522880be..09d38ecf 100644
--- a/game/game_logic/src/rendering/utilities/mod.rs
+++ b/game/game_logic/src/rendering/utilities/mod.rs
@@ -1,2 +1 @@
-pub mod vram_anim;
-pub mod size_mismatch;
\ No newline at end of file
+pub mod anim_texture;
\ No newline at end of file
diff --git a/game/game_logic/src/rendering/utilities/size_mismatch.rs b/game/game_logic/src/rendering/utilities/size_mismatch.rs
deleted file mode 100644
index 17508b0a..00000000
--- a/game/game_logic/src/rendering/utilities/size_mismatch.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-/// The policy for how to handle rendering a small frame on a big texture
-
-#[repr(C)]
-#[derive(Debug, Clone, PartialEq, Copy)]
-pub enum TextureSizeMismatchRenderPolicy {
-    TopLeft,
-    Center,
-}
-
diff --git a/game/game_logic/src/rendering/utilities/vram_anim.rs b/game/game_logic/src/rendering/utilities/vram_anim.rs
deleted file mode 100644
index c551d238..00000000
--- a/game/game_logic/src/rendering/utilities/vram_anim.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-use raylib::texture::Texture2D;
-
-#[derive(Debug)]
-pub struct VramAnimTexture {}
-
-impl VramAnimTexture {
-    /// Construct a new `VramAnimTexture`
-    pub fn new(texture: Texture2D) -> Self {
-        Self {}
-    }
-}
diff --git a/game/game_logic/src/scenes/mod.rs b/game/game_logic/src/scenes/mod.rs
index 6a4a063c..f264f964 100644
--- a/game/game_logic/src/scenes/mod.rs
+++ b/game/game_logic/src/scenes/mod.rs
@@ -7,16 +7,44 @@ use raylib::prelude::*;
 
 use crate::{discord::DiscordChannel, global_resource_package::GlobalResources};
 
-/// This is called every frame once the game has started.
-/// 
-/// Keep in mind everything you do here will block the main thread (no loading files plz)
-pub fn process_ingame_frame(
-    raylib: &mut RaylibHandle,
-    rl_thread: &RaylibThread,
-    discord: &DiscordChannel,
-    global_resources: &GlobalResources,
-) {
-    let mut d = raylib.begin_drawing(&rl_thread);
+use self::test_fox::TestFoxScene;
+mod test_fox;
 
-    d.clear_background(raylib::color::Color::WHITE);
+/// Delegate for handling rendering.
+/// This is a struct to allow for stateful data (like sub-screens) to be set up
+pub struct SceneRenderDelegate {
+    /* Scenes */
+    scene_test_fox: TestFoxScene,
+}
+
+impl SceneRenderDelegate {
+    /// This is called when the game first loads
+    pub fn on_game_start() -> Self {
+        // TODO: Stick any init code you want here.
+
+        // Init some scenes
+        let scene_test_fox = TestFoxScene::new();
+
+        Self { scene_test_fox }
+    }
+
+    /// This is called every frame once the game has started.
+    ///
+    /// Keep in mind everything you do here will block the main thread (no loading files plz)
+    pub fn process_ingame_frame(
+        &mut self,
+        raylib: &mut RaylibHandle,
+        rl_thread: &RaylibThread,
+        discord: &DiscordChannel,
+        global_resources: &GlobalResources,
+    ) {
+        // For now, we will just render the test fox scene
+        self.scene_test_fox
+            .render_frame(raylib, rl_thread, &discord, global_resources);
+    }
+}
+
+impl Drop for SceneRenderDelegate {
+    /// If you need anything to happen when the game closes, stick it here.
+    fn drop(&mut self) {}
 }
diff --git a/game/game_logic/src/scenes/test_fox.rs b/game/game_logic/src/scenes/test_fox.rs
new file mode 100644
index 00000000..e575a181
--- /dev/null
+++ b/game/game_logic/src/scenes/test_fox.rs
@@ -0,0 +1,34 @@
+//! This "scene" is used only for testing animation and resource loading
+//! It should be removed once the game is being worked on
+
+use raylib::{RaylibHandle, RaylibThread};
+
+use crate::{
+    discord::DiscordChannel, global_resource_package::GlobalResources,
+    rendering::utilities::anim_texture::AnimatedTexture,
+};
+
+#[derive(Debug)]
+pub struct TestFoxScene {
+    fox_animation: AnimatedTexture,
+}
+
+impl TestFoxScene {
+    /// Construct a new `TestFoxScene`
+    pub fn new() -> Self {
+        // Load the fox texture
+        let fox = AnimatedTexture::new();
+
+        Self { fox_animation: fox }
+    }
+
+    /// Handler for each frame
+    pub fn render_frame(
+        &mut self,
+        raylib: &mut RaylibHandle,
+        rl_thread: &RaylibThread,
+        discord: &DiscordChannel,
+        global_resources: &GlobalResources,
+    ) {
+    }
+}
diff --git a/launch_anim_stitcher.sh b/launch_anim_stitcher.sh
old mode 100644
new mode 100755