kitgit

tirbofish/dropbear · commit

33746879348c36a118172de31bd1a614dafeba63

started work on redback-runtime, added many values onto scenes, runtime configs. documented many different functions, structs and values. also just did a simple 4tkbytes -> tirbofish (to show my new github name and ensure all docs are correct). last commit for the weekend. assessment season coming up, and i CANNOT fuck this up for my HSC. looks like this is a public transport project for now, or a _potential_ weekend project. jarvis, run github actions

Unverified

Thribhu K <4tkbytes@pm.me> · 2025-11-30 12:37

view full diff

diff --git a/.github/workflows/create_executable.yaml b/.github/workflows/create_executable.yaml
index 3292b0d..91e30c3 100644
--- a/.github/workflows/create_executable.yaml
+++ b/.github/workflows/create_executable.yaml
@@ -57,12 +57,13 @@ jobs:
         with:
           targets: ${{ matrix.target }}
       - name: build eucalyptus packages
-        run: cargo build --release --package eucalyptus-core --package eucalyptus-editor --package magna-carta --target ${{ matrix.target }}
+        run: cargo build --release --package eucalyptus-core --package eucalyptus-editor --package magna-carta --package redback-runtime --target ${{ matrix.target }}
       - name: prepare the artifact
         run: |
           mkdir -p dist/libs
           cp target/${{ matrix.target }}/release/eucalyptus-editor${{ matrix.ext }} dist/
           cp target/${{ matrix.target }}/release/magna-carta${{ matrix.ext }} dist/
+          cp target/${{ matrix.target }}/release/redback-runtime${{ matrix.ext }} dist/
           if [ "${{ matrix.os }}" = "windows-latest" ]; then
             cp target/${{ matrix.target }}/release/eucalyptus_core.dll dist/ 2>/dev/null || true
             cp target/${{ matrix.target }}/release/eucalyptus_core.dll.lib dist/ 2>/dev/null || true
diff --git a/Cargo.toml b/Cargo.toml
index b3b006b..0c0e35e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,7 +2,7 @@
 package.version = "0.1.2"
 package.edition = "2024"
 package.license = "MIT OR Apache-2.0"
-package.repository = "https://github.com/4tkbytes/dropbear"
+package.repository = "https://github.com/tirbofish/dropbear"
 package.readme = "README.md"
 
 resolver = "3"
@@ -78,6 +78,7 @@ syn = { version = "2.0", features = ["full"] }
 quote = "1.0"
 egui_ltreeview = { version = "0.6", features = ["doc"] }
 dyn-hash = "1.0"
+semver = { version = "1.0", features = ["serde"] }
 
 [workspace.dependencies.image]
 version = "0.25"
diff --git a/build.gradle.kts b/build.gradle.kts
index 693f382..2954aa1 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -165,27 +165,27 @@ publishing {
         pom {
             name.set("dropbear")
             description.set("The dropbear scripting part of the engine... uhh yeah!")
-            url.set("https://github.com/4tkbytes/dropbear")
+            url.set("https://github.com/tirbofish/dropbear")
 
             licenses {
                 license {
                     name.set("dropbear engine License, Version 1.2")
-                    url.set("https://raw.githubusercontent.com/4tkbytes/dropbear/refs/heads/main/LICENSE.md")
+                    url.set("https://raw.githubusercontent.com/tirbofish/dropbear/refs/heads/main/LICENSE.md")
                 }
             }
 
             developers {
                 developer {
-                    id.set("4tkbytes")
+                    id.set("tirbofish")
                     name.set("tk")
-                    email.set("4tkbytes@pm.me")
+                    email.set("tirbofish@pm.me")
                 }
             }
 
             scm {
-                url.set("https://github.com/4tkbytes/dropbear")
-                connection.set("scm:git:git://github.com/4tkbytes/dropbear.git")
-                developerConnection.set("scm:git:ssh://git@github.com/4tkbytes/dropbear.git")
+                url.set("https://github.com/tirbofish/dropbear")
+                connection.set("scm:git:git://github.com/tirbofish/dropbear.git")
+                developerConnection.set("scm:git:ssh://git@github.com/tirbofish/dropbear.git")
             }
         }
     }
diff --git a/dropbear-engine/Cargo.toml b/dropbear-engine/Cargo.toml
index 9d4992d..59bfe8c 100644
--- a/dropbear-engine/Cargo.toml
+++ b/dropbear-engine/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "dropbear-engine"
-description = "A game engine made by 4tkbytes. Thats really it..."
+description = "A game engine made by tirbofish. Thats really it..."
 
 version.workspace = true
 edition.workspace = true
diff --git a/dropbear-engine/src/entity.rs b/dropbear-engine/src/entity.rs
index 1fb563e..d9029d0 100644
--- a/dropbear-engine/src/entity.rs
+++ b/dropbear-engine/src/entity.rs
@@ -145,7 +145,7 @@ impl Transform {
 /// A renderer for meshes and materials related to a model.
 ///
 /// It includes the instances as well as a handle. The reason for a handle is so the model being rendered can be swapped
-/// to something else without deleting the entire renderer. Also saves memory by rendering anything that has been loaded. 
+/// to something else without deleting the entire renderer. Also saves memory by rendering anything that has been loaded.
 pub struct MeshRenderer {
     handle: LoadedModel,
     pub instance: Instance,
diff --git a/dropbear-engine/src/lib.rs b/dropbear-engine/src/lib.rs
index bb19291..03e96cd 100644
--- a/dropbear-engine/src/lib.rs
+++ b/dropbear-engine/src/lib.rs
@@ -533,7 +533,7 @@ impl App {
             env!("CARGO_PKG_VERSION"),
             rustc_version_runtime::version_meta().short_version_string
         );
-        log::info!("Made by tk with love at https://github.com/4tkbytes/dropbear <3");
+        log::info!("Made by tk with love at https://github.com/tirbofish/dropbear <3");
         log::info!("======================================================================");
         #[cfg(debug_assertions)]
         {
@@ -577,7 +577,7 @@ impl App {
 ///
 /// # Parameters
 /// * config - [`WindowConfiguration`]: The configuration/settings of the window.
-/// * queue - [`Option<Throwable<FutureQueue>>`]: An optional value for a [`FutureQueue`]
+/// * queue - [`Option<Arc<FutureQueue>>`]: An optional value for a [`FutureQueue`]
 /// * setup - [`FnOnce`]: A function that sets up all the scenes. It shouldn't be loaded
 ///   but instead be set as an [`Arc<Mutex<T>>`].
 macro_rules! run_app {
diff --git a/dropbear-engine/src/procedural/plane.rs b/dropbear-engine/src/procedural/plane.rs
index 8c0418d..d702650 100644
--- a/dropbear-engine/src/procedural/plane.rs
+++ b/dropbear-engine/src/procedural/plane.rs
@@ -1,6 +1,6 @@
 //! A straight plane (and some components). That's it.
 //!
-//! Inspiration taken from `https://github.com/4tkbytes/RedLight/blob/main/src/RedLight/Entities/Plane.cs`,
+//! Inspiration taken from `https://github.com/tirbofish/RedLight/blob/main/src/RedLight/Entities/Plane.cs`,
 //! my old game engine made in C sharp, where this is the plane "algorithm".
 
 use crate::asset::{ASSET_REGISTRY, AssetRegistry};
diff --git a/dropbear-engine/src/scene.rs b/dropbear-engine/src/scene.rs
index 2765260..bbc9df5 100644
--- a/dropbear-engine/src/scene.rs
+++ b/dropbear-engine/src/scene.rs
@@ -63,6 +63,7 @@ impl Manager {
         }
     }
 
+    /// Switches the scene from the current one to another.
     pub fn switch(&mut self, name: &str) {
         if self.scenes.contains_key(name) {
             self.next_scene = Some(name.to_string());
@@ -179,6 +180,11 @@ impl Manager {
     }
 }
 
+/// Helper function that adds a struct that implements [`Scene`], [`input::Keyboard`],
+/// [`input::Mouse`] and [`input::Controller`].
+///
+/// Specifically, it adds the struct as keyboard, mouse and controller, then it attaches
+/// that input structs to the scene.
 pub fn add_scene_with_input<
     S: 'static + Scene + input::Keyboard + input::Mouse + input::Controller,
 >(
diff --git a/dropbear-gradle-plugin/build.gradle.kts b/dropbear-gradle-plugin/build.gradle.kts
index 7ab6cfb..7a511fe 100644
--- a/dropbear-gradle-plugin/build.gradle.kts
+++ b/dropbear-gradle-plugin/build.gradle.kts
@@ -19,8 +19,8 @@ dependencies {
 }
 
 gradlePlugin {
-    website.set("https://github.com/4tkbytes/dropbear")
-    vcsUrl.set("https://github.com/4tkbytes/dropbear")
+    website.set("https://github.com/tirbofish/dropbear")
+    vcsUrl.set("https://github.com/tirbofish/dropbear")
     plugins {
 
         create("dropbearGradlePlugin") {
@@ -39,7 +39,7 @@ publishing {
             pom {
                 name.set("dropbear-gradle-plugin")
                 description.set("Gradle plugin for dropbear dependency management")
-                url.set("https://4tkbytes.github.io/dropbear/")
+                url.set("https://tirbofish.github.io/dropbear/")
 
                 licenses {
                     license {
@@ -49,15 +49,15 @@ publishing {
                 }
                 developers {
                     developer {
-                        id.set("4tkbytes")
+                        id.set("tirbofish")
                         name.set("tk")
-                        email.set("4tkbytes@pm.me")
+                        email.set("tirbofish@pm.me")
                     }
                 }
                 scm {
-                    connection.set("scm:git:git://github.com/4tkbytes/dropbear.git")
-                    developerConnection.set("scm:git:ssh://github.com/4tkbytes/dropbear.git")
-                    url.set("https://github.com/4tkbytes/dropbear")
+                    connection.set("scm:git:git://github.com/tirbofish/dropbear.git")
+                    developerConnection.set("scm:git:ssh://github.com/tirbofish/dropbear.git")
+                    url.set("https://github.com/tirbofish/dropbear")
                 }
             }
         }
diff --git a/dropbear-traits/src/lib.rs b/dropbear-traits/src/lib.rs
index 5324092..a05f2ed 100644
--- a/dropbear-traits/src/lib.rs
+++ b/dropbear-traits/src/lib.rs
@@ -20,7 +20,11 @@ pub trait SerializableComponent: Send + Sync + Debug {
     /// Returns the display name of the component.
     fn display_name(&self) -> String {
         let type_name = self.type_name();
-        type_name.split("::").last().unwrap_or(type_name).to_string()
+        type_name
+            .split("::")
+            .last()
+            .unwrap_or(type_name)
+            .to_string()
     }
 }
 
diff --git a/dropbear-traits/src/registry.rs b/dropbear-traits/src/registry.rs
index b2e4c40..1295cc7 100644
--- a/dropbear-traits/src/registry.rs
+++ b/dropbear-traits/src/registry.rs
@@ -96,7 +96,6 @@ impl ComponentRegistry {
         })
     }
 
-
     // Register a custom converter for special cases
     pub fn register_converter<From, To, F>(&mut self, converter_fn: F)
     where
diff --git a/dropbear_future-queue/src/lib.rs b/dropbear_future-queue/src/lib.rs
index a0ac14c..a7ae812 100644
--- a/dropbear_future-queue/src/lib.rs
+++ b/dropbear_future-queue/src/lib.rs
@@ -1,6 +1,6 @@
 //! Enabling multithreading for functions and apps that are purely single threaded.
 //!
-//! This was originally a module in my [dropbear](https://github.com/4tkbytes/dropbear) game engine,
+//! This was originally a module in my [dropbear](https://github.com/tirbofish/dropbear) game engine,
 //! however I thought there were barely any libraries that had future queuing. It takes inspiration
 //! from Unity and how they handle their events.
 //!
diff --git a/eucalyptus-core/Cargo.toml b/eucalyptus-core/Cargo.toml
index 1e32321..2dd66d2 100644
--- a/eucalyptus-core/Cargo.toml
+++ b/eucalyptus-core/Cargo.toml
@@ -39,6 +39,7 @@ app_dirs2.workspace = true
 log-once.workspace = true
 rfd = { workspace = true, optional = true }
 typetag.workspace = true
+semver.workspace = true
 
 [features]
 # editor only stuff
diff --git a/eucalyptus-core/build.rs b/eucalyptus-core/build.rs
index ec0208f..e18ebe8 100644
--- a/eucalyptus-core/build.rs
+++ b/eucalyptus-core/build.rs
@@ -1,6 +1,6 @@
 fn main() -> anyhow::Result<()> {
     // // todo: move this into the "setup" process
-    // let repo_zip_url = "https://github.com/4tkbytes/dropbear/archive/refs/heads/main.zip";
+    // let repo_zip_url = "https://github.com/tirbofish/dropbear/archive/refs/heads/main.zip";
     // let response = reqwest::blocking::get(repo_zip_url)
     //     .map_err(|e| anyhow::anyhow!("Failed to download repo zip: {}", e))?
     //     .bytes()
@@ -12,7 +12,7 @@ fn main() -> anyhow::Result<()> {
     //
     // let app_info = app_dirs2::AppInfo {
     //     name: "Eucalyptus",
-    //     author: "4tkbytes",
+    //     author: "tirbofish",
     // };
     // let app_data_dir = app_dirs2::app_root(app_dirs2::AppDataType::UserData, &app_info)
     //     .map_err(|e| anyhow::anyhow!("Could not determine app data directory: {}", e))?;
@@ -48,7 +48,7 @@ fn main() -> anyhow::Result<()> {
     //
     // if !found_resource {
     //     return Err(anyhow::anyhow!(
-    //         "No resources folder found in the github repository [4tkbytes/dropbear] :("
+    //         "No resources folder found in the github repository [tirbofish/dropbear] :("
     //     ));
     // }
 
diff --git a/eucalyptus-core/src/config.rs b/eucalyptus-core/src/config.rs
index dd90c81..e638550 100644
--- a/eucalyptus-core/src/config.rs
+++ b/eucalyptus-core/src/config.rs
@@ -1,3 +1,4 @@
+use crate::runtime::{Authoring, RuntimeSettings};
 use crate::scene::SceneConfig;
 use crate::states::{
     EditorSettings, EditorTab, File, Folder, Node, RESOURCES, ResourceType, SCENES, SOURCE,
@@ -8,7 +9,6 @@ use ron::ser::PrettyConfig;
 use serde::{Deserialize, Serialize};
 use std::fs;
 use std::path::{Path, PathBuf};
-use crate::runtime::RuntimeSettings;
 
 /// The root config file, responsible for building and other metadata.
 ///
@@ -20,8 +20,13 @@ pub struct ProjectConfig {
     pub project_path: PathBuf,
     pub date_created: String,
     pub date_last_accessed: String,
+
+    /// Semantic version of the project. Default is set to `0.1.0`
     #[serde(default)]
-    pub dock_layout: Option<DockState<EditorTab>>,
+    pub project_version: Option<String>,
+
+    #[serde(default)]
+    pub authors: Authoring,
 
     #[serde(default)]
     pub editor_settings: EditorSettings,
@@ -31,6 +36,10 @@ pub struct ProjectConfig {
 
     #[serde(default)]
     pub last_opened_scene: Option<String>,
+
+    // ensure this is last otherwise it clutters the .eucp file
+    #[serde(default)]
+    pub dock_layout: Option<DockState<EditorTab>>,
 }
 
 impl ProjectConfig {
@@ -45,10 +54,12 @@ impl ProjectConfig {
             project_path: project_path.as_ref().to_path_buf(),
             date_created,
             date_last_accessed,
+            project_version: None,
             editor_settings: Default::default(),
             dock_layout: None,
             last_opened_scene: None,
             runtime_settings: Default::default(),
+            authors: Default::default(),
         };
         let _ = result.load_config_to_memory();
         result
@@ -203,13 +214,16 @@ impl ProjectConfig {
                             log::error!("Failed to write new scene: {}", err);
                             rfd::MessageDialog::new()
                                 .set_title("Write Error")
-                                .set_description(&format!("Failed to create new scene file: {}", err))
+                                .set_description(&format!(
+                                    "Failed to create new scene file: {}",
+                                    err
+                                ))
                                 .show();
                             std::process::exit(1);
                         }
 
                         return Some(new_scene);
-                    },
+                    }
                     _ => {
                         std::process::exit(1);
                     }
diff --git a/eucalyptus-core/src/lib.rs b/eucalyptus-core/src/lib.rs
index 3605a6a..896d523 100644
--- a/eucalyptus-core/src/lib.rs
+++ b/eucalyptus-core/src/lib.rs
@@ -24,5 +24,5 @@ pub use egui;
 /// By default, most of its items are located in [`app_dirs2::AppDataType::UserData`].
 pub const APP_INFO: app_dirs2::AppInfo = app_dirs2::AppInfo {
     name: "Eucalyptus",
-    author: "4tkbytes",
+    author: "tirbofish",
 };
diff --git a/eucalyptus-core/src/runtime.rs b/eucalyptus-core/src/runtime.rs
index 99fe08e..0b664bb 100644
--- a/eucalyptus-core/src/runtime.rs
+++ b/eucalyptus-core/src/runtime.rs
@@ -1,25 +1,28 @@
-use crate::scene::{SceneConfig};
+use crate::scene::SceneConfig;
 use crate::states::{PROJECT, SCENES};
+use anyhow::Context;
+use semver::Version;
 
-/// The settings of a project in its runtime. 
-/// 
+/// The settings of a project in its runtime.
+///
 /// This is different to [`SceneSettings`], which contains settings for ONLY
-/// that specific scene. This is for any configurations of the project during its runtime, 
-/// such as initial scene and stuff like that. 
-#[derive(
-    bincode::Decode,
-    bincode::Encode,
-    serde::Serialize,
-    serde::Deserialize,
-    Debug,
-    Clone,
-)]
-pub struct RuntimeSettings {}
+/// that specific scene. This is for any configurations of the project during its runtime,
+/// such as initial scene and stuff like that.
+#[derive(bincode::Decode, bincode::Encode, serde::Serialize, serde::Deserialize, Debug, Clone)]
+pub struct RuntimeSettings {
+    /// The first scene that shows up when redback-runtime is ran. 
+    /// 
+    /// The first scene is not set is expected to be the first scene out of the
+    /// projects scene list, or just a normal anyhow error. 
+    pub initial_scene: Option<String>,
+}
 
 impl RuntimeSettings {
-    /// Creates a new [`RuntimeSettings`] config. 
+    /// Creates a new [`RuntimeSettings`] config.
     pub fn new() -> Self {
-        Self { }
+        Self {
+            initial_scene: None,
+        }
     }
 }
 
@@ -29,36 +32,92 @@ impl Default for RuntimeSettings {
     }
 }
 
-/// The configuration of a packaged eucalyptus project. 
-/// 
+/// A struct that represents the authors and creators of the eucalyptus project.
+#[derive(bincode::Decode, bincode::Encode, serde::Serialize, serde::Deserialize, Debug, Clone)]
+pub struct Authoring {
+    /// The team behind the game
+    pub developer: String,
+}
+
+impl Default for Authoring {
+    fn default() -> Self {
+        Self {
+            developer: String::from("Unknown"),
+        }
+    }
+}
+
+/// The configuration of a packaged eucalyptus project.
+///
 /// Often stored as a single .eupak file, it contains all the scenes and the references of different
-/// resources. 
-#[derive(bincode::Decode, bincode::Encode, serde::Serialize, serde::Deserialize, Debug)]
+/// resources.
+#[derive(bincode::Decode, bincode::Encode, serde::Serialize, serde::Deserialize, Debug, Clone)]
 pub struct RuntimeProjectConfig {
+    /// The name of the project
     #[bincode(with_serde)]
     pub project_name: String,
 
-    // authoring stuff needs to be added, maybe later
+    /// The initial/first scene that will show up. 
+    /// 
+    /// Access to other scenes are done with the game's scripting. 
+    #[bincode(with_serde)]
+    pub initial_scene: String,
+
+    /// Authors and creators of the game
+    #[bincode(with_serde)]
+    pub authors: Authoring,
+
+    /// The version of dropbear engine and eucalyptus-editor.
+    ///
+    /// dropbear and eucalyptus all share the same semver version.
+    #[bincode(with_serde)]
+    pub editor_version: Version,
 
-    // versioning stuff too
+    /// The version of the project. By default, it returns `0.1.0` if none has been specified.
+    #[bincode(with_serde)]
+    pub project_version: Version,
 
+    /// Any specific settings to do with the runtime.
     #[bincode(with_serde)]
     pub runtime_settings: RuntimeSettings,
-    
+
+    /// All scenes that are available in the project.
     #[bincode(with_serde)]
     pub scenes: Vec<SceneConfig>,
 }
 
 impl RuntimeProjectConfig {
-    /// Creates a [RuntimeProjectConfig] from a loaded [PROJECT] and [SCENES] states. 
-    pub fn from_memory() -> Self {
+    /// Creates a [RuntimeProjectConfig] from a loaded [PROJECT] and [SCENES] states.
+    pub fn from_memory() -> anyhow::Result<Self> {
         let project = PROJECT.read();
         let scenes = SCENES.read();
-        
-        Self {
+
+        let initial_scene = match &project.runtime_settings.initial_scene {
+            Some(val) => val.clone(),
+            None => {
+                log::warn!("Unable to fetch initial settings, using first scene available");
+                let scene = scenes.first().ok_or(anyhow::anyhow!("Unable to locate first scene in SCENES"))?;
+                scene.scene_name.clone()
+            },
+        };
+
+        let result = Self {
             project_name: project.project_name.clone(),
+            authors: Authoring::default(),
+            editor_version: Version::parse(env!("CARGO_PKG_VERSION")).context("This should not happen, unless some issue is happening with env!(\"CARGO_PKG_VERSION\")")?,
+            project_version: Version::parse(
+                project
+                    .project_version
+                    .clone()
+                    .unwrap_or("0.1.0".to_string())
+                    .as_str(),
+            )
+            .unwrap_or(Version::new(0, 1, 0)),
             runtime_settings: project.runtime_settings.clone(),
             scenes: scenes.to_vec(),
-        }
+            initial_scene,
+        };
+
+        Ok(result)
     }
-}
\ No newline at end of file
+}
diff --git a/eucalyptus-core/src/scene.rs b/eucalyptus-core/src/scene.rs
index d0e7b3c..1f2d0b9 100644
--- a/eucalyptus-core/src/scene.rs
+++ b/eucalyptus-core/src/scene.rs
@@ -1,8 +1,8 @@
 use crate::camera::{CameraComponent, CameraType};
 use crate::hierarchy::{Children, Parent, SceneHierarchy};
 use crate::states::{
-    Camera3D, Label, Light, ModelProperties, PROJECT, Script,
-    SerializedMeshRenderer, WorldLoadingStatus,
+    Camera3D, Label, Light, ModelProperties, PROJECT, Script, SerializedMeshRenderer,
+    WorldLoadingStatus,
 };
 use crate::utils::ResolveReference;
 use dropbear_engine::asset::ASSET_REGISTRY;
@@ -65,14 +65,14 @@ impl SceneEntity {
 pub struct SceneSettings {/* *crickets* */}
 
 impl SceneSettings {
-    /// Creates a new [`SceneSettings`] config. 
+    /// Creates a new [`SceneSettings`] config.
     pub fn new() -> Self {
         Self {}
     }
 }
 
-/// Specifies the configuration of a scene, such as its entities, hierarchies and any settings that 
-/// may be necessary. 
+/// Specifies the configuration of a scene, such as its entities, hierarchies and any settings that
+/// may be necessary.
 #[derive(Default, Debug, Serialize, Deserialize, Clone)]
 pub struct SceneConfig {
     #[serde(default)]
@@ -141,8 +141,9 @@ impl SceneConfig {
                         .await?;
 
                         let model = loaded_model.make_mut();
-                        model.path =
-                            ResourceReference::from_euca_uri("euca://internal/dropbear/models/cube")?;
+                        model.path = ResourceReference::from_euca_uri(
+                            "euca://internal/dropbear/models/cube",
+                        )?;
 
                         loaded_model.refresh_registry();
 
@@ -179,7 +180,8 @@ impl SceneConfig {
                     .await?;
 
                     let model = loaded_model.make_mut();
-                    model.path = ResourceReference::from_euca_uri("euca://internal/dropbear/models/cube")?;
+                    model.path =
+                        ResourceReference::from_euca_uri("euca://internal/dropbear/models/cube")?;
 
                     loaded_model.refresh_registry();
 
@@ -554,7 +556,8 @@ impl SceneConfig {
                     ..Default::default()
                 };
                 let light =
-                    EngineLight::new(graphics.clone(), comp.clone(), trans, Some("Default Light")).await;
+                    EngineLight::new(graphics.clone(), comp.clone(), trans, Some("Default Light"))
+                        .await;
 
                 let light_config = Light {
                     label: "Default Light".to_string(),
diff --git a/eucalyptus-core/src/scripting.rs b/eucalyptus-core/src/scripting.rs
index 69ba43e..f95e37e 100644
--- a/eucalyptus-core/src/scripting.rs
+++ b/eucalyptus-core/src/scripting.rs
@@ -66,7 +66,7 @@ pub struct ScriptManager {
 
 impl ScriptManager {
     /// Creates a new [`ScriptManager`] uninitialised instance, as well as a new
-    /// JVM instance.
+    /// JVM instance (if the JVM flag is enabled)
     pub fn new() -> anyhow::Result<Self> {
         #[allow(unused_mut)]
         let mut result = Self {
@@ -284,6 +284,12 @@ impl ScriptManager {
     }
 }
 
+/// Fetches the gradle command available for that operating system.
+///
+/// # Platform-specific behaviours
+/// - `windows` - Windows uses `gradlew.bat`
+/// - `linux` - Linux uses `./gradlew`
+/// - `macos` - macOS uses `./gradlew`
 fn get_gradle_command(project_root: impl AsRef<Path>) -> String {
     let project_root = project_root.as_ref().to_owned();
     if cfg!(target_os = "windows") {
@@ -303,7 +309,7 @@ fn get_gradle_command(project_root: impl AsRef<Path>) -> String {
     }
 }
 
-/// Asynchronously builds a project for the JVM using gradle. 
+/// Asynchronously builds a project for the JVM using gradle.
 pub async fn build_jvm(
     project_root: impl AsRef<Path>,
     status_sender: Sender<BuildStatus>,
@@ -450,9 +456,11 @@ pub async fn build_native(
     };
 
     let lib_filename = format!("{}.{}", lib_name, lib_ext);
-    
+
     let current_exe = std::env::current_exe().context("Failed to get current executable path")?;
-    let exe_dir = current_exe.parent().context("Failed to get executable directory")?;
+    let exe_dir = current_exe
+        .parent()
+        .context("Failed to get executable directory")?;
     let source_lib_path = exe_dir.join(&lib_filename);
 
     if source_lib_path.exists() {
@@ -461,19 +469,22 @@ pub async fn build_native(
     } else {
         let cwd_lib_path = std::env::current_dir()?.join(&lib_filename);
         if cwd_lib_path.exists() {
-             std::fs::copy(&cwd_lib_path, libs_dir.join(&lib_filename))
+            std::fs::copy(&cwd_lib_path, libs_dir.join(&lib_filename))
                 .context(format!("Failed to copy {} to libs", lib_filename))?;
         } else {
-             let err = format!("Could not find core library {} to copy", lib_filename);
-             let _ = status_sender.send(BuildStatus::Failed(err.clone()));
-             return Err(anyhow::anyhow!(err));
+            let err = format!("Could not find core library {} to copy", lib_filename);
+            let _ = status_sender.send(BuildStatus::Failed(err.clone()));
+            return Err(anyhow::anyhow!(err));
         }
     }
 
     let _ = status_sender.send(BuildStatus::Started);
 
     let gradle_cmd = get_gradle_command(project_root);
-    let _ = status_sender.send(BuildStatus::Building(format!("Running: {} build", gradle_cmd)));
+    let _ = status_sender.send(BuildStatus::Building(format!(
+        "Running: {} build",
+        gradle_cmd
+    )));
 
     let mut child = Command::new(&gradle_cmd)
         .current_dir(project_root)
@@ -518,7 +529,10 @@ pub async fn build_native(
 
     let output_dir = project_root.join("build/bin/nativeLib/releaseShared");
     if !output_dir.exists() {
-        let err = format!("Build succeeded but output directory missing: {:?}", output_dir);
+        let err = format!(
+            "Build succeeded but output directory missing: {:?}",
+            output_dir
+        );
         let _ = status_sender.send(BuildStatus::Failed(err.clone()));
         return Err(anyhow::anyhow!(err));
     }
diff --git a/eucalyptus-core/src/scripting/jni/exports.rs b/eucalyptus-core/src/scripting/jni/exports.rs
index ab5549c..ada2aec 100644
--- a/eucalyptus-core/src/scripting/jni/exports.rs
+++ b/eucalyptus-core/src/scripting/jni/exports.rs
@@ -20,7 +20,10 @@ use glam::{DQuat, DVec3};
 use hecs::World;
 use jni::JNIEnv;
 use jni::objects::{JClass, JObject, JPrimitiveArray, JString, JValue};
-use jni::sys::{JNI_FALSE, jboolean, jclass, jdouble, jfloatArray, jint, jlong, jobject, jobjectArray, jstring, jlongArray};
+use jni::sys::{
+    JNI_FALSE, jboolean, jclass, jdouble, jfloatArray, jint, jlong, jlongArray, jobject,
+    jobjectArray, jstring,
+};
 use parking_lot::Mutex;
 use std::collections::{HashMap, HashSet};
 use std::sync::Arc;
@@ -2490,7 +2493,9 @@ pub fn Java_com_dropbear_ffi_JNINative_getChildren(
     let world = convert_ptr!(world_handle, WorldPtr => World);
     let entity = convert_jlong_to_entity!(entity_id);
 
-    let entities = if let Ok(mut q) = world.query_one::<&Children>(entity) && let Some(children) = q.get() {
+    let entities = if let Ok(mut q) = world.query_one::<&Children>(entity)
+        && let Some(children) = q.get()
+    {
         let children = children.children();
         let mut array = vec![];
         for child in children {
@@ -2529,7 +2534,9 @@ pub fn Java_com_dropbear_ffi_JNINative_getChildByLabel(
     let entity = convert_jlong_to_entity!(entity_id);
     let target = convert_jstring!(env, label);
 
-    if let Ok(mut q) = world.query_one::<&Children>(entity) && let Some(children) = q.get() {
+    if let Ok(mut q) = world.query_one::<&Children>(entity)
+        && let Some(children) = q.get()
+    {
         for child in children.children() {
             if let Ok(label) = world.get::<&Label>(entity) {
                 if label.as_str() == target {
@@ -2540,7 +2547,6 @@ pub fn Java_com_dropbear_ffi_JNINative_getChildByLabel(
                 continue;
             }
         }
-
     } else {
         // no children exist for the entity
         return -2 as jlong;
@@ -2571,4 +2577,4 @@ pub fn Java_com_dropbear_ffi_JNINative_getParent(
     } else {
         crate::ffi_error_return!("No entity exists")
     }
-}
\ No newline at end of file
+}
diff --git a/eucalyptus-core/src/states.rs b/eucalyptus-core/src/states.rs
index 517230d..8277fe3 100644
--- a/eucalyptus-core/src/states.rs
+++ b/eucalyptus-core/src/states.rs
@@ -6,6 +6,7 @@ use dropbear_engine::camera::Camera;
 use dropbear_engine::entity::{MaterialOverride, MeshRenderer, Transform};
 use dropbear_engine::lighting::LightComponent;
 use dropbear_engine::utils::ResourceReference;
+use dropbear_engine::{MutableWindowConfiguration};
 use dropbear_macro::SerializableComponent;
 use egui::Ui;
 use once_cell::sync::Lazy;
@@ -18,7 +19,7 @@ use std::fmt::{Display, Formatter};
 use std::ops::{Deref, DerefMut};
 use std::path::PathBuf;
 
-/// A global "singleton" that contains the configuration of a project.  
+/// A global "singleton" that contains the configuration of a project.
 pub static PROJECT: Lazy<RwLock<ProjectConfig>> =
     Lazy::new(|| RwLock::new(ProjectConfig::default()));
 
@@ -182,18 +183,15 @@ impl Default for Camera3D {
 }
 
 impl Camera3D {
-    pub fn from_ecs_camera(
-        camera: &Camera,
-        component: &CameraComponent,
-    ) -> Self {
+    pub fn from_ecs_camera(camera: &Camera, component: &CameraComponent) -> Self {
         let position = glam::DVec3::from_array(camera.eye.to_array());
         let target = glam::DVec3::from_array(camera.target.to_array());
         let up = glam::DVec3::from_array(camera.up.to_array());
-        
+
         let rotation = if (target - position).length_squared() > 0.0001 {
-             glam::DQuat::from_mat4(&glam::DMat4::look_at_lh(position, target, up)).inverse()
+            glam::DQuat::from_mat4(&glam::DMat4::look_at_lh(position, target, up)).inverse()
         } else {
-             glam::DQuat::IDENTITY
+            glam::DQuat::IDENTITY
         };
 
         let transform = Transform {
@@ -542,14 +540,14 @@ impl SerializableComponent for SerializedMeshRenderer {
         self
     }
 
-    fn clone_boxed(&self) -> Box<dyn SerializableComponent> {
-        Box::new(self.clone())
-    }
-
     fn type_name(&self) -> &'static str {
         "SerializedMeshRenderer"
     }
 
+    fn clone_boxed(&self) -> Box<dyn SerializableComponent> {
+        Box::new(self.clone())
+    }
+
     fn display_name(&self) -> String {
         "MeshRenderer".to_string()
     }
@@ -565,3 +563,12 @@ impl SerializedMeshRenderer {
         }
     }
 }
+
+/// A file called `config.eucfg` that contains all the file-editable contents of
+#[derive(Debug, Clone, Deserialize, Serialize, bincode::Encode, bincode::Decode)]
+pub struct ConfigFile {
+    pub jvm_args: Option<String>,
+
+    #[bincode(with_serde)]
+    pub window_configuration: MutableWindowConfiguration,
+}
diff --git a/eucalyptus-editor/Cargo.toml b/eucalyptus-editor/Cargo.toml
index 389d10d..70064ab 100644
--- a/eucalyptus-editor/Cargo.toml
+++ b/eucalyptus-editor/Cargo.toml
@@ -48,6 +48,7 @@ chrono.workspace = true
 egui_ltreeview.workspace = true
 ron.workspace = true
 rfd.workspace = true
+semver.workspace = true
 
 [features]
 default = ["editor"]
diff --git a/eucalyptus-editor/src/build.rs b/eucalyptus-editor/src/build.rs
index 1ffd9b4..73e3554 100644
--- a/eucalyptus-editor/src/build.rs
+++ b/eucalyptus-editor/src/build.rs
@@ -1,11 +1,12 @@
 use eucalyptus_core::config::ProjectConfig;
 use eucalyptus_core::runtime::RuntimeProjectConfig;
 use eucalyptus_core::scene::SceneConfig;
+use semver::Version;
 use std::fs;
 use std::path::{Path, PathBuf};
 
-/// Builds a eucalyptus project into a single bundle. 
-/// 
+/// Builds a eucalyptus project into a single bundle.
+///
 /// Returns the path of the build directory
 pub fn build(project_config: PathBuf) -> anyhow::Result<PathBuf> {
     log::info!("Started project building");
@@ -53,6 +54,16 @@ pub fn build(project_config: PathBuf) -> anyhow::Result<PathBuf> {
         project_name: config.project_name.clone(),
         runtime_settings: config.runtime_settings.clone(),
         scenes,
+        authors: config.authors.clone(),
+        editor_version: Version::parse(env!("CARGO_PKG_VERSION"))?,
+        project_version: Version::parse(
+            config
+                .project_version
+                .clone()
+                .unwrap_or(String::from("0.1.0"))
+                .as_str(),
+        )?,
+        initial_scene: config.runtime_settings.initial_scene.ok_or(anyhow::anyhow!("Project was expected to be an initial scene"))?,
     };
     log::debug!("Converted to runtime project config");
 
@@ -61,7 +72,7 @@ pub fn build(project_config: PathBuf) -> anyhow::Result<PathBuf> {
     let config_bytes = bincode::encode_to_vec(&runtime_config, bincode::config::standard())?;
     fs::write(&eupak_path, config_bytes)?;
     log::debug!("Exported scene config to {:?}", eupak_path);
-    
+
     // copy resources
     let resources_src = project_root.join("resources");
     let resources_dst = build_dir.join("resources");
@@ -89,13 +100,13 @@ fn copy_dir_recursive(src: &Path, dst: &Path) -> anyhow::Result<()> {
     Ok(())
 }
 
-/// Reads the contents of a data.eupak file into a pretty print format. 
-/// 
-/// Returns the contents of the project config. 
+/// Reads the contents of a data.eupak file into a pretty print format.
+///
+/// Returns the contents of the project config.
 pub fn read(eupak: PathBuf) -> anyhow::Result<RuntimeProjectConfig> {
     let bytes = std::fs::read(&eupak)?;
     let (content, _): (RuntimeProjectConfig, usize) =
         bincode::decode_from_slice(&bytes, bincode::config::standard())?;
     println!("{} contents: {:#?}", eupak.display(), content);
     Ok(content)
-}
\ No newline at end of file
+}
diff --git a/eucalyptus-editor/src/editor/component.rs b/eucalyptus-editor/src/editor/component.rs
index b5d2645..c189456 100644
--- a/eucalyptus-editor/src/editor/component.rs
+++ b/eucalyptus-editor/src/editor/component.rs
@@ -8,8 +8,8 @@ use dropbear_engine::graphics::NO_TEXTURE;
 use dropbear_engine::lighting::{LightComponent, LightType};
 use dropbear_engine::utils::ResourceReference;
 use egui::{CollapsingHeader, ComboBox, DragValue, Grid, RichText, TextEdit, Ui, UiBuilder};
-use eucalyptus_core::states::{Camera3D, Light, ModelProperties, Property, Script, Value};
 use eucalyptus_core::camera::CameraType;
+use eucalyptus_core::states::{Camera3D, Light, ModelProperties, Property, Script, Value};
 use eucalyptus_core::{fatal, warn};
 use glam::{DVec3, Vec3};
 use hecs::Entity;
@@ -646,17 +646,7 @@ impl InspectableComponent for Transform {
         _signal: &mut Signal,
         label: &mut String,
     ) {
-        inspect_transform(
-            self,
-            entity,
-            cfg,
-            ui,
-            undo_stack,
-            label,
-            true,
-            true,
-            true,
-        );
+        inspect_transform(self, entity, cfg, ui, undo_stack, label, true, true, true);
     }
 }
 
@@ -833,14 +823,15 @@ impl InspectableComponent for MeshRenderer {
                 ui.horizontal(|ui| {
                     ui.label("URI:");
                     let id = ui.make_persistent_id("mesh_renderer_uri_input");
-                    let mut uri_string = ui.data_mut(|d| d.get_temp::<String>(id).unwrap_or_default());
-                    
+                    let mut uri_string =
+                        ui.data_mut(|d| d.get_temp::<String>(id).unwrap_or_default());
+
                     ui.text_edit_singleline(&mut uri_string);
-                    
+
                     if ui.button("Load").clicked() {
                         *signal = Signal::LoadModel(*entity, uri_string.clone());
                     }
-                    
+
                     ui.data_mut(|d| d.insert_temp(id, uri_string));
                 });
 
@@ -1177,7 +1168,10 @@ impl InspectableComponent for Light {
         signal: &mut Signal,
         label: &mut String,
     ) {
-        let show_position = matches!(self.light_component.light_type, LightType::Point | LightType::Spot);
+        let show_position = matches!(
+            self.light_component.light_type,
+            LightType::Point | LightType::Spot
+        );
 
         inspect_transform(
             &mut self.transform,
@@ -1191,7 +1185,8 @@ impl InspectableComponent for Light {
             true,
         );
 
-        self.light_component.inspect(entity, cfg, ui, undo_stack, signal, label);
+        self.light_component
+            .inspect(entity, cfg, ui, undo_stack, signal, label);
     }
 }
 
@@ -1205,7 +1200,8 @@ impl InspectableComponent for Camera3D {
         signal: &mut Signal,
         label: &mut String,
     ) {
-        self.transform.inspect(entity, cfg, ui, undo_stack, signal, label);
+        self.transform
+            .inspect(entity, cfg, ui, undo_stack, signal, label);
 
         ui.vertical(|ui| {
             CollapsingHeader::new("Camera Settings")
@@ -1216,9 +1212,21 @@ impl InspectableComponent for Camera3D {
                         ComboBox::from_id_salt("camera_type")
                             .selected_text(format!("{:?}", self.camera_type))
                             .show_ui(ui, |ui| {
-                                ui.selectable_value(&mut self.camera_type, CameraType::Normal, "Normal");
-                                ui.selectable_value(&mut self.camera_type, CameraType::Debug, "Debug");
-                                ui.selectable_value(&mut self.camera_type, CameraType::Player, "Player");
+                                ui.selectable_value(
+                                    &mut self.camera_type,
+                                    CameraType::Normal,
+                                    "Normal",
+                                );
+                                ui.selectable_value(
+                                    &mut self.camera_type,
+                                    CameraType::Debug,
+                                    "Debug",
+                                );
+                                ui.selectable_value(
+                                    &mut self.camera_type,
+                                    CameraType::Player,
+                                    "Player",
+                                );
                             });
                     });
 
@@ -1229,12 +1237,20 @@ impl InspectableComponent for Camera3D {
 
                     ui.horizontal(|ui| {
                         ui.label("Near Plane");
-                        ui.add(egui::DragValue::new(&mut self.near).speed(0.1).range(0.01..=1000.0));
+                        ui.add(
+                            egui::DragValue::new(&mut self.near)
+                                .speed(0.1)
+                                .range(0.01..=1000.0),
+                        );
                     });
 
                     ui.horizontal(|ui| {
                         ui.label("Far Plane");
-                        ui.add(egui::DragValue::new(&mut self.far).speed(1.0).range(0.1..=10000.0));
+                        ui.add(
+                            egui::DragValue::new(&mut self.far)
+                                .speed(1.0)
+                                .range(0.1..=10000.0),
+                        );
                     });
 
                     ui.separator();
diff --git a/eucalyptus-editor/src/editor/dock.rs b/eucalyptus-editor/src/editor/dock.rs
index 88604b8..df90e1f 100644
--- a/eucalyptus-editor/src/editor/dock.rs
+++ b/eucalyptus-editor/src/editor/dock.rs
@@ -24,11 +24,9 @@ use dropbear_engine::{
 use egui::{self, Margin, RichText};
 use egui_dock::TabViewer;
 use egui_ltreeview::{NodeBuilder, TreeViewBuilder};
-use eucalyptus_core::states::{
-    Label, Light, ModelProperties, PROJECT, Script,
-};
-use eucalyptus_core::traits::registry::ComponentRegistry;
 use eucalyptus_core::hierarchy::{Children, Hierarchy, Parent};
+use eucalyptus_core::states::{Label, Light, ModelProperties, PROJECT, Script};
+use eucalyptus_core::traits::registry::ComponentRegistry;
 use hecs::{Entity, EntityBuilder, World};
 use indexmap::Equivalent;
 use log;
@@ -334,9 +332,21 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                             let parent_pos = parent_transform.position;
 
                             let safe_parent_scale = glam::DVec3::new(
-                                if parent_scale.x.abs() < 1e-6 { 1.0 } else { parent_scale.x },
-                                if parent_scale.y.abs() < 1e-6 { 1.0 } else { parent_scale.y },
-                                if parent_scale.z.abs() < 1e-6 { 1.0 } else { parent_scale.z },
+                                if parent_scale.x.abs() < 1e-6 {
+                                    1.0
+                                } else {
+                                    parent_scale.x
+                                },
+                                if parent_scale.y.abs() < 1e-6 {
+                                    1.0
+                                } else {
+                                    parent_scale.y
+                                },
+                                if parent_scale.z.abs() < 1e-6 {
+                                    1.0
+                                } else {
+                                    parent_scale.z
+                                },
                             );
 
                             let local_transform = entity_transform.local_mut();
@@ -468,25 +478,46 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                                             if name.contains("EntityTransform") {
                                                 continue;
                                             }
-                                            let short_name = name.split("::").last().unwrap_or(name);
-                                            let display_name = if short_name == "SerializedMeshRenderer" {
-                                                "MeshRenderer"
-                                            } else {
-                                                short_name
-                                            };
+                                            let short_name =
+                                                name.split("::").last().unwrap_or(name);
+                                            let display_name =
+                                                if short_name == "SerializedMeshRenderer" {
+                                                    "MeshRenderer"
+                                                } else {
+                                                    short_name
+                                                };
 
                                             if ui.button(display_name).clicked() {
                                                 if name.contains("MeshRenderer") {
-                                                    *signal = Signal::AddComponent(entity, "MeshRenderer".to_string());
-                                                } else if name.contains("CameraComponent") || name.contains("Camera3D") {
-                                                    *signal = Signal::AddComponent(entity, "CameraComponent".to_string());
+                                                    *signal = Signal::AddComponent(
+                                                        entity,
+                                                        "MeshRenderer".to_string(),
+                                                    );
+                                                } else if name.contains("CameraComponent")
+                                                    || name.contains("Camera3D")
+                                                {
+                                                    *signal = Signal::AddComponent(
+                                                        entity,
+                                                        "CameraComponent".to_string(),
+                                                    );
                                                 } else if name.contains("Light") {
-                                                    *signal = Signal::AddComponent(entity, "Light".to_string());
+                                                    *signal = Signal::AddComponent(
+                                                        entity,
+                                                        "Light".to_string(),
+                                                    );
                                                 } else {
-                                                    if let Some(comp) = registry.create_default_component(id) {
+                                                    if let Some(comp) =
+                                                        registry.create_default_component(id)
+                                                    {
                                                         let mut builder = EntityBuilder::new();
-                                                        if let Ok(_) = registry.deserialize_into_builder(comp.as_ref(), &mut builder) {
-                                                            let _ = world.insert(entity, builder.build());
+                                                        if let Ok(_) = registry
+                                                            .deserialize_into_builder(
+                                                                comp.as_ref(),
+                                                                &mut builder,
+                                                            )
+                                                        {
+                                                            let _ = world
+                                                                .insert(entity, builder.build());
                                                         }
                                                     }
                                                 }
@@ -514,21 +545,27 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                             };
                             let component_node_id =
                                 cfg.component_node_id(entity, component_type_id);
-                            let display = format!("{} (id #{component_type_id})", component.display_name());
+                            let display =
+                                format!("{} (id #{component_type_id})", component.display_name());
 
                             builder.node(
                                 NodeBuilder::leaf(component_node_id)
                                     .label(display)
                                     .context_menu(|ui| {
                                         if ui.button("Remove Component").clicked() {
-                                            registry.remove_component_by_id(world, entity, component_type_id);
+                                            registry.remove_component_by_id(
+                                                world,
+                                                entity,
+                                                component_type_id,
+                                            );
                                             ui.close();
                                         }
                                     }),
                             );
                         }
 
-                        let children_entities = if let Ok(children) = world.get::<&Children>(entity) {
+                        let children_entities = if let Ok(children) = world.get::<&Children>(entity)
+                        {
                             children.children().to_vec()
                         } else {
                             Vec::new()
@@ -550,7 +587,13 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                         Ok(())
                     }
 
-                    let root_entities: Vec<Entity> = self.world.query::<()>().without::<&Parent>().iter().map(|(e, _)| e).collect();
+                    let root_entities: Vec<Entity> = self
+                        .world
+                        .query::<()>()
+                        .without::<&Parent>()
+                        .iter()
+                        .map(|(e, _)| e)
+                        .collect();
 
                     for entity in root_entities {
                         if let Err(e) = add_entity_to_tree(
@@ -714,15 +757,13 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                                 }
                             }
 
-                            if let Ok(mut q) = self
-                                .world
-                                .query_one::<(
-                                    &mut Light,
-                                    Option<&mut Transform>,
-                                    Option<&mut EntityTransform>,
-                                    Option<&mut LightComponent>,
-                                    Option<&mut EngineLight>,
-                                )>(*entity)
+                            if let Ok(mut q) = self.world.query_one::<(
+                                &mut Light,
+                                Option<&mut Transform>,
+                                Option<&mut EntityTransform>,
+                                Option<&mut LightComponent>,
+                                Option<&mut EngineLight>,
+                            )>(*entity)
                                 && let Some((
                                     light,
                                     mut transform_opt,
@@ -746,7 +787,8 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                                     &mut String::new(),
                                 );
 
-                                if let Some(entity_transform) = entity_transform_opt.as_deref_mut() {
+                                if let Some(entity_transform) = entity_transform_opt.as_deref_mut()
+                                {
                                     let parent_transform = entity_transform.world();
                                     let parent_rot = parent_transform.rotation;
                                     let parent_scale = parent_transform.scale;
@@ -757,9 +799,21 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                                     let new_world_scale = light.transform.scale;
 
                                     let safe_parent_scale = glam::DVec3::new(
-                                        if parent_scale.x.abs() < 1e-6 { 1.0 } else { parent_scale.x },
-                                        if parent_scale.y.abs() < 1e-6 { 1.0 } else { parent_scale.y },
-                                        if parent_scale.z.abs() < 1e-6 { 1.0 } else { parent_scale.z },
+                                        if parent_scale.x.abs() < 1e-6 {
+                                            1.0
+                                        } else {
+                                            parent_scale.x
+                                        },
+                                        if parent_scale.y.abs() < 1e-6 {
+                                            1.0
+                                        } else {
+                                            parent_scale.y
+                                        },
+                                        if parent_scale.z.abs() < 1e-6 {
+                                            1.0
+                                        } else {
+                                            parent_scale.z
+                                        },
                                     );
 
                                     let local_transform = entity_transform.local_mut();
@@ -1129,7 +1183,11 @@ impl<'a> EditorTabViewer<'a> {
                     source_path.display(),
                     err
                 );
-                Self::add_placeholder_leaf(builder, &format!("{source_label}/unreadable"), "unreadable");
+                Self::add_placeholder_leaf(
+                    builder,
+                    &format!("{source_label}/unreadable"),
+                    "unreadable",
+                );
                 return true;
             }
         };
@@ -1176,7 +1234,11 @@ impl<'a> EditorTabViewer<'a> {
                     dir_path.display(),
                     err
                 );
-                Self::add_placeholder_leaf(builder, &format!("{parent_label}/unreadable"), "unreadable");
+                Self::add_placeholder_leaf(
+                    builder,
+                    &format!("{parent_label}/unreadable"),
+                    "unreadable",
+                );
                 return;
             }
         };
@@ -1211,13 +1273,21 @@ impl<'a> EditorTabViewer<'a> {
                     kotlin_path.display(),
                     err
                 );
-                Self::add_placeholder_leaf(builder, &format!("{source_label}/unreadable"), "unreadable");
+                Self::add_placeholder_leaf(
+                    builder,
+                    &format!("{source_label}/unreadable"),
+                    "unreadable",
+                );
                 return true;
             }
         };
 
         if entries.is_empty() {
-            Self::add_placeholder_leaf(builder, &format!("{source_label}/no_kotlin_files"), "no kotlin files");
+            Self::add_placeholder_leaf(
+                builder,
+                &format!("{source_label}/no_kotlin_files"),
+                "no kotlin files",
+            );
             return true;
         }
 
@@ -1258,7 +1328,11 @@ impl<'a> EditorTabViewer<'a> {
                     dir_path.display(),
                     err
                 );
-                Self::add_placeholder_leaf(builder, &format!("{full_path_str}/unreadable"), "unreadable");
+                Self::add_placeholder_leaf(
+                    builder,
+                    &format!("{full_path_str}/unreadable"),
+                    "unreadable",
+                );
                 return;
             }
         };
@@ -1392,7 +1466,10 @@ impl<'a> EditorTabViewer<'a> {
     }
 
     fn with_icon<'ui>(builder: NodeBuilder<'ui, u64>) -> NodeBuilder<'ui, u64> {
-        builder.icon(|ui| {egui_extras::install_image_loaders(ui.ctx()); Self::draw_asset_icon(ui)})
+        builder.icon(|ui| {
+            egui_extras::install_image_loaders(ui.ctx());
+            Self::draw_asset_icon(ui)
+        })
     }
 
     fn draw_asset_icon(ui: &mut egui::Ui) {
diff --git a/eucalyptus-editor/src/editor/input.rs b/eucalyptus-editor/src/editor/input.rs
index 02dd0fa..bb88b64 100644
--- a/eucalyptus-editor/src/editor/input.rs
+++ b/eucalyptus-editor/src/editor/input.rs
@@ -279,7 +279,7 @@ impl Mouse for Editor {
             if let Some(window) = &self.window {
                 window.set_cursor_visible(false);
                 if let Err(e) = window.set_cursor_grab(CursorGrabMode::Locked).or_else(|_| {
-                    log_once::warn_once!("Using cursor grab fallback: CursorGrabMode::Locked");
+                    log_once::warn_once!("Using cursor grab fallback: CursorGrabMode::Confined");
                     window.set_cursor_grab(CursorGrabMode::Confined)
                 }) {
                     log_once::error_once!("Unable to grab mouse: {}", e);
diff --git a/eucalyptus-editor/src/editor/mod.rs b/eucalyptus-editor/src/editor/mod.rs
index 8d63d11..37f8dbd 100644
--- a/eucalyptus-editor/src/editor/mod.rs
+++ b/eucalyptus-editor/src/editor/mod.rs
@@ -40,8 +40,7 @@ use eucalyptus_core::{
     scripting::{BuildStatus, ScriptManager, ScriptTarget},
     states,
     states::{
-        Camera3D, EditorTab, Light, ModelProperties, PROJECT, SCENES, Script,
-        WorldLoadingStatus,
+        Camera3D, EditorTab, Light, ModelProperties, PROJECT, SCENES, Script, WorldLoadingStatus,
     },
     success, success_without_console,
     utils::ViewportMode,
@@ -77,7 +76,7 @@ pub struct Editor {
     pub light_manager: LightManager,
     pub color: Color,
 
-    pub active_camera: Arc<Mutex<Option<hecs::Entity>>>,
+    pub active_camera: Arc<Mutex<Option<Entity>>>,
 
     pub is_viewport_focused: bool,
     // is_cursor_locked: bool,
@@ -1106,14 +1105,14 @@ impl Editor {
 
                     ui.add_space(12.0);
 
-                    ui.label("Made with love by 4tkbytes ♥️");
+                    ui.label("Made with love by tirbofish ♥️");
 
                     ui.add_space(12.0);
 
                     ui.horizontal(|ui| {
                         ui.label("Check out the repository at");
-                        if ui.label("https://github.com/4tkbytes/dropbear").clicked() {
-                            let _ = open::that("https://github.com/4tkbytes/dropbear");
+                        if ui.label("https://github.com/tirbofish/dropbear").clicked() {
+                            let _ = open::that("https://github.com/tirbofish/dropbear");
                         }
                     });
 
diff --git a/eucalyptus-editor/src/editor/scene.rs b/eucalyptus-editor/src/editor/scene.rs
index b0c5178..4597f45 100644
--- a/eucalyptus-editor/src/editor/scene.rs
+++ b/eucalyptus-editor/src/editor/scene.rs
@@ -10,9 +10,9 @@ use dropbear_engine::{
     model::{DrawLight, DrawModel},
     scene::{Scene, SceneCommand},
 };
+use eucalyptus_core::hierarchy::EntityTransformExt;
 use eucalyptus_core::logging;
 use eucalyptus_core::states::{Label, WorldLoadingStatus};
-use eucalyptus_core::hierarchy::{EntityTransformExt};
 use eucalyptus_core::window::poll;
 use log;
 use parking_lot::Mutex;
@@ -297,7 +297,10 @@ impl Scene for Editor {
                 }
 
                 for (entity, final_transform) in updates {
-                    if let Ok(mut q) = self.world.query_one::<(&mut LightComponent, &mut Light)>(entity) {
+                    if let Ok(mut q) = self
+                        .world
+                        .query_one::<(&mut LightComponent, &mut Light)>(entity)
+                    {
                         if let Some((light_component, light)) = q.get() {
                             light.update(light_component, &final_transform);
                         }
diff --git a/eucalyptus-editor/src/main.rs b/eucalyptus-editor/src/main.rs
index 89389da..489e5cc 100644
--- a/eucalyptus-editor/src/main.rs
+++ b/eucalyptus-editor/src/main.rs
@@ -1,5 +1,5 @@
 // #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
-// note to self: when it becomes release, remember to readd this back
+// note to self: when it becomes release, remember to re-add this back
 
 use clap::{Arg, Command};
 use dropbear_engine::future::FutureQueue;
@@ -115,14 +115,12 @@ async fn main() -> anyhow::Result<()> {
                 ),
         )
         .subcommand(
-            Command::new("read")
-                .about("Reads a .eupak file")
-                .arg(
-                    Arg::new("eupak_file")
-                        .help("Path to the .eupak data file")
-                        .value_name("EUPAK_FILE")
-                        .required(true)
-                ),
+            Command::new("read").about("Reads a .eupak file").arg(
+                Arg::new("eupak_file")
+                    .help("Path to the .eupak data file")
+                    .value_name("EUPAK_FILE")
+                    .required(true),
+            ),
         )
         .get_matches();
 
@@ -144,7 +142,10 @@ async fn main() -> anyhow::Result<()> {
         Some(("read", sub_matches)) => {
             let eupak = match sub_matches.get_one::<String>("eupak_file") {
                 Some(path) => PathBuf::from(path),
-                None => {log::error!("Eupak file returned none"); std::process::exit(1)},
+                None => {
+                    log::error!("Eupak file returned none");
+                    std::process::exit(1)
+                }
             };
 
             build::read(eupak)?;
diff --git a/eucalyptus-editor/src/menu.rs b/eucalyptus-editor/src/menu.rs
index 84d72df..9925fdf 100644
--- a/eucalyptus-editor/src/menu.rs
+++ b/eucalyptus-editor/src/menu.rs
@@ -157,7 +157,7 @@ impl MainMenu {
                         }
                         "gradle" => {
                             log::debug!("Cloning gradle template from GitHub");
-                            let url = "https://github.com/4tkbytes/eucalyptus-gradle-template";
+                            let url = "https://github.com/tirbofish/eucalyptus-gradle-template";
 
                             fs::create_dir_all(path)
                                 .context("Failed to create project directory")?;
diff --git a/eucalyptus-editor/src/signal.rs b/eucalyptus-editor/src/signal.rs
index e9bb78a..26caf5b 100644
--- a/eucalyptus-editor/src/signal.rs
+++ b/eucalyptus-editor/src/signal.rs
@@ -495,10 +495,7 @@ impl SignalController for Editor {
                     "typeid of CameraComponent: {:?}",
                     TypeId::of::<CameraComponent>()
                 );
-                log::info!(
-                    "typeid of Script: {:?}",
-                    TypeId::of::<Script>()
-                );
+                log::info!("typeid of Script: {:?}", TypeId::of::<Script>());
                 log::info!("typeid of EngineLight: {:?}", TypeId::of::<EngineLight>());
                 log::info!(
                     "typeid of LightComponent: {:?}",
@@ -633,8 +630,9 @@ impl SignalController for Editor {
                         .await?;
 
                         let model = loaded_model.make_mut();
-                        model.path =
-                            ResourceReference::from_euca_uri("euca://internal/dropbear/models/cube")?;
+                        model.path = ResourceReference::from_euca_uri(
+                            "euca://internal/dropbear/models/cube",
+                        )?;
 
                         loaded_model.refresh_registry();
 
@@ -665,8 +663,9 @@ impl SignalController for Editor {
                             light_comp.clone(),
                             transform,
                             Some("New Light"),
-                        ).await;
-                        
+                        )
+                        .await;
+
                         let light_config = Light {
                             label: "New Light".to_string(),
                             transform,
@@ -675,13 +674,21 @@ impl SignalController for Editor {
                             entity_id: None,
                         };
 
-                        Ok::<(LightComponent, EngineLight, Light, Transform), anyhow::Error>((light_comp, engine_light, light_config, transform))
+                        Ok::<(LightComponent, EngineLight, Light, Transform), anyhow::Error>((
+                            light_comp,
+                            engine_light,
+                            light_config,
+                            transform,
+                        ))
                     };
                     let handle = graphics.future_queue.push(Box::pin(future));
                     self.pending_components.push((*entity, handle));
                     success!("Queued Light addition for entity {:?}", entity);
                 } else {
-                    warn!("Unknown component type for AddComponent signal: {}", component_name);
+                    warn!(
+                        "Unknown component type for AddComponent signal: {}",
+                        component_name
+                    );
                 }
                 self.signal = Signal::None;
                 Ok(())
@@ -727,7 +734,7 @@ impl SignalController for Editor {
                         )
                     }
                 };
-                
+
                 let handle = graphics.future_queue.push(Box::pin(future));
                 self.pending_components.push((*entity, handle));
                 success!("Queued model load for entity {:?} from '{}'", entity, uri);
diff --git a/eucalyptus-editor/src/spawn.rs b/eucalyptus-editor/src/spawn.rs
index e93dcfa..0bb7d65 100644
--- a/eucalyptus-editor/src/spawn.rs
+++ b/eucalyptus-editor/src/spawn.rs
@@ -10,7 +10,9 @@ use dropbear_engine::utils::{ResourceReference, ResourceReferenceType};
 use eucalyptus_core::camera::CameraComponent;
 use eucalyptus_core::scene::SceneEntity;
 pub(crate) use eucalyptus_core::spawn::{PENDING_SPAWNS, PendingSpawnController};
-use eucalyptus_core::states::{Light as LightConfig, ModelProperties, Script, SerializedMeshRenderer};
+use eucalyptus_core::states::{
+    Light as LightConfig, ModelProperties, Script, SerializedMeshRenderer,
+};
 use eucalyptus_core::utils::ResolveReference;
 use eucalyptus_core::{fatal, success};
 use hecs::EntityBuilder;
@@ -144,10 +146,23 @@ impl PendingSpawnController for Editor {
                                 }
                             }
                             Err(result) => {
-                                if let Ok(r) = result.downcast::<anyhow::Result<(LightComponent, Light, LightConfig, Transform)>>() {
+                                if let Ok(r) = result.downcast::<anyhow::Result<(
+                                    LightComponent,
+                                    Light,
+                                    LightConfig,
+                                    Transform,
+                                )>>() {
                                     match Arc::try_unwrap(r) {
-                                        Ok(Ok((light_comp, engine_light, light_config, transform))) => {
-                                            let _ = self.world.insert(*entity, (light_comp, engine_light, light_config, transform));
+                                        Ok(Ok((
+                                            light_comp,
+                                            engine_light,
+                                            light_config,
+                                            transform,
+                                        ))) => {
+                                            let _ = self.world.insert(
+                                                *entity,
+                                                (light_comp, engine_light, light_config, transform),
+                                            );
                                             success!("Added Light to entity {:?}", entity);
                                             completed_components.push(index);
                                         }
@@ -228,7 +243,8 @@ async fn load_renderer_from_serialized(
                 .await?;
 
                 let model = loaded_model.make_mut();
-                model.path = ResourceReference::from_euca_uri("euca://internal/dropbear/models/cube")?;
+                model.path =
+                    ResourceReference::from_euca_uri("euca://internal/dropbear/models/cube")?;
 
                 loaded_model.refresh_registry();
 
diff --git a/headers/dropbear.h b/headers/dropbear.h
index 44cbf49..ffebeac 100644
--- a/headers/dropbear.h
+++ b/headers/dropbear.h
@@ -1,5 +1,5 @@
 /**
- * dropbear-engine native header definitions. Created by 4tkbytes as part of the dropbear project. 
+ * dropbear-engine native header definitions. Created by tirbofish as part of the dropbear project. 
  * 
  * Primarily used for Kotlin/Native, however nothing is stopping you from implementing it to your own language. 
  * Exports are located at `eucalyptus_core::scripting::native::exports`. 
diff --git a/magna-carta-plugin/build.gradle.kts b/magna-carta-plugin/build.gradle.kts
index ae2f669..303c21e 100644
--- a/magna-carta-plugin/build.gradle.kts
+++ b/magna-carta-plugin/build.gradle.kts
@@ -19,8 +19,8 @@ dependencies {
 }
 
 gradlePlugin {
-    website.set("https://github.com/4tkbytes/dropbear")
-    vcsUrl.set("https://github.com/4tkbytes/dropbear")
+    website.set("https://github.com/tirbofish/dropbear")
+    vcsUrl.set("https://github.com/tirbofish/dropbear")
     plugins {
 
         create("magnaCartaPlugin") {
@@ -40,7 +40,7 @@ publishing {
             pom {
                 name.set("magna-carta")
                 description.set("Gradle plugin for manifest generation for the Dropbear engine")
-                url.set("https://4tkbytes.github.io/dropbear/")
+                url.set("https://tirbofish.github.io/dropbear/")
 
                 licenses {
                     license {
@@ -50,15 +50,15 @@ publishing {
                 }
                 developers {
                     developer {
-                        id.set("4tkbytes")
-                        name.set("4tkbytes")
-                        email.set("4tkbytes@pm.me")
+                        id.set("tirbofish")
+                        name.set("tirbofish")
+                        email.set("tirbofish@pm.me")
                     }
                 }
                 scm {
-                    connection.set("scm:git:git://github.com/4tkbytes/dropbear.git")
-                    developerConnection.set("scm:git:ssh://github.com/4tkbytes/dropbear.git")
-                    url.set("https://github.com/4tkbytes/dropbear")
+                    connection.set("scm:git:git://github.com/tirbofish/dropbear.git")
+                    developerConnection.set("scm:git:ssh://github.com/tirbofish/dropbear.git")
+                    url.set("https://github.com/tirbofish/dropbear")
                 }
             }
         }
diff --git a/magna-carta-plugin/src/main/kotlin/com/dropbear/magna_carta/DownloadMagnaCartaToolTask.kt b/magna-carta-plugin/src/main/kotlin/com/dropbear/magna_carta/DownloadMagnaCartaToolTask.kt
index 22b3021..f8a17b4 100644
--- a/magna-carta-plugin/src/main/kotlin/com/dropbear/magna_carta/DownloadMagnaCartaToolTask.kt
+++ b/magna-carta-plugin/src/main/kotlin/com/dropbear/magna_carta/DownloadMagnaCartaToolTask.kt
@@ -30,23 +30,23 @@ abstract class DownloadMagnaCartaToolTask: Download() {
         val (fileName, url) = when {
             os.isLinux && arch == "amd64" -> arrayOf(
                 "magna-carta-linux-x64",
-                "https://github.com/4tkbytes/dropbear/releases/download/${toolVersion.get()}/magna-carta-linux-x64",
+                "https://github.com/tirbofish/dropbear/releases/download/${toolVersion.get()}/magna-carta-linux-x64",
             )
             os.isMacOsX && arch == "aarch64" -> arrayOf(
                 "magna-carta-macos-arm64",
-                "https://github.com/4tkbytes/dropbear/releases/download/${toolVersion.get()}/magna-carta-macos-arm64",
+                "https://github.com/tirbofish/dropbear/releases/download/${toolVersion.get()}/magna-carta-macos-arm64",
             )
             os.isMacOsX && (arch == "x86_64" || arch == "amd64") -> arrayOf(
                 "magna-carta-macos-x64",
-                "https://github.com/4tkbytes/dropbear/releases/download/${toolVersion.get()}/magna-carta-macos-x64",
+                "https://github.com/tirbofish/dropbear/releases/download/${toolVersion.get()}/magna-carta-macos-x64",
             )
             os.isWindows && arch == "aarch64" -> arrayOf(
                 "magna-carta-windows-arm64.exe",
-                "https://github.com/4tkbytes/dropbear/releases/download/${toolVersion.get()}/magna-carta-windows-arm64.exe",
+                "https://github.com/tirbofish/dropbear/releases/download/${toolVersion.get()}/magna-carta-windows-arm64.exe",
             )
             os.isWindows && (arch == "x86_64" || arch == "amd64") -> arrayOf(
                 "magna-carta-windows-x64.exe",
-                "https://github.com/4tkbytes/dropbear/releases/download/${toolVersion.get()}/magna-carta-windows-x64.exe",
+                "https://github.com/tirbofish/dropbear/releases/download/${toolVersion.get()}/magna-carta-windows-x64.exe",
             )
             else -> throw GradleException("Unsupported OS/arch: $os / $arch")
         }
diff --git a/redback-runtime/Cargo.toml b/redback-runtime/Cargo.toml
index 444527e..997e0ae 100644
--- a/redback-runtime/Cargo.toml
+++ b/redback-runtime/Cargo.toml
@@ -11,6 +11,14 @@ dropbear-engine = { path = "../dropbear-engine" }
 eucalyptus-core = { path = "../eucalyptus-core" }
 
 anyhow.workspace = true
-log = "0.4.27"
+log.workspace = true
 app_dirs2.workspace = true
 tokio.workspace = true
+chrono.workspace = true
+env_logger.workspace = true
+colored.workspace = true
+parking_lot.workspace = true
+bincode.workspace = true
+hecs.workspace = true
+bytemuck.workspace = true
+log-once.workspace = true
\ No newline at end of file
diff --git a/redback-runtime/src/input.rs b/redback-runtime/src/input.rs
new file mode 100644
index 0000000..3c0f0cf
--- /dev/null
+++ b/redback-runtime/src/input.rs
@@ -0,0 +1,78 @@
+use crate::scene::RuntimeScene;
+use dropbear_engine::gilrs::{Button, GamepadId};
+use dropbear_engine::input::{Controller, Keyboard, Mouse};
+use dropbear_engine::winit::dpi::PhysicalPosition;
+use dropbear_engine::winit::event::MouseButton;
+use dropbear_engine::winit::event_loop::ActiveEventLoop;
+use dropbear_engine::winit::keyboard::KeyCode;
+
+impl Keyboard for RuntimeScene {
+    fn key_down(&mut self, key: KeyCode, _event_loop: &ActiveEventLoop) {
+        self.input_state.pressed_keys.insert(key);
+    }
+
+    fn key_up(&mut self, key: KeyCode, _event_loop: &ActiveEventLoop) {
+        self.input_state.pressed_keys.remove(&key);
+    }
+}
+
+impl Mouse for RuntimeScene {
+    fn mouse_move(&mut self, position: PhysicalPosition<f64>, delta: Option<(f64, f64)>) {
+        let delta = if delta.is_none() {
+            if let Some(last_pos) = self.input_state.last_mouse_pos {
+                Some((last_pos.0 - position.x, last_pos.1 - position.y))
+            } else {
+                None
+            }
+        } else {
+            delta
+        };
+
+        self.input_state.mouse_delta = delta;
+        self.input_state.mouse_pos = (position.x, position.y);
+        self.input_state.last_mouse_pos = Some(<(f64, f64)>::from(position));
+    }
+
+    fn mouse_down(&mut self, button: MouseButton) {
+        self.input_state.mouse_button.insert(button);
+    }
+
+    fn mouse_up(&mut self, button: MouseButton) {
+        self.input_state.mouse_button.remove(&button);
+    }
+}
+
+impl Controller for RuntimeScene {
+    fn button_down(&mut self, button: Button, id: GamepadId) {
+        self.input_state
+            .pressed_buttons
+            .entry(id)
+            .or_default()
+            .insert(button);
+    }
+
+    fn button_up(&mut self, button: Button, id: GamepadId) {
+        if let Some(buttons) = self.input_state.pressed_buttons.get_mut(&id) {
+            buttons.remove(&button);
+        }
+    }
+
+    fn left_stick_changed(&mut self, x: f32, y: f32, id: GamepadId) {
+        self.input_state.left_stick_position.insert(id, (x, y));
+    }
+
+    fn right_stick_changed(&mut self, x: f32, y: f32, id: GamepadId) {
+        self.input_state.right_stick_position.insert(id, (x, y));
+    }
+
+    fn on_connect(&mut self, id: GamepadId) {
+        self.input_state.connected_gamepads.insert(id);
+    }
+
+    fn on_disconnect(&mut self, id: GamepadId) {
+        self.input_state.connected_gamepads.remove(&id);
+        self.input_state.pressed_buttons.remove(&id);
+        self.input_state.left_stick_position.remove(&id);
+        self.input_state.right_stick_position.remove(&id);
+    }
+}
diff --git a/redback-runtime/src/main.rs b/redback-runtime/src/main.rs
index 5e753ff..6c8c9cd 100644
--- a/redback-runtime/src/main.rs
+++ b/redback-runtime/src/main.rs
@@ -1,4 +1,191 @@
+mod input;
+mod scene;
+
+use crate::scene::RuntimeScene;
+use app_dirs2::AppInfo;
+use dropbear_engine::future::FutureQueue;
+use dropbear_engine::{MutableWindowConfiguration, WindowConfiguration, WindowedModes};
+use eucalyptus_core::runtime::RuntimeProjectConfig;
+use eucalyptus_core::states::ConfigFile;
+use parking_lot::RwLock;
+use std::env::current_exe;
+use std::fs;
+use std::rc::Rc;
+use std::sync::Arc;
+
 #[tokio::main]
-async fn main() {
+async fn main() -> anyhow::Result<()> {
+    #[cfg(not(target_os = "android"))]
+    {
+        use chrono::offset::Local;
+        use colored::Colorize;
+        use env_logger::Builder;
+        use log::LevelFilter;
+        use parking_lot::Mutex;
+        use std::fs::OpenOptions;
+
+        let log_dir =
+            app_dirs2::app_root(app_dirs2::AppDataType::UserData, &eucalyptus_core::APP_INFO)
+                .expect("Failed to get app data directory")
+                .join("logs");
+        fs::create_dir_all(&log_dir).expect("Failed to create log dir");
+
+        let datetime_str = Local::now().format("%Y-%m-%d_%H-%M-%S");
+        let log_filename = format!("{}.{}.log", "redback-runtime", datetime_str);
+        let log_path = log_dir.join(log_filename);
+
+        let file = OpenOptions::new()
+            .create(true)
+            .append(true)
+            .open(&log_path)
+            .expect("Failed to open log file");
+        let file = Mutex::new(file);
+
+        Builder::new()
+            .format(move |buf, record| {
+                use std::io::Write;
+
+                let ts = Local::now().format("%Y-%m-%dT%H:%M:%S");
+
+                let colored_level = match record.level() {
+                    log::Level::Error => record.level().to_string().red().bold(),
+                    log::Level::Warn => record.level().to_string().yellow().bold(),
+                    log::Level::Info => record.level().to_string().green().bold(),
+                    log::Level::Debug => record.level().to_string().blue().bold(),
+                    log::Level::Trace => record.level().to_string().cyan().bold(),
+                };
+
+                let colored_timestamp = ts.to_string().bright_black();
+
+                let file_info = format!(
+                    "{}:{}",
+                    record.file().unwrap_or("unknown"),
+                    record.line().unwrap_or(0)
+                )
+                .bright_black();
+
+                let console_line = format!(
+                    "{} {} [{}] - {}\n",
+                    file_info,
+                    colored_timestamp,
+                    colored_level,
+                    record.args()
+                );
+
+                let file_line = format!(
+                    "{}:{} {} [{}] - {}\n",
+                    record.file().unwrap_or("unknown"),
+                    record.line().unwrap_or(0),
+                    ts,
+                    record.level(),
+                    record.args()
+                );
+
+                write!(buf, "{}", console_line)?;
+
+                let mut fh = file.lock();
+                let _ = fh.write_all(file_line.as_bytes());
+
+                Ok(())
+            })
+            .filter(Some("dropbear_engine"), LevelFilter::Trace)
+            .filter(
+                Some("redback-runtime".replace('-', "_").as_str()),
+                LevelFilter::Debug,
+            )
+            .filter(Some("eucalyptus_core"), LevelFilter::Debug)
+            .filter(Some("dropbear_traits"), LevelFilter::Debug)
+            .init();
+        log::info!("Initialised logger");
+    }
+
+    dropbear_engine::panic::set_hook();
+    log::debug!("Set panic hook");
+
+    let window_config_file = current_exe()?
+        .parent()
+        .ok_or(anyhow::anyhow!(
+            "Unable to get parent of current executable"
+        ))?
+        .join("config.eucfg");
+
+    let value = fs::read(&window_config_file);
+
+    let config = match value {
+        Ok(val) => {
+            let (config, _): (ConfigFile, usize) =
+                bincode::decode_from_slice(val.as_slice(), bincode::config::standard())?;
+            config
+        }
+        Err(e) => {
+            log::warn!("Unable to read config: {}", e);
+            log::warn!("Creating new config file to overwrite old one");
+            let window_configuration = MutableWindowConfiguration {
+                max_fps: u32::MAX,
+                windowed_mode: WindowedModes::Maximised,
+            };
+            let cfg = ConfigFile {
+                jvm_args: None,
+                window_configuration,
+            };
+            let vec = bincode::encode_to_vec(cfg.clone(), bincode::config::standard())?;
+            if let Err(e) = fs::write(&window_config_file, vec) {
+                log::warn!("Unable to write, still running game: {}", e);
+            }
+            cfg
+        }
+    };
+
+    let scene_config = fs::read(
+        current_exe()?
+            .parent()
+            .ok_or(anyhow::anyhow!(
+                "Unable to locate parent folder for current executable"
+            ))?
+            .join("data.eupak"),
+    )?;
+    let (scene_config, _): (RuntimeProjectConfig, usize) =
+        bincode::decode_from_slice(scene_config.as_slice(), bincode::config::standard())?;
+
+    let runtime_scene = Rc::new(RwLock::new(RuntimeScene::new(scene_config.clone())?));
+    let future_queue = Arc::new(FutureQueue::new());
+
+    let name = current_exe()?
+        .file_stem()
+        .ok_or(anyhow::anyhow!("Unable to locate file name of current exe"))?
+        .to_str()
+        .ok_or(anyhow::anyhow!("Unable to convert file name to string"))?
+        .to_string();
+
+    let authors = scene_config.authors.developer.clone();
+    let project_name = scene_config.project_name.clone();
+
+    let win_cfg = WindowConfiguration {
+        title: name,
+        window_config: config.window_configuration,
+        app_info: AppInfo {
+            name: Box::leak(project_name.into_boxed_str()),
+            author: Box::leak(authors.into_boxed_str()),
+        },
+    };
+
+    dropbear_engine::run_app!(
+        win_cfg,
+        Some(future_queue),
+        |mut scene_mgr, mut input_mgr| {
+            dropbear_engine::scene::add_scene_with_input(
+                &mut scene_mgr,
+                &mut input_mgr,
+                runtime_scene,
+                "runtime_scene",
+            );
+
+            scene_mgr.switch("runtime_scene");
+
+            (scene_mgr, input_mgr)
+        }
+    )
+    .await?;
 
+    Ok(())
 }
diff --git a/redback-runtime/src/scene.rs b/redback-runtime/src/scene.rs
new file mode 100644
index 0000000..7f3b95f
--- /dev/null
+++ b/redback-runtime/src/scene.rs
@@ -0,0 +1,645 @@
+use std::collections::HashMap;
+use std::env;
+use std::path::PathBuf;
+use std::sync::Arc;
+use std::time::Duration;
+
+use anyhow::Context;
+use dropbear_engine::camera::Camera;
+use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform};
+use dropbear_engine::future::FutureHandle;
+use dropbear_engine::graphics::{InstanceRaw, RenderContext};
+use dropbear_engine::lighting::{Light, LightComponent, LightManager};
+use dropbear_engine::model::{DrawLight, DrawModel, MODEL_CACHE, ModelId};
+use dropbear_engine::scene::{Scene, SceneCommand};
+use dropbear_engine::shader::{self, Shader};
+use dropbear_engine::wgpu::util::DeviceExt;
+use dropbear_engine::wgpu::{self, Color, RenderPipeline};
+use dropbear_engine::winit::event_loop::ActiveEventLoop;
+use dropbear_engine::winit::window::Window;
+use eucalyptus_core::camera::CameraComponent;
+use eucalyptus_core::hierarchy::EntityTransformExt;
+use eucalyptus_core::input::InputState;
+use eucalyptus_core::ptr::{GraphicsPtr, InputStatePtr, WorldPtr};
+use eucalyptus_core::runtime::RuntimeProjectConfig;
+use eucalyptus_core::scene::SceneConfig;
+use eucalyptus_core::scripting::{ScriptManager, ScriptTarget};
+use eucalyptus_core::states::Script;
+use eucalyptus_core::traits::registry::ComponentRegistry;
+use eucalyptus_core::window::{self, GRAPHICS_COMMAND};
+use hecs::{Entity, World};
+use parking_lot::Mutex;
+use tokio::sync::oneshot;
+use tokio::sync::oneshot::error::TryRecvError;
+
+/// The scene that the redback-runtime uses.
+pub(crate) struct RuntimeScene {
+    #[allow(dead_code)]
+    project_config: RuntimeProjectConfig,
+    scenes: HashMap<String, SceneConfig>,
+    initial_scene: String,
+
+    pub world: Box<World>,
+    pub input_state: Box<InputState>,
+    pub active_camera: Arc<Mutex<Option<Entity>>>,
+    render_pipeline: Option<RenderPipeline>,
+    light_manager: LightManager,
+    component_registry: Arc<ComponentRegistry>,
+    script_manager: ScriptManager,
+    script_target: Option<ScriptTarget>,
+    scripts_ready: bool,
+    scene_command: SceneCommand,
+
+    current_scene: Option<String>,
+    pending_scene: Option<PendingSceneLoad>,
+    pub window: Option<Arc<Window>>,
+}
+
+struct PendingSceneLoad {
+    name: String,
+    receiver: oneshot::Receiver<anyhow::Result<LoadedScene>>,
+    handle: FutureHandle,
+}
+
+struct LoadedScene {
+    world: World,
+    active_camera: Entity,
+}
+
+impl RuntimeScene {
+    /// Creates a new instance of [`RuntimeScene`]
+    pub fn new(project_config: RuntimeProjectConfig) -> anyhow::Result<Self> {
+        // checks for any deadlocks in another thread.
+        std::thread::spawn(move || {
+            loop {
+                std::thread::sleep(Duration::from_secs(1));
+                let deadlocks = parking_lot::deadlock::check_deadlock();
+                if deadlocks.is_empty() {
+                    continue;
+                }
+
+                for (i, threads) in deadlocks.iter().enumerate() {
+                    log::error!("Deadlock #{}", i);
+                    for t in threads {
+                        log::error!("Thread Id {:#?}", t.thread_id());
+                        log::error!("{:#?}", t.backtrace());
+                    }
+                }
+                panic!(
+                    "Fatal: {} deadlocks detected, unable to continue on normal process",
+                    deadlocks.len()
+                );
+            }
+        });
+
+        let initial_scene = project_config.initial_scene.clone();
+
+        let scenes = project_config
+            .scenes
+            .iter()
+            .map(|scene| (scene.scene_name.clone(), scene.clone()))
+            .collect::<HashMap<_, _>>();
+
+        let result = Self {
+            project_config: project_config.clone(),
+            scenes,
+            initial_scene,
+            world: Box::new(World::new()),
+            input_state: Box::new(InputState::new()),
+            active_camera: Arc::new(Mutex::new(None)),
+            render_pipeline: None,
+            light_manager: LightManager::new(),
+            current_scene: None,
+            component_registry: Arc::new(ComponentRegistry::new()),
+            script_manager: ScriptManager::new()?,
+            script_target: None,
+            scripts_ready: false,
+            scene_command: Default::default(),
+            pending_scene: None,
+            window: None,
+        };
+
+        Ok(result)
+    }
+
+    fn start_scene_load(
+        &mut self,
+        scene_name: &str,
+        graphics: &mut RenderContext,
+    ) -> anyhow::Result<()> {
+        if self.scenes.is_empty() {
+            anyhow::bail!("No scenes available in this runtime build");
+        }
+
+        if let Some(pending) = self.pending_scene.take() {
+            graphics.shared.future_queue.cancel(&pending.handle);
+        }
+
+        self.world.clear();
+        self.render_pipeline = None;
+        self.light_manager = LightManager::new();
+        self.scripts_ready = false;
+        self.script_target = None;
+        *self.active_camera.lock() = None;
+
+        let scene = self
+            .scenes
+            .get(scene_name)
+            .cloned()
+            .ok_or_else(|| anyhow::anyhow!("Scene '{}' does not exist", scene_name))?;
+
+        let graphics_shared = graphics.shared.clone();
+        let component_registry = self.component_registry.clone();
+        let (tx, rx) = oneshot::channel();
+
+        let handle = graphics.shared.future_queue.push(async move {
+            let mut temp_world = World::new();
+            let result = scene
+                .load_into_world(
+                    &mut temp_world,
+                    graphics_shared,
+                    Some(component_registry.as_ref()),
+                    None,
+                )
+                .await
+                .map(|active_camera| LoadedScene {
+                    world: temp_world,
+                    active_camera,
+                });
+
+            if tx.send(result).is_err() {
+                log::warn!("Scene load result receiver dropped before completion");
+            }
+        });
+
+        self.pending_scene = Some(PendingSceneLoad {
+            name: scene_name.to_string(),
+            receiver: rx,
+            handle,
+        });
+
+        Ok(())
+    }
+
+    fn poll_pending_scene(&mut self, graphics: &mut RenderContext) {
+        let Some(mut pending) = self.pending_scene.take() else {
+            return;
+        };
+
+        match pending.receiver.try_recv() {
+            Ok(result) => {
+                let _ = graphics
+                    .shared
+                    .future_queue
+                    .exchange_owned_as::<()>(&pending.handle);
+
+                match result {
+                    Ok(loaded) => {
+                        self.on_scene_loaded(loaded, pending.name, graphics);
+                    }
+                    Err(err) => {
+                        log::error!("Failed to load scene: {err:?}");
+                        self.scene_command = SceneCommand::Quit;
+                    }
+                }
+            }
+            Err(TryRecvError::Empty) => {
+                self.pending_scene = Some(pending);
+            }
+            Err(TryRecvError::Closed) => {
+                log::error!("Scene load task for '{}' closed unexpectedly", pending.name);
+                let _ = graphics
+                    .shared
+                    .future_queue
+                    .exchange_owned_as::<()>(&pending.handle);
+                self.scene_command = SceneCommand::Quit;
+            }
+        }
+    }
+
+    fn on_scene_loaded(
+        &mut self,
+        loaded: LoadedScene,
+        scene_name: String,
+        graphics: &mut RenderContext,
+    ) {
+        self.world = Box::new(loaded.world);
+        *self.active_camera.lock() = Some(loaded.active_camera);
+        self.current_scene = Some(scene_name);
+        self.pending_scene = None;
+        self.window = Some(graphics.shared.window.clone());
+        self.input_state.window = Some(graphics.shared.window.clone());
+
+        if let Err(err) = self.prepare_render_resources(graphics) {
+            log::error!("Failed to prepare render pipeline: {err:?}");
+        }
+
+        if let Err(err) = self.initialise_scripts() {
+            log::warn!("Unable to initialise scripts: {err:?}");
+        }
+    }
+
+    fn prepare_render_resources(&mut self, graphics: &mut RenderContext) -> anyhow::Result<()> {
+        self.light_manager
+            .create_light_array_resources(graphics.shared.clone());
+
+        let active_camera = self
+            .active_camera
+            .lock()
+            .context("Active camera was not set")?;
+
+        let mut query = self
+            .world
+            .query_one::<&Camera>(active_camera)
+            .map_err(|_| anyhow::anyhow!("Unable to query active camera"))?;
+
+        let camera = query
+            .get()
+            .ok_or_else(|| anyhow::anyhow!("Camera component missing on active entity"))?;
+
+        let shader = Shader::new(
+            graphics.shared.clone(),
+            shader::shader_wesl::SHADER_SHADER,
+            Some("runtime_default"),
+        );
+
+        let pipeline = graphics.create_render_pipline(
+            &shader,
+            vec![
+                graphics.shared.texture_bind_layout.as_ref(),
+                camera.layout(),
+                self.light_manager.layout(),
+            ],
+            Some("Runtime Scene Pipeline"),
+        );
+
+        self.render_pipeline = Some(pipeline);
+
+        self.light_manager.create_render_pipeline(
+            graphics.shared.clone(),
+            shader::shader_wesl::LIGHT_SHADER,
+            camera,
+            Some("Runtime Light Pipeline"),
+        );
+
+        Ok(())
+    }
+
+    fn initialise_scripts(&mut self) -> anyhow::Result<()> {
+        let mut tag_database: HashMap<String, Vec<Entity>> = HashMap::new();
+
+        for (entity, script) in self.world.query::<&Script>().iter() {
+            for tag in &script.tags {
+                tag_database.entry(tag.clone()).or_default().push(entity);
+            }
+        }
+
+        if tag_database.is_empty() {
+            self.scripts_ready = false;
+            return Ok(());
+        }
+
+        let Some(target) = self.detect_script_target()? else {
+            log::warn!(
+                "Script components detected but no script artifact found next to the runtime"
+            );
+            self.scripts_ready = false;
+            return Ok(());
+        };
+
+        self.script_manager
+            .init_script(tag_database, target.clone())?;
+
+        let world_ptr = self.world.as_mut() as WorldPtr;
+        let input_ptr = self.input_state.as_mut() as InputStatePtr;
+        let graphics_ptr = GRAPHICS_COMMAND.0.as_ref() as GraphicsPtr;
+
+        self.script_manager
+            .load_script(world_ptr, input_ptr, graphics_ptr)?;
+
+        self.script_target = Some(target);
+        self.scripts_ready = true;
+
+        Ok(())
+    }
+
+    fn detect_script_target(&self) -> anyhow::Result<Option<ScriptTarget>> {
+        if let Ok(path) = env::var("DROPBEAR_SCRIPT_PATH") {
+            let path = PathBuf::from(path);
+            if Self::is_native_library(&path) {
+                return Ok(Some(ScriptTarget::Native { library_path: path }));
+            }
+            if Self::is_jvm_artifact(&path) {
+                return Ok(Some(ScriptTarget::JVM { library_path: path }));
+            }
+        }
+
+        let exe_dir = env::current_exe()
+            .context("Unable to locate runtime executable path")?
+            .parent()
+            .ok_or_else(|| anyhow::anyhow!("Executable has no parent directory"))?
+            .to_path_buf();
+
+        let search_dirs = [exe_dir.join("libs"), exe_dir.clone()];
+        let mut native_candidates = Vec::new();
+        let mut jar_candidates = Vec::new();
+
+        for dir in search_dirs {
+            if !dir.exists() {
+                continue;
+            }
+            if let Ok(entries) = std::fs::read_dir(&dir) {
+                for entry in entries.flatten() {
+                    let path = entry.path();
+                    if !path.is_file() {
+                        continue;
+                    }
+                    if Self::is_native_library(&path) {
+                        native_candidates.push(path);
+                    } else if Self::is_jvm_artifact(&path) {
+                        jar_candidates.push(path);
+                    }
+                }
+            }
+        }
+
+        if let Some(native) = Self::pick_preferred(native_candidates) {
+            return Ok(Some(ScriptTarget::Native {
+                library_path: native,
+            }));
+        }
+
+        if let Some(jar) = Self::pick_preferred(jar_candidates) {
+            return Ok(Some(ScriptTarget::JVM { library_path: jar }));
+        }
+
+        Ok(None)
+    }
+
+    fn is_native_library(path: &PathBuf) -> bool {
+        let ext = path
+            .extension()
+            .and_then(|ext| ext.to_str())
+            .unwrap_or("")
+            .to_ascii_lowercase();
+
+        match env::consts::OS {
+            "windows" => ext == "dll",
+            "macos" => ext == "dylib",
+            _ => ext == "so",
+        }
+    }
+
+    fn is_jvm_artifact(path: &PathBuf) -> bool {
+        path.extension()
+            .and_then(|ext| ext.to_str())
+            .map(|ext| ext.eq_ignore_ascii_case("jar"))
+            .unwrap_or(false)
+    }
+
+    fn pick_preferred(mut candidates: Vec<PathBuf>) -> Option<PathBuf> {
+        if candidates.is_empty() {
+            return None;
+        }
+
+        candidates.sort_by(|a, b| {
+            let priority_a = !Self::is_preferred_name(a);
+            let priority_b = !Self::is_preferred_name(b);
+            priority_a.cmp(&priority_b).then_with(|| {
+                b.metadata()
+                    .and_then(|m| m.modified())
+                    .ok()
+                    .cmp(&a.metadata().and_then(|m| m.modified()).ok())
+            })
+        });
+
+        candidates.into_iter().next()
+    }
+
+    fn is_preferred_name(path: &PathBuf) -> bool {
+        path.file_stem()
+            .and_then(|stem| stem.to_str())
+            .map(|name| {
+                let lower = name.to_ascii_lowercase();
+                lower.contains("dropbear") || lower.contains("native")
+            })
+            .unwrap_or(false)
+    }
+
+    fn update_cameras(&mut self, graphics: &mut RenderContext) {
+        let (width, height) = graphics.frame.screen_size;
+        if height <= 0.0 {
+            return;
+        }
+        let aspect = f64::from(width / height);
+
+        for (_, (camera, component)) in self
+            .world
+            .query::<(&mut Camera, &mut CameraComponent)>()
+            .iter()
+        {
+            camera.aspect = aspect;
+            component.update(camera);
+            camera.update(graphics.shared.clone());
+        }
+    }
+
+    fn update_render_transforms(&mut self) {
+        for (_, (renderer, transform)) in
+            self.world.query::<(&mut MeshRenderer, &Transform)>().iter()
+        {
+            renderer.update(transform);
+        }
+
+        let mut propagated = Vec::new();
+        for (entity, entity_transform) in self.world.query::<&EntityTransform>().iter() {
+            let final_transform = entity_transform.propagate(&self.world, entity);
+            propagated.push((entity, final_transform));
+        }
+
+        for (entity, final_transform) in propagated {
+            if let Ok(mut query) = self.world.query_one::<&mut MeshRenderer>(entity) {
+                if let Some(renderer) = query.get() {
+                    renderer.update(&final_transform);
+                }
+            }
+
+            if let Ok(mut query) = self
+                .world
+                .query_one::<(&mut LightComponent, &mut Light)>(entity)
+            {
+                if let Some((component, light)) = query.get() {
+                    light.update(component, &final_transform);
+                }
+            }
+        }
+    }
+
+    fn update_lights(&mut self, graphics: &mut RenderContext) {
+        self.light_manager
+            .update(graphics.shared.clone(), &self.world);
+    }
+}
+
+impl Scene for RuntimeScene {
+    fn load(&mut self, graphics: &mut RenderContext) {
+        self.window = Some(graphics.shared.window.clone());
+        self.input_state.window = Some(graphics.shared.window.clone());
+
+        let target_scene = self
+            .current_scene
+            .clone()
+            .or_else(|| Some(self.initial_scene.clone()))
+            .or_else(|| self.scenes.keys().next().cloned());
+
+        if let Some(scene_name) = target_scene {
+            if let Err(err) = self.start_scene_load(&scene_name, graphics) {
+                log::error!("Failed to start scene load: {err:?}");
+                self.scene_command = SceneCommand::Quit;
+            }
+        } else {
+            log::error!("Unable to determine a scene to load");
+            self.scene_command = SceneCommand::Quit;
+        }
+    }
+
+    fn update(&mut self, dt: f32, graphics: &mut RenderContext) {
+        self.window = Some(graphics.shared.window.clone());
+        self.input_state.window = Some(graphics.shared.window.clone());
+
+        self.poll_pending_scene(graphics);
+
+        if self.render_pipeline.is_none() {
+            return;
+        }
+
+        window::poll(graphics.shared.window.clone());
+
+        if self.scripts_ready {
+            let world_ptr = self.world.as_mut() as WorldPtr;
+            if let Err(err) = unsafe {
+                self.script_manager
+                    .update_script(world_ptr, &self.input_state, dt)
+            } {
+                log::error!("Script runtime error: {err:?}");
+            }
+        }
+
+        self.update_cameras(graphics);
+        self.update_render_transforms();
+        self.update_lights(graphics);
+    }
+
+    fn render(&mut self, graphics: &mut RenderContext) {
+        let Some(pipeline) = &self.render_pipeline else {
+            return;
+        };
+
+        let color = Color {
+            r: 100.0 / 255.0,
+            g: 149.0 / 255.0,
+            b: 237.0 / 255.0,
+            a: 1.0,
+        };
+
+        let Some(active_camera) = self.active_camera.lock().clone() else {
+            log::warn!("No active camera available for rendering");
+            return;
+        };
+
+        let camera = match self
+            .world
+            .query_one::<&Camera>(active_camera)
+            .and_then(|mut q| Ok(q.get().cloned()))
+        {
+            Ok(Some(camera)) => camera,
+            _ => {
+                log::warn!("Unable to fetch camera component for active camera entity");
+                return;
+            }
+        };
+
+        self.window = Some(graphics.shared.window.clone());
+
+        let lights = self.world.query::<(&Light, &LightComponent)>().iter().map(|(_, (light, component))| (light.clone(), component.clone())).collect::<Vec<_>>();
+
+        {
+            let mut render_pass = graphics.clear_colour(color);
+            if let Some(light_pipeline) = &self.light_manager.pipeline {
+                render_pass.set_pipeline(light_pipeline);
+                for (light, component) in &lights
+                {
+                    if !component.enabled {
+                        continue;
+                    }
+                    if let Some(instance_buffer) = &light.instance_buffer {
+                        render_pass.set_vertex_buffer(1, instance_buffer.slice(..));
+                    }
+                    render_pass.draw_light_model(
+                        light.model(),
+                        camera.bind_group(),
+                        light.bind_group(),
+                    );
+                }
+            }
+        }
+
+        let mut model_batches: HashMap<ModelId, Vec<InstanceRaw>> = HashMap::new();
+
+        for (_, renderer) in self.world.query::<&MeshRenderer>().iter() {
+            model_batches
+                .entry(renderer.model_id())
+                .or_default()
+                .push(renderer.instance.to_raw());
+        }
+
+        for (model_id, instances) in model_batches {
+            let model = {
+                let cache = MODEL_CACHE.lock();
+                cache.values().find(|model| model.id == model_id).cloned()
+            };
+
+            let Some(model) = model else {
+                log::warn!("Unable to locate model with id {:?}", model_id);
+                continue;
+            };
+
+            let instance_buffer =
+                graphics
+                    .shared
+                    .device
+                    .create_buffer_init(&wgpu::util::BufferInitDescriptor {
+                        label: Some("Runtime Instance Buffer"),
+                        contents: bytemuck::cast_slice(&instances),
+                        usage: wgpu::BufferUsages::VERTEX,
+                    });
+
+            let mut render_pass = graphics.continue_pass();
+            render_pass.set_pipeline(pipeline);
+            render_pass.set_vertex_buffer(1, instance_buffer.slice(..));
+            render_pass.draw_model_instanced(
+                &model,
+                0..instances.len() as u32,
+                camera.bind_group(),
+                self.light_manager.bind_group(),
+            );
+        }
+    }
+
+    fn exit(&mut self, event_loop: &ActiveEventLoop) {
+        let _ = event_loop;
+        self.scene_command = SceneCommand::None;
+        if let Some(window) = &self.window {
+            window.set_cursor_visible(true);
+        }
+        self.world.clear();
+        self.render_pipeline = None;
+        self.scripts_ready = false;
+        self.script_target = None;
+        self.pending_scene = None;
+    }
+
+    fn run_command(&mut self) -> SceneCommand {
+        std::mem::replace(&mut self.scene_command, SceneCommand::None)
+    }
+}