kitgit

tirbofish/dropbear · commit

6aabee48694ef7e3d8c3c52c97e5d7e94412b208

Merge pull request #43 from 4tkbytes/crate-split workspace: splitting the eucalyptus crate into two crates

Unverified

tk <4tkbytes@pm.me> · 2025-09-09 06:16

view full diff

diff --git a/Cargo.toml b/Cargo.toml
index 3d89336..7cc34e8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,7 +6,7 @@ package.repository = "https://github.com/4tkbytes/dropbear-engine"
 package.readme = "README.md"
 
 resolver = "3"
-members = ["dropbear-engine", "eucalyptus", "redback-runtime"]
+members = ["dropbear-engine", "eucalyptus-core", "eucalyptus-editor", "redback-runtime"]
 
 [workspace.dependencies]
 anyhow = { version = "1.0", features = ["backtrace"] }
@@ -28,7 +28,7 @@ egui_extras = { version = "0.32", features = ["all_loaders"] }
 env_logger = "0.11"
 futures = "0.3"
 gilrs = "0.11"
-git2 = "0.20"
+git2 = { version = "0.20", features = ["vendored-openssl"] }
 glam = { version = "0.30", features = ["serde"] }
 hecs = { version = "0.10", features = ["serde"] }
 lazy_static = "1.5"
@@ -41,11 +41,10 @@ parking_lot = {version = "0.12", features = ["deadlock_detection"] }
 pollster = "0.4"
 rfd = "0.15.4"
 ron = "0.11"
-russimp-ng = { version = "3.2" }
+russimp-ng = { version = "3.2", features = ["static-link"] }
 rustyscript = { version = "0.12" }
 serde = { version = "1.0.219", features = ["derive"] }
 spin_sleep = "1.3"
-tokio = { version = "1", features = ["full"] }
 transform-gizmo-egui = { git = "https://github.com/4tkbytes/transform-gizmo"}
 wgpu = "26"
 winit = { version = "0.30", features = [] }
diff --git a/eucalyptus-core/Cargo.toml b/eucalyptus-core/Cargo.toml
new file mode 100644
index 0000000..4c24389
--- /dev/null
+++ b/eucalyptus-core/Cargo.toml
@@ -0,0 +1,40 @@
+[package]
+name = "eucalyptus-core"
+version.workspace = true
+edition.workspace = true
+license-file.workspace = true
+repository.workspace = true
+readme.workspace = true
+
+[dependencies]
+anyhow.workspace = true
+app_dirs2.workspace = true
+bincode.workspace = true
+chrono.workspace = true
+dropbear-engine.workspace = true
+egui-toast-fork.workspace = true
+egui.workspace = true
+egui_dock-fork.workspace = true
+glam.workspace = true
+hecs.workspace = true
+log.workspace = true
+log-once.workspace = true
+once_cell.workspace = true
+parking_lot.workspace = true
+ron.workspace = true
+serde.workspace = true
+winit.workspace = true
+zip.workspace = true
+rustyscript.workspace = true
+
+[features]
+editor = []
+
+[target.'cfg(not(target_os = "android"))'.dependencies]
+rfd.workspace = true
+
+[build-dependencies]
+anyhow = "1.0"
+app_dirs2 = "2.5"
+reqwest = { version = "0.12", features = ["blocking"] }
+zip = "4.3"
diff --git a/eucalyptus-core/build.rs b/eucalyptus-core/build.rs
new file mode 100644
index 0000000..8966f89
--- /dev/null
+++ b/eucalyptus-core/build.rs
@@ -0,0 +1,67 @@
+use std::fs::{self, File};
+use std::io::Cursor;
+
+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 response = reqwest::blocking::get(repo_zip_url)
+        .map_err(|e| anyhow::anyhow!("Failed to download repo zip: {}", e))?
+        .bytes()
+        .map_err(|e| anyhow::anyhow!("Failed to read zip bytes: {}", e))?;
+
+    let reader = Cursor::new(response);
+    let mut zip = zip::ZipArchive::new(reader)
+        .map_err(|e| anyhow::anyhow!("Failed to read zip archive: {}", e))?;
+
+    let app_info = app_dirs2::AppInfo {
+        name: "Eucalyptus",
+        author: "4tkbytes",
+    };
+    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))?;
+
+    fs::create_dir_all(&app_data_dir)
+        .map_err(|e| anyhow::anyhow!("Failed to create app data directory: {}", e))?;
+
+    let resource_prefix = "dropbear-main/resources/";
+    let mut found_resource = false;
+    for i in 0..zip.len() {
+        let mut file = zip.by_index(i).unwrap();
+        let name = file.name();
+
+        if name.starts_with(resource_prefix) && !name.ends_with('/') {
+            found_resource = true;
+            let rel_path = &name[resource_prefix.len()..];
+            let rel_path = rel_path.strip_prefix('/').unwrap_or(rel_path);
+            let dest_path = app_data_dir.join(rel_path);
+
+            if let Some(parent) = dest_path.parent() {
+                fs::create_dir_all(parent)
+                    .map_err(|e| anyhow::anyhow!("Failed to create parent directory: {}", e))?;
+            }
+
+            println!("Copying {} to {:?}", name, dest_path);
+
+            let mut outfile = File::create(&dest_path)
+                .map_err(|e| anyhow::anyhow!("Failed to create file: {}", e))?;
+            std::io::copy(&mut file, &mut outfile)
+                .map_err(|e| anyhow::anyhow!("Failed to copy file: {}", e))?;
+        }
+    }
+
+    if !found_resource {
+        return Err(anyhow::anyhow!(
+            "No resources folder found in the github repository [4tkbytes/dropbear] :("
+        ));
+    }
+
+    // fuck you windows :(
+    #[cfg(target_os = "windows")]
+    {
+        println!("cargo:rustc-link-arg=/FORCE:MULTIPLE");
+        println!("cargo:rustc-link-arg=/NODEFAULTLIB:libcmt.lib");
+    }
+
+    println!("cargo:rerun-if-changed=build.rs");
+    Ok(())
+}
diff --git a/eucalyptus-core/src/camera.rs b/eucalyptus-core/src/camera.rs
new file mode 100644
index 0000000..e5f6002
--- /dev/null
+++ b/eucalyptus-core/src/camera.rs
@@ -0,0 +1,126 @@
+use std::collections::HashSet;
+use glam::DVec3;
+use serde::{Deserialize, Serialize};
+use dropbear_engine::camera::Camera;
+use winit::keyboard::KeyCode;
+
+#[derive(Debug, Clone)]
+pub struct CameraComponent {
+    pub speed: f64,
+    pub sensitivity: f64,
+    pub fov_y: f64,
+    pub camera_type: CameraType
+}
+
+impl CameraComponent {
+    pub fn new() -> Self {
+        Self {
+            speed: 5.0,
+            sensitivity: 0.002,
+            fov_y: 60.0,
+            camera_type: CameraType::Normal,
+        }
+    }
+
+    pub fn update(&mut self, camera: &mut Camera) {
+        camera.speed = self.speed;
+        camera.sensitivity = self.sensitivity;
+        camera.fov_y = self.fov_y;
+    }
+
+    // setting camera offset is just adding the CameraFollowTarget struct
+    // to the ecs system
+}
+
+pub struct PlayerCamera;
+
+impl PlayerCamera {
+    pub fn new() -> CameraComponent {
+        CameraComponent {
+            camera_type: CameraType::Player,
+            ..CameraComponent::new()
+        }
+    }
+
+    pub fn handle_keyboard_input(
+        camera: &mut Camera,
+        pressed_keys: &HashSet<KeyCode>
+    ) {
+        for key in pressed_keys {
+            match key {
+                KeyCode::KeyW => camera.move_forwards(),
+                KeyCode::KeyA => camera.move_left(),
+                KeyCode::KeyD => camera.move_right(),
+                KeyCode::KeyS => camera.move_back(),
+                KeyCode::ShiftLeft => camera.move_down(),
+                KeyCode::Space => camera.move_up(),
+                _ => {}
+            }
+        }
+    }
+
+    pub fn handle_mouse_input(camera: &mut Camera, component: &CameraComponent, mouse_delta: Option<(f64, f64)>) {
+        if let Some((dx, dy)) = mouse_delta {
+            camera.track_mouse_delta(dx * component.sensitivity, dy * component.sensitivity);
+        }
+    }
+}
+
+pub struct DebugCamera;
+
+impl DebugCamera {
+    pub fn new() -> CameraComponent {
+        CameraComponent {
+            camera_type: CameraType::Debug,
+            ..CameraComponent::new()
+        }
+    }
+
+    pub fn handle_keyboard_input(
+        camera: &mut Camera,
+        pressed_keys: &HashSet<KeyCode>
+    ) {
+        for key in pressed_keys {
+            match key {
+                KeyCode::KeyW => camera.move_forwards(),
+                KeyCode::KeyA => camera.move_left(),
+                KeyCode::KeyD => camera.move_right(),
+                KeyCode::KeyS => camera.move_back(),
+                KeyCode::ShiftLeft => camera.move_down(),
+                KeyCode::Space => camera.move_up(),
+                _ => {}
+            }
+        }
+    }
+
+    pub fn handle_mouse_input(camera: &mut Camera, component: &CameraComponent, mouse_delta: Option<(f64, f64)>) {
+        if let Some((dx, dy)) = mouse_delta {
+            camera.track_mouse_delta(dx * component.sensitivity, dy * component.sensitivity);
+        }
+    }
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct CameraFollowTarget {
+    pub follow_target: String,
+    pub offset: DVec3,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
+pub enum CameraType {
+    Normal,
+    Debug,
+    Player,
+}
+
+impl Default for CameraType {
+    fn default() -> Self {
+        Self::Normal
+    }
+}
+
+#[derive(Debug, Clone)]
+pub enum CameraAction {
+    SetPlayerTarget { entity: hecs::Entity, offset: DVec3 },
+    ClearPlayerTarget,
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/lib.rs b/eucalyptus-core/src/lib.rs
new file mode 100644
index 0000000..0ac84c1
--- /dev/null
+++ b/eucalyptus-core/src/lib.rs
@@ -0,0 +1,5 @@
+pub mod logging;
+pub mod camera;
+pub mod states;
+pub mod utils;
+pub mod scripting;
\ No newline at end of file
diff --git a/eucalyptus-core/src/logging.rs b/eucalyptus-core/src/logging.rs
new file mode 100644
index 0000000..6c07d4c
--- /dev/null
+++ b/eucalyptus-core/src/logging.rs
@@ -0,0 +1,242 @@
+//! This crate will allow for logging to specific locations. Essentially, just removing boilerplate
+//!
+//! # Supported logging locations:
+//! - Toasts (egui)
+//! - Console
+//! - File (to be implemented)
+
+#[cfg(feature = "editor")]
+use egui::Context;
+
+#[cfg(feature = "editor")]
+use egui_toast_fork::Toasts;
+
+#[cfg(feature = "editor")]
+use once_cell::sync::Lazy;
+#[cfg(feature = "editor")]
+use parking_lot::Mutex;
+
+#[cfg(feature = "editor")]
+pub static GLOBAL_TOASTS: Lazy<Mutex<Toasts>> = Lazy::new(|| {
+    Mutex::new(
+        Toasts::new()
+            .anchor(egui::Align2::RIGHT_BOTTOM, (-10.0, -10.0))
+            .direction(egui::Direction::BottomUp),
+    )
+});
+
+/// Renders the toasts. Requires an egui context.
+///
+/// Useful when paired with a function that contains [`crate`]
+#[cfg(feature = "editor")]
+pub fn render(context: &Context) {
+    let mut toasts = GLOBAL_TOASTS.lock();
+    toasts.show(context);
+}
+
+/// Fatal log macro
+///
+/// This is useful for when there is a fatal error like a missing file cannot be found.
+///
+/// This macro creates a toast under the [`egui_toast_fork::ToastKind::Error`] and logs
+/// with [`log::error!`]
+#[macro_export]
+macro_rules! fatal {
+    ($($arg:tt)*) => {{
+        let _msg = format!($($arg)*);
+        log::error!("{}", _msg);
+
+        #[cfg(feature = "editor")]
+        {
+            use egui_toast_fork::{Toast, ToastKind};
+            use eucalyptus_core::logging::GLOBAL_TOASTS;
+            let mut toasts = GLOBAL_TOASTS.lock();
+            toasts.add(Toast {
+                text: _msg.into(),
+                kind: ToastKind::Error,
+                options: egui_toast_fork::ToastOptions::default()
+                    .duration_in_seconds(3.0)
+                    .show_progress(true),
+                style: egui_toast_fork::ToastStyle::default(),
+            });
+        }
+    }};
+}
+
+/// Success log macro
+///
+/// This is useful for when loading a save is successful.
+///
+/// This macro creates a toast under the [`egui_toast_fork::ToastKind::Success`] and logs
+/// with [`log::info!`]
+#[macro_export]
+macro_rules! success {
+    ($($arg:tt)*) => {{
+        let _msg = format!($($arg)*);
+        log::debug!("{}", _msg);
+
+        #[cfg(feature = "editor")]
+        {
+            use egui_toast_fork::{Toast, ToastKind};
+            use eucalyptus_core::logging::GLOBAL_TOASTS;
+            let mut toasts = GLOBAL_TOASTS.lock();
+            toasts.add(Toast {
+                    text: _msg.into(),
+                    kind: ToastKind::Success,
+                    options: egui_toast_fork::ToastOptions::default()
+                        .duration_in_seconds(3.0)
+                        .show_progress(true),
+                    style: egui_toast_fork::ToastStyle::default(),
+                });
+            };
+        }
+    };
+}
+
+/// Warn log macro
+///
+/// This is useful for when there is a non-fatal error like unable to copy.
+///
+/// This macro creates a toast under the [`egui_toast_fork::ToastKind::Warning`] and logs
+/// with [`log::warn!`]
+#[macro_export]
+macro_rules! warn {
+    ($($arg:tt)*) => {{
+        let _msg = format!($($arg)*);
+        log::warn!("{}", _msg);
+
+        #[cfg(feature = "editor")]
+        {
+            use egui_toast_fork::{Toast, ToastKind};
+            use eucalyptus_core::logging::GLOBAL_TOASTS;
+            let mut toasts = GLOBAL_TOASTS.lock();
+            toasts.add(Toast {
+                    text: _msg.into(),
+                    kind: ToastKind::Warning,
+                    options: egui_toast_fork::ToastOptions::default()
+                        .duration_in_seconds(3.0)
+                        .show_progress(true),
+                    style: egui_toast_fork::ToastStyle::default(),
+                });
+        }
+    }};
+}
+
+/// Info log macro
+///
+/// This is useful for notifying the user of a change, where it doesn't have to be important.
+///
+/// This macro creates a toast under the [`egui_toast_fork::ToastKind::Info`] and logs
+/// with [`log::debug!`]
+#[macro_export]
+macro_rules! info {
+    ($($arg:tt)*) => {{
+        let _msg = format!($($arg)*);
+        log::debug!("{}", _msg);
+
+        #[cfg(feature = "editor")]
+        {
+            use egui_toast_fork::{Toast, ToastKind};
+            use eucalyptus_core::logging::GLOBAL_TOASTS;
+            let mut toasts = GLOBAL_TOASTS.lock();
+            toasts.add(Toast {
+                text: _msg.into(),
+                kind: ToastKind::Info,
+                options: egui_toast_fork::ToastOptions::default()
+                    .duration_in_seconds(1.0)
+                    .show_progress(false),
+                style: egui_toast_fork::ToastStyle::default(),
+            });
+        }
+    }};
+}
+
+/// Macro for logging info without the console
+///
+/// This macro should be "info_toast", however in the case that I ever need to add some more functionality,
+/// this would be useful.
+///
+/// Its feature-heavy counterpart would be [`crate::success!`].
+///
+/// It creates a toast under [`egui_toast_fork::ToastKind::Info`].
+#[macro_export]
+macro_rules! info_without_console {
+    ($($arg:tt)*) => {
+        let _msg = format!($($arg)*);
+
+        #[cfg(feature = "editor")]
+        {
+            use egui_toast_fork::{Toast, ToastKind};
+            use eucalyptus_core::logging::GLOBAL_TOASTS;
+            let mut toasts = GLOBAL_TOASTS.lock();
+            toasts.add(Toast {
+                text: _msg.into(),
+                kind: ToastKind::Info,
+                options: egui_toast_fork::ToastOptions::default()
+                    .duration_in_seconds(1.0)
+                    .show_progress(false),
+                style: egui_toast_fork::ToastStyle::default(),
+            });
+        }
+    };
+}
+
+/// Macro for logging a successful action without the console
+///
+/// This macro should be "success_toast", however in the case that I ever need to add some more functionality,
+/// this would be useful.
+///
+/// Its feature-heavy counterpart would be [`crate::success!`].
+///
+/// It creates a toast under [`egui_toast_fork::ToastKind::Success`].
+#[macro_export]
+macro_rules! success_without_console {
+    ($($arg:tt)*) => {
+        let _msg = format!($($arg)*);
+
+        #[cfg(feature = "editor")]
+        {
+            use egui_toast_fork::{Toast, ToastKind};
+            use eucalyptus_core::logging::GLOBAL_TOASTS;
+            let mut toasts = GLOBAL_TOASTS.lock();
+            toasts.add(Toast {
+                text: _msg.into(),
+                kind: ToastKind::Success,
+                options: egui_toast_fork::ToastOptions::default()
+                    .duration_in_seconds(3.0)
+                    .show_progress(true),
+                style: egui_toast_fork::ToastStyle::default(),
+            });
+        }
+    };
+}
+
+/// Macro for logging a successful action without the console
+///
+/// This macro should be "success_toast", however in the case that I ever need to add some more functionality,
+/// this would be useful.
+///
+/// Its feature-heavy counterpart would be [`crate::warn!`].
+///
+/// It creates a toast under [`egui_toast_fork::ToastKind::Warning`].
+#[macro_export]
+macro_rules! warn_without_console {
+    ($($arg:tt)*) => {
+        let _msg = format!($($arg)*);
+
+        #[cfg(feature = "editor")]
+        {
+            use egui_toast_fork::{Toast, ToastKind};
+            use eucalyptus_core::logging::GLOBAL_TOASTS;
+            let mut toasts = GLOBAL_TOASTS.lock();
+            toasts.add(Toast {
+                text: _msg.into(),
+                kind: ToastKind::Warning,
+                options: egui_toast_fork::ToastOptions::default()
+                    .duration_in_seconds(3.0)
+                    .show_progress(true),
+                style: egui_toast_fork::ToastStyle::default(),
+            });
+        }
+    };
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting/camera.rs b/eucalyptus-core/src/scripting/camera.rs
new file mode 100644
index 0000000..9921526
--- /dev/null
+++ b/eucalyptus-core/src/scripting/camera.rs
@@ -0,0 +1,386 @@
+use glam::DVec3;
+use rustyscript::{serde_json, Runtime};
+use serde::{Deserialize, Serialize};
+
+use crate::{camera::CameraType, scripting::ScriptableModule};
+
+impl ScriptableModule for SerializableCamera {
+    fn register(runtime: &mut Runtime) -> anyhow::Result<()> {
+        runtime.register_function("moveForward", Self::move_forward)?;
+        runtime.register_function("moveBackward", Self::move_backward)?;
+        runtime.register_function("moveLeft", Self::move_left)?;
+        runtime.register_function("moveRight", Self::move_right)?;
+        runtime.register_function("moveUp", Self::move_up)?;
+        runtime.register_function("moveDown", Self::move_down)?;
+
+        // Mouse look
+        runtime.register_function("trackMouseDelta", Self::track_mouse_delta)?;
+
+        // Camera switching and management
+        runtime.register_function("switchToCameraByLabel", Self::switch_to_camera_by_label)?;
+        runtime.register_function("getActiveCameraLabel", Self::get_active_camera_label)?;
+        runtime.register_function("getAllCameraLabels", Self::get_all_camera_labels)?;
+        runtime.register_function("getCamerasByType", Self::get_cameras_by_type)?;
+
+        // Multi-camera manipulation
+        runtime.register_function("manipulateCameraByLabel", Self::manipulate_camera_by_label)?;
+        runtime.register_function("getCameraByLabel", Self::get_camera_by_label)?;
+        runtime.register_function("setCameraByLabel", Self::set_camera_by_label)?;
+
+        Ok(())
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct SerializableCamera {
+    pub label: String,
+    pub eye: DVec3,
+    pub target: DVec3,
+    pub up: DVec3,
+    pub aspect: f64,
+    pub fov: f64,
+    pub near: f64,
+    pub far: f64,
+    pub yaw: f64,
+    pub pitch: f64,
+    
+    pub speed: f64,
+    pub sensitivity: f64,
+
+    pub camera_type: CameraType,
+}
+
+impl SerializableCamera {
+    /// Moves the camera forward. 
+    /// 
+    /// # Parameters
+    /// * args[0] - Self as a Camera. The value of the speed can be adjusted
+    pub fn move_forward(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
+        if args.len() != 1 {
+            return Err(rustyscript::Error::Runtime("moveForward requires 1 arguments".to_string()));
+        }
+
+        let mut camera: SerializableCamera = serde_json::from_value(args[0].clone())
+            .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera: {}", e)))?;
+
+        let forward = (camera.target - camera.eye).normalize();
+        camera.eye += forward * camera.speed;
+        camera.target += forward * camera.speed;
+
+        serde_json::to_value(camera)
+            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Camera: {}", e)))
+    }
+
+    /// Moves the camera back. 
+    /// 
+    /// # Parameters
+    /// * args[0] - Self as a Camera. The value of the speed can be adjusted
+    pub fn move_backward(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
+        if args.len() != 1 {
+            return Err(rustyscript::Error::Runtime("moveBackward requires 1 arguments".to_string()));
+        }
+
+        let mut camera: SerializableCamera = serde_json::from_value(args[0].clone())
+            .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera: {}", e)))?;
+
+        let forward = (camera.target - camera.eye).normalize();
+        camera.eye -= forward * camera.speed;
+        camera.target -= forward * camera.speed;
+
+        serde_json::to_value(camera)
+            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Camera: {}", e)))
+    }
+
+    /// Moves the camera left. 
+    /// 
+    /// # Parameters
+    /// * args[0] - Self as a Camera. The value of the speed can be adjusted
+    pub fn move_left(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
+        if args.len() != 1 {
+            return Err(rustyscript::Error::Runtime("moveLeft requires 1 arguments".to_string()));
+        }
+
+        let mut camera: SerializableCamera = serde_json::from_value(args[0].clone())
+            .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera: {}", e)))?;
+
+        let forward = (camera.target - camera.eye).normalize();
+        let right = camera.up.cross(forward).normalize();
+        camera.eye -= right * camera.speed;
+        camera.target -= right * camera.speed;
+
+        serde_json::to_value(camera)
+            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Camera: {}", e)))
+    }
+
+    /// Moves the camera right. 
+    /// 
+    /// # Parameters
+    /// * args[0] - Self as a Camera. The value of the speed can be adjusted
+    pub fn move_right(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
+        if args.len() != 1 {
+            return Err(rustyscript::Error::Runtime("moveRight requires 1 arguments".to_string()));
+        }
+
+        let mut camera: SerializableCamera = serde_json::from_value(args[0].clone())
+            .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera: {}", e)))?;
+
+        let forward = (camera.target - camera.eye).normalize();
+        let right = camera.up.cross(forward).normalize();
+        camera.eye += right * camera.speed;
+        camera.target += right * camera.speed;
+
+        serde_json::to_value(camera)
+            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Camera: {}", e)))
+    }
+
+    /// Moves the camera up. 
+    /// 
+    /// # Parameters
+    /// * args[0] - Self as a Camera. The value of the speed can be adjusted
+    pub fn move_up(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
+        if args.len() != 1 {
+            return Err(rustyscript::Error::Runtime("moveUp requires 1 arguments".to_string()));
+        }
+
+        let mut camera: SerializableCamera = serde_json::from_value(args[0].clone())
+            .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera: {}", e)))?;
+
+        let up = camera.up.normalize();
+        camera.eye += up * camera.speed;
+        camera.target += up * camera.speed;
+
+        serde_json::to_value(camera)
+            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Camera: {}", e)))
+    }
+
+    /// Moves the camera down. 
+    /// 
+    /// # Parameters
+    /// * args[0] - Self as a Camera. The value of the speed can be adjusted
+    pub fn move_down(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
+        if args.len() != 1 {
+            return Err(rustyscript::Error::Runtime("moveDown requires 1 arguments".to_string()));
+        }
+
+        let mut camera: SerializableCamera = serde_json::from_value(args[0].clone())
+            .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera: {}", e)))?;
+
+        let up = camera.up.normalize();
+        camera.eye -= up * camera.speed;
+        camera.target -= up * camera.speed;
+
+        serde_json::to_value(camera)
+            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Camera: {}", e)))
+    }
+
+    /// Sets the tracking of the camera to the mouse delta
+    /// 
+    /// # Parameters
+    /// * args[0] - Self as a Camera
+    /// * args[1] - The dx value of the mouse position as a number
+    /// * args[2] - The dy value of the mouse position as a number
+    pub fn track_mouse_delta(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
+        if args.len() != 3 {
+            return Err(rustyscript::Error::Runtime("trackMouseDelta requires 3 arguments (camera, dx, dy)".to_string()));
+        }
+
+        let mut camera: SerializableCamera = serde_json::from_value(args[0].clone())
+            .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera: {}", e)))?;
+
+        let dx = args[1].as_f64().ok_or_else(|| {
+            rustyscript::Error::Runtime("dx must be a number".to_string())
+        })?;
+
+        let dy = args[2].as_f64().ok_or_else(|| {
+            rustyscript::Error::Runtime("dy must be a number".to_string())
+        })?;
+
+        camera.yaw += dx * camera.sensitivity;
+        camera.pitch += dy * camera.sensitivity;
+
+        camera.pitch = camera.pitch.clamp(-89.0_f64.to_radians(), 89.0_f64.to_radians());
+
+        let direction = DVec3::new(
+            camera.yaw.cos() * camera.pitch.cos(),
+            camera.pitch.sin(),
+            camera.yaw.sin() * camera.pitch.cos(),
+        );
+        camera.target = camera.eye + direction;
+
+        serde_json::to_value(camera)
+            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Camera: {}", e)))
+    }
+
+    pub fn switch_to_camera_by_label(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
+        if args.len() != 1 {
+            return Err(rustyscript::Error::Runtime("switchToCameraByLabel requires 1 argument (label)".to_string()));
+        }
+
+        let label = args[0].as_str().ok_or_else(|| {
+            rustyscript::Error::Runtime("Label must be a string".to_string())
+        })?;
+
+        let action = CameraAction {
+            action: "switch_camera".to_string(),
+            label: Some(label.to_string()),
+            camera_type: None,
+            camera_data: None,
+            property: None,
+            value: None,
+        };
+
+        serde_json::to_value(action)
+            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize camera action: {}", e)))
+    }
+
+    /// Get the label of the currently active camera
+    pub fn get_active_camera_label(_args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
+        let action = CameraAction {
+            action: "get_active_camera".to_string(),
+            label: None,
+            camera_type: None,
+            camera_data: None,
+            property: None,
+            value: None,
+        };
+
+        serde_json::to_value(action)
+            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize camera action: {}", e)))
+    }
+
+    /// Get all camera labels in the world
+    pub fn get_all_camera_labels(_args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
+        let action = CameraAction {
+            action: "get_all_cameras".to_string(),
+            label: None,
+            camera_type: None,
+            camera_data: None,
+            property: None,
+            value: None,
+        };
+
+        serde_json::to_value(action)
+            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize camera action: {}", e)))
+    }
+
+    /// Get cameras by type
+    pub fn get_cameras_by_type(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
+        if args.len() != 1 {
+            return Err(rustyscript::Error::Runtime("getCamerasByType requires 1 argument (camera_type)".to_string()));
+        }
+
+        let camera_type = args[0].as_str().ok_or_else(|| {
+            rustyscript::Error::Runtime("Camera type must be a string".to_string())
+        })?;
+
+        // Validate camera type
+        match camera_type {
+            "Normal" | "Debug" | "Player" => {
+                let action = CameraAction {
+                    action: "get_cameras_by_type".to_string(),
+                    label: None,
+                    camera_type: Some(camera_type.to_string()),
+                    camera_data: None,
+                    property: None,
+                    value: None,
+                };
+
+                serde_json::to_value(action)
+                    .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize camera action: {}", e)))
+            }
+            _ => Err(rustyscript::Error::Runtime("Invalid camera type. Use 'Normal', 'Debug', or 'Player'".to_string()))
+        }
+    }
+
+    /// Manipulate a specific camera by label without switching to it
+    pub fn manipulate_camera_by_label(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
+        if args.len() != 3 {
+            return Err(rustyscript::Error::Runtime("manipulateCameraByLabel requires 3 arguments (label, property, value)".to_string()));
+        }
+
+        let label = args[0].as_str().ok_or_else(|| {
+            rustyscript::Error::Runtime("Label must be a string".to_string())
+        })?;
+
+        let property = args[1].as_str().ok_or_else(|| {
+            rustyscript::Error::Runtime("Property must be a string".to_string())
+        })?;
+
+        // Validate property
+        match property {
+            "position" | "target" | "speed" | "sensitivity" | "fov" | "yaw" | "pitch" => {
+                let action = CameraAction {
+                    action: "manipulate_camera".to_string(),
+                    label: Some(label.to_string()),
+                    camera_type: None,
+                    camera_data: None,
+                    property: Some(property.to_string()),
+                    value: Some(args[2].clone()),
+                };
+
+                serde_json::to_value(action)
+                    .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize camera action: {}", e)))
+            }
+            _ => Err(rustyscript::Error::Runtime("Invalid property. Use 'position', 'target', 'speed', 'sensitivity', 'fov', 'yaw', or 'pitch'".to_string()))
+        }
+    }
+
+    /// Get a camera's data by label
+    pub fn get_camera_by_label(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
+        if args.len() != 1 {
+            return Err(rustyscript::Error::Runtime("getCameraByLabel requires 1 argument (label)".to_string()));
+        }
+
+        let label = args[0].as_str().ok_or_else(|| {
+            rustyscript::Error::Runtime("Label must be a string".to_string())
+        })?;
+
+        let action = CameraAction {
+            action: "get_camera_by_label".to_string(),
+            label: Some(label.to_string()),
+            camera_type: None,
+            camera_data: None,
+            property: None,
+            value: None,
+        };
+
+        serde_json::to_value(action)
+            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize camera action: {}", e)))
+    }
+
+    /// Set a camera's complete data by label
+    pub fn set_camera_by_label(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
+        if args.len() != 2 {
+            return Err(rustyscript::Error::Runtime("setCameraByLabel requires 2 arguments (label, camera_data)".to_string()));
+        }
+
+        let label = args[0].as_str().ok_or_else(|| {
+            rustyscript::Error::Runtime("Label must be a string".to_string())
+        })?;
+
+        let camera_data: SerializableCamera = serde_json::from_value(args[1].clone())
+            .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera data: {}", e)))?;
+
+        let action = CameraAction {
+            action: "set_camera_by_label".to_string(),
+            label: Some(label.to_string()),
+            camera_type: None,
+            camera_data: Some(camera_data),
+            property: None,
+            value: None,
+        };
+
+        serde_json::to_value(action)
+            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize camera action: {}", e)))
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct CameraAction {
+    pub action: String,
+    pub label: Option<String>,
+    pub camera_type: Option<String>,
+    pub camera_data: Option<SerializableCamera>,
+    pub property: Option<String>,
+    pub value: Option<serde_json::Value>,
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting/entity.rs b/eucalyptus-core/src/scripting/entity.rs
new file mode 100644
index 0000000..c4a8652
--- /dev/null
+++ b/eucalyptus-core/src/scripting/entity.rs
@@ -0,0 +1,332 @@
+use crate::{scripting::ScriptableModule, states::{ModelProperties, PropertyValue}};
+use rustyscript::{serde_json, Runtime};
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Serialize, Deserialize)]
+pub struct SerializableModelProperties {
+    pub properties: std::collections::HashMap<String, serde_json::Value>,
+}
+
+impl From<&ModelProperties> for SerializableModelProperties {
+    fn from(props: &ModelProperties) -> Self {
+        let mut properties = std::collections::HashMap::new();
+        
+        for (key, value) in props.custom_properties.iter() {
+            let json_value = match value {
+                PropertyValue::String(s) => serde_json::Value::String(s.clone()),
+                PropertyValue::Int(i) => serde_json::Value::Number(serde_json::Number::from(*i)),
+                PropertyValue::Float(f) => serde_json::Value::Number(
+                    serde_json::Number::from_f64(*f as f64).unwrap_or(serde_json::Number::from(0))
+                ),
+                PropertyValue::Bool(b) => serde_json::Value::Bool(*b),
+                PropertyValue::Vec3(v) => serde_json::Value::Array(vec![
+                    serde_json::Value::Number(serde_json::Number::from_f64(v[0] as f64).unwrap()),
+                    serde_json::Value::Number(serde_json::Number::from_f64(v[1] as f64).unwrap()),
+                    serde_json::Value::Number(serde_json::Number::from_f64(v[2] as f64).unwrap()),
+                ]),
+            };
+            properties.insert(key.clone(), json_value);
+        }
+        
+        Self { properties }
+    }
+}
+
+impl SerializableModelProperties {
+    pub fn to_model_properties(&self) -> ModelProperties {
+        let mut props = ModelProperties::default();
+        
+        for (key, value) in &self.properties {
+            let property_value = match value {
+                serde_json::Value::String(s) => PropertyValue::String(s.clone()),
+                serde_json::Value::Number(n) => {
+                    if let Some(i) = n.as_i64() {
+                        PropertyValue::Int(i)
+                    } else if let Some(f) = n.as_f64() {
+                        PropertyValue::Float(f)
+                    } else {
+                        continue;
+                    }
+                },
+                serde_json::Value::Bool(b) => PropertyValue::Bool(*b),
+                serde_json::Value::Array(arr) => {
+                    if arr.len() == 3 {
+                        if let (Some(x), Some(y), Some(z)) = (
+                            arr[0].as_f64(),
+                            arr[1].as_f64(),
+                            arr[2].as_f64(),
+                        ) {
+                            PropertyValue::Vec3([x as f32, y as f32, z as f32])
+                        } else {
+                            continue;
+                        }
+                    } else {
+                        continue;
+                    }
+                },
+                _ => continue,
+            };
+            props.set_property(key.clone(), property_value);
+        }
+        
+        props
+    }
+}
+
+impl ScriptableModule for ModelProperties {
+    fn register(runtime: &mut Runtime) -> anyhow::Result<()> {
+        runtime.register_function("getProperty", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 2 {
+                return Err(rustyscript::Error::Runtime("getProperty requires 2 arguments (properties, key)".to_string()));
+            }
+            
+            let props: SerializableModelProperties = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
+            
+            let key = args[1].as_str()
+                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
+            
+            let value = props.properties.get(key)
+                .cloned()
+                .unwrap_or(serde_json::Value::Null);
+            
+            Ok(value)
+        })?;
+
+        // Set property (string)
+        runtime.register_function("setPropertyString", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 3 {
+                return Err(rustyscript::Error::Runtime("setPropertyString requires 3 arguments (properties, key, value)".to_string()));
+            }
+            
+            let mut props: SerializableModelProperties = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
+            
+            let key = args[1].as_str()
+                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
+            
+            let value = args[2].as_str()
+                .ok_or_else(|| rustyscript::Error::Runtime("Value must be a string".to_string()))?;
+            
+            props.properties.insert(key.to_string(), serde_json::Value::String(value.to_string()));
+            
+            serde_json::to_value(props)
+                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize properties: {}", e)))
+        })?;
+
+        // Set property (int)
+        runtime.register_function("setPropertyInt", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 3 {
+                return Err(rustyscript::Error::Runtime("setPropertyInt requires 3 arguments (properties, key, value)".to_string()));
+            }
+            
+            let mut props: SerializableModelProperties = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
+            
+            let key = args[1].as_str()
+                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
+            
+            let value = args[2].as_i64()
+                .ok_or_else(|| rustyscript::Error::Runtime("Value must be an integer".to_string()))?;
+            
+            props.properties.insert(key.to_string(), serde_json::Value::Number(serde_json::Number::from(value)));
+            
+            serde_json::to_value(props)
+                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize properties: {}", e)))
+        })?;
+
+        // Set property (float)
+        runtime.register_function("setPropertyFloat", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 3 {
+                return Err(rustyscript::Error::Runtime("setPropertyFloat requires 3 arguments (properties, key, value)".to_string()));
+            }
+            
+            let mut props: SerializableModelProperties = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
+            
+            let key = args[1].as_str()
+                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
+            
+            let value = args[2].as_f64()
+                .ok_or_else(|| rustyscript::Error::Runtime("Value must be a number".to_string()))?;
+            
+            props.properties.insert(key.to_string(), serde_json::Value::Number(
+                serde_json::Number::from_f64(value).unwrap_or(serde_json::Number::from(0))
+            ));
+            
+            serde_json::to_value(props)
+                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize properties: {}", e)))
+        })?;
+
+        // Set property (bool)
+        runtime.register_function("setPropertyBool", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 3 {
+                return Err(rustyscript::Error::Runtime("setPropertyBool requires 3 arguments (properties, key, value)".to_string()));
+            }
+            
+            let mut props: SerializableModelProperties = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
+            
+            let key = args[1].as_str()
+                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
+            
+            let value = args[2].as_bool()
+                .ok_or_else(|| rustyscript::Error::Runtime("Value must be a boolean".to_string()))?;
+            
+            props.properties.insert(key.to_string(), serde_json::Value::Bool(value));
+            
+            serde_json::to_value(props)
+                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize properties: {}", e)))
+        })?;
+
+        // Set property (Vec3)
+        runtime.register_function("setPropertyVec3", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 3 {
+                return Err(rustyscript::Error::Runtime("setPropertyVec3 requires 3 arguments (properties, key, value)".to_string()));
+            }
+            
+            let mut props: SerializableModelProperties = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
+            
+            let key = args[1].as_str()
+                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
+            
+            let value = args[2].as_array()
+                .ok_or_else(|| rustyscript::Error::Runtime("Value must be an array".to_string()))?;
+            
+            if value.len() != 3 {
+                return Err(rustyscript::Error::Runtime("Vec3 array must have exactly 3 elements".to_string()));
+            }
+            
+            let x = value[0].as_f64().ok_or_else(|| rustyscript::Error::Runtime("Vec3 elements must be numbers".to_string()))?;
+            let y = value[1].as_f64().ok_or_else(|| rustyscript::Error::Runtime("Vec3 elements must be numbers".to_string()))?;
+            let z = value[2].as_f64().ok_or_else(|| rustyscript::Error::Runtime("Vec3 elements must be numbers".to_string()))?;
+            
+            let vec3_array = serde_json::Value::Array(vec![
+                serde_json::Value::Number(serde_json::Number::from_f64(x).unwrap()),
+                serde_json::Value::Number(serde_json::Number::from_f64(y).unwrap()),
+                serde_json::Value::Number(serde_json::Number::from_f64(z).unwrap()),
+            ]);
+            
+            props.properties.insert(key.to_string(), vec3_array);
+            
+            serde_json::to_value(props)
+                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize properties: {}", e)))
+        })?;
+
+        // Typed getters
+        runtime.register_function("getString", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 2 {
+                return Err(rustyscript::Error::Runtime("getString requires 2 arguments (properties, key)".to_string()));
+            }
+            
+            let props: SerializableModelProperties = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
+            
+            let key = args[1].as_str()
+                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
+            
+            let value = props.properties.get(key)
+                .and_then(|v| v.as_str())
+                .unwrap_or("")
+                .to_string();
+            
+            Ok(serde_json::Value::String(value))
+        })?;
+
+        runtime.register_function("getInt", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 2 {
+                return Err(rustyscript::Error::Runtime("getInt requires 2 arguments (properties, key)".to_string()));
+            }
+            
+            let props: SerializableModelProperties = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
+            
+            let key = args[1].as_str()
+                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
+            
+            let value = props.properties.get(key)
+                .and_then(|v| v.as_i64())
+                .unwrap_or(0);
+            
+            Ok(serde_json::Value::Number(serde_json::Number::from(value)))
+        })?;
+
+        runtime.register_function("getFloat", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 2 {
+                return Err(rustyscript::Error::Runtime("getFloat requires 2 arguments (properties, key)".to_string()));
+            }
+            
+            let props: SerializableModelProperties = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
+            
+            let key = args[1].as_str()
+                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
+            
+            let value = props.properties.get(key)
+                .and_then(|v| v.as_f64())
+                .unwrap_or(0.0);
+            
+            Ok(serde_json::Value::Number(serde_json::Number::from_f64(value).unwrap()))
+        })?;
+
+        runtime.register_function("getBool", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 2 {
+                return Err(rustyscript::Error::Runtime("getBool requires 2 arguments (properties, key)".to_string()));
+            }
+            
+            let props: SerializableModelProperties = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
+            
+            let key = args[1].as_str()
+                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
+            
+            let value = props.properties.get(key)
+                .and_then(|v| v.as_bool())
+                .unwrap_or(false);
+            
+            Ok(serde_json::Value::Bool(value))
+        })?;
+
+        runtime.register_function("getVec3", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 2 {
+                return Err(rustyscript::Error::Runtime("getVec3 requires 2 arguments (properties, key)".to_string()));
+            }
+            
+            let props: SerializableModelProperties = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
+            
+            let key = args[1].as_str()
+                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
+            
+            let value = props.properties.get(key)
+                .and_then(|v| v.as_array())
+                .cloned()
+                .unwrap_or_else(|| vec![
+                    serde_json::Value::Number(serde_json::Number::from(0)),
+                    serde_json::Value::Number(serde_json::Number::from(0)),
+                    serde_json::Value::Number(serde_json::Number::from(0)),
+                ]);
+            
+            Ok(serde_json::Value::Array(value))
+        })?;
+
+        runtime.register_function("hasProperty", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 2 {
+                return Err(rustyscript::Error::Runtime("hasProperty requires 2 arguments (properties, key)".to_string()));
+            }
+            
+            let props: SerializableModelProperties = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
+            
+            let key = args[1].as_str()
+                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
+            
+            let has_property = props.properties.contains_key(key);
+            Ok(serde_json::Value::Bool(has_property))
+        })?;
+
+        log::info!("[Script] Initialised entity custom properties module");
+        Ok(())
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting/input.rs b/eucalyptus-core/src/scripting/input.rs
new file mode 100644
index 0000000..d590516
--- /dev/null
+++ b/eucalyptus-core/src/scripting/input.rs
@@ -0,0 +1,288 @@
+use std::{collections::{HashMap, HashSet}, time::{Duration, Instant}};
+
+use rustyscript::{serde_json, Runtime};
+use serde::{Deserialize, Serialize};
+use winit::{event::MouseButton, keyboard::KeyCode};
+
+use crate::scripting::ScriptableModule;
+
+#[derive(Clone)]
+pub struct InputState {
+    #[allow(dead_code)]
+    pub last_key_press_times: HashMap<KeyCode, Instant>,
+    #[allow(dead_code)]
+    pub double_press_threshold: Duration,
+    pub mouse_pos: (f64, f64),
+    pub mouse_button: HashSet<MouseButton>,    
+    pub pressed_keys: HashSet<KeyCode>,
+    pub mouse_delta: Option<(f64, f64)>,
+    pub is_cursor_locked: bool,
+}
+
+#[derive(Clone, Serialize, Deserialize)]
+pub struct SerializableInputState {
+    pub mouse_pos: (f64, f64),
+    pub pressed_keys: Vec<String>,
+    pub mouse_delta: Option<(f64, f64)>,
+    pub is_cursor_locked: bool,
+}
+
+impl From<&InputState> for SerializableInputState {
+    fn from(input_state: &InputState) -> Self {
+        Self {
+            mouse_pos: input_state.mouse_pos,
+            pressed_keys: input_state.pressed_keys.iter()
+                .filter_map(|key| keycode_to_string(*key))
+                .collect(),
+            mouse_delta: input_state.mouse_delta,
+            is_cursor_locked: input_state.is_cursor_locked,
+        }
+    }
+}
+
+impl Default for InputState {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl ScriptableModule for InputState {
+    fn register(runtime: &mut Runtime) -> anyhow::Result<()> {
+        runtime.register_function("isKeyPressed", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 2 {
+                return Err(rustyscript::Error::Runtime("isKeyPressed requires 2 arguments (inputState, keyCode)".to_string()));
+            }
+            
+            let input_state: SerializableInputState = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid input state: {}", e)))?;
+            
+            let key_code_str = args[1].as_str()
+                .ok_or_else(|| rustyscript::Error::Runtime("Key code must be a string".to_string()))?;
+            
+            let is_pressed = input_state.pressed_keys.contains(&key_code_str.to_string());
+            Ok(serde_json::Value::Bool(is_pressed))
+        })?;
+
+        runtime.register_function("getMouseX", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 1 {
+                return Err(rustyscript::Error::Runtime("getMouseX requires 1 argument (inputState)".to_string()));
+            }
+            
+            let input_state: SerializableInputState = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid input state: {}", e)))?;
+            
+            Ok(serde_json::Value::Number(serde_json::Number::from_f64(input_state.mouse_pos.0).unwrap()))
+        })?;
+
+        runtime.register_function("getMouseY", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 1 {
+                return Err(rustyscript::Error::Runtime("getMouseY requires 1 argument (inputState)".to_string()));
+            }
+            
+            let input_state: SerializableInputState = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid input state: {}", e)))?;
+            
+            Ok(serde_json::Value::Number(serde_json::Number::from_f64(input_state.mouse_pos.1).unwrap()))
+        })?;
+
+        runtime.register_function("getMouseDeltaX", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 1 {
+                return Err(rustyscript::Error::Runtime("getMouseDeltaX requires 1 argument (inputState)".to_string()));
+            }
+            
+            let input_state: SerializableInputState = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid input state: {}", e)))?;
+            
+            let delta_x = input_state.mouse_delta.map(|d| d.0).unwrap_or(0.0);
+            Ok(serde_json::Value::Number(serde_json::Number::from_f64(delta_x).unwrap()))
+        })?;
+
+        runtime.register_function("getMouseDeltaY", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 1 {
+                return Err(rustyscript::Error::Runtime("getMouseDeltaY requires 1 argument (inputState)".to_string()));
+            }
+            
+            let input_state: SerializableInputState = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid input state: {}", e)))?;
+            
+            let delta_y = input_state.mouse_delta.map(|d| d.1).unwrap_or(0.0);
+            Ok(serde_json::Value::Number(serde_json::Number::from_f64(delta_y).unwrap()))
+        })?;
+
+        runtime.register_function("lockCursor", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 1 {
+                return Err(rustyscript::Error::Runtime("lockCursor requires 1 argument (locked)".to_string()));
+            }
+            
+            let _locked = args[0].as_bool()
+                .ok_or_else(|| rustyscript::Error::Runtime("Locked parameter must be a boolean".to_string()))?;
+            
+            // This function would need to communicate back to the engine to actually lock the cursor
+            // For now, just return success
+            Ok(serde_json::Value::Bool(true))
+        })?;
+
+        log::info!("[Script] Initialised input module");
+        Ok(())
+    }
+}
+
+impl InputState {
+    pub fn new() -> Self {
+        Self {
+            mouse_pos: Default::default(),
+            mouse_button: Default::default(),
+            pressed_keys: HashSet::new(),
+            last_key_press_times: HashMap::new(),
+            double_press_threshold: Duration::from_millis(300),
+            mouse_delta: None,
+            is_cursor_locked: false,
+        }
+    }
+
+    pub fn lock_cursor(&mut self, toggle: bool) {
+        self.is_cursor_locked = toggle;
+    }
+}
+
+/// Helper function to convert string to KeyCode
+#[allow(dead_code)]
+fn string_to_keycode(key_str: &str) -> Option<KeyCode> {
+    match key_str {
+        "KeyA" => Some(KeyCode::KeyA),
+        "KeyB" => Some(KeyCode::KeyB),
+        "KeyC" => Some(KeyCode::KeyC),
+        "KeyD" => Some(KeyCode::KeyD),
+        "KeyE" => Some(KeyCode::KeyE),
+        "KeyF" => Some(KeyCode::KeyF),
+        "KeyG" => Some(KeyCode::KeyG),
+        "KeyH" => Some(KeyCode::KeyH),
+        "KeyI" => Some(KeyCode::KeyI),
+        "KeyJ" => Some(KeyCode::KeyJ),
+        "KeyK" => Some(KeyCode::KeyK),
+        "KeyL" => Some(KeyCode::KeyL),
+        "KeyM" => Some(KeyCode::KeyM),
+        "KeyN" => Some(KeyCode::KeyN),
+        "KeyO" => Some(KeyCode::KeyO),
+        "KeyP" => Some(KeyCode::KeyP),
+        "KeyQ" => Some(KeyCode::KeyQ),
+        "KeyR" => Some(KeyCode::KeyR),
+        "KeyS" => Some(KeyCode::KeyS),
+        "KeyT" => Some(KeyCode::KeyT),
+        "KeyU" => Some(KeyCode::KeyU),
+        "KeyV" => Some(KeyCode::KeyV),
+        "KeyW" => Some(KeyCode::KeyW),
+        "KeyX" => Some(KeyCode::KeyX),
+        "KeyY" => Some(KeyCode::KeyY),
+        "KeyZ" => Some(KeyCode::KeyZ),
+        "Space" => Some(KeyCode::Space),
+        "ShiftLeft" => Some(KeyCode::ShiftLeft),
+        "ShiftRight" => Some(KeyCode::ShiftRight),
+        "ControlLeft" => Some(KeyCode::ControlLeft),
+        "ControlRight" => Some(KeyCode::ControlRight),
+        "AltLeft" => Some(KeyCode::AltLeft),
+        "AltRight" => Some(KeyCode::AltRight),
+        "Escape" => Some(KeyCode::Escape),
+        "Enter" => Some(KeyCode::Enter),
+        "Tab" => Some(KeyCode::Tab),
+        "ArrowUp" => Some(KeyCode::ArrowUp),
+        "ArrowDown" => Some(KeyCode::ArrowDown),
+        "ArrowLeft" => Some(KeyCode::ArrowLeft),
+        "ArrowRight" => Some(KeyCode::ArrowRight),
+        "Digit0" => Some(KeyCode::Digit0),
+        "Digit1" => Some(KeyCode::Digit1),
+        "Digit2" => Some(KeyCode::Digit2),
+        "Digit3" => Some(KeyCode::Digit3),
+        "Digit4" => Some(KeyCode::Digit4),
+        "Digit5" => Some(KeyCode::Digit5),
+        "Digit6" => Some(KeyCode::Digit6),
+        "Digit7" => Some(KeyCode::Digit7),
+        "Digit8" => Some(KeyCode::Digit8),
+        "Digit9" => Some(KeyCode::Digit9),
+        "F1" => Some(KeyCode::F1),
+        "F2" => Some(KeyCode::F2),
+        "F3" => Some(KeyCode::F3),
+        "F4" => Some(KeyCode::F4),
+        "F5" => Some(KeyCode::F5),
+        "F6" => Some(KeyCode::F6),
+        "F7" => Some(KeyCode::F7),
+        "F8" => Some(KeyCode::F8),
+        "F9" => Some(KeyCode::F9),
+        "F10" => Some(KeyCode::F10),
+        "F11" => Some(KeyCode::F11),
+        "F12" => Some(KeyCode::F12),
+        _ => None,
+    }
+}
+
+/// Helper function to convert KeyCode to string
+fn keycode_to_string(key_code: KeyCode) -> Option<String> {
+    match key_code {
+        KeyCode::KeyA => Some("KeyA".to_string()),
+        KeyCode::KeyB => Some("KeyB".to_string()),
+        KeyCode::KeyC => Some("KeyC".to_string()),
+        KeyCode::KeyD => Some("KeyD".to_string()),
+        KeyCode::KeyE => Some("KeyE".to_string()),
+        KeyCode::KeyF => Some("KeyF".to_string()),
+        KeyCode::KeyG => Some("KeyG".to_string()),
+        KeyCode::KeyH => Some("KeyH".to_string()),
+        KeyCode::KeyI => Some("KeyI".to_string()),
+        KeyCode::KeyJ => Some("KeyJ".to_string()),
+        KeyCode::KeyK => Some("KeyK".to_string()),
+        KeyCode::KeyL => Some("KeyL".to_string()),
+        KeyCode::KeyM => Some("KeyM".to_string()),
+        KeyCode::KeyN => Some("KeyN".to_string()),
+        KeyCode::KeyO => Some("KeyO".to_string()),
+        KeyCode::KeyP => Some("KeyP".to_string()),
+        KeyCode::KeyQ => Some("KeyQ".to_string()),
+        KeyCode::KeyR => Some("KeyR".to_string()),
+        KeyCode::KeyS => Some("KeyS".to_string()),
+        KeyCode::KeyT => Some("KeyT".to_string()),
+        KeyCode::KeyU => Some("KeyU".to_string()),
+        KeyCode::KeyV => Some("KeyV".to_string()),
+        KeyCode::KeyW => Some("KeyW".to_string()),
+        KeyCode::KeyX => Some("KeyX".to_string()),
+        KeyCode::KeyY => Some("KeyY".to_string()),
+        KeyCode::KeyZ => Some("KeyZ".to_string()),
+        KeyCode::Space => Some("Space".to_string()),
+        KeyCode::ShiftLeft => Some("ShiftLeft".to_string()),
+        KeyCode::ShiftRight => Some("ShiftRight".to_string()),
+        KeyCode::ControlLeft => Some("ControlLeft".to_string()),
+        KeyCode::ControlRight => Some("ControlRight".to_string()),
+        KeyCode::AltLeft => Some("AltLeft".to_string()),
+        KeyCode::AltRight => Some("AltRight".to_string()),
+        KeyCode::Escape => Some("Escape".to_string()),
+        KeyCode::Enter => Some("Enter".to_string()),
+        KeyCode::Tab => Some("Tab".to_string()),
+        KeyCode::ArrowUp => Some("ArrowUp".to_string()),
+        KeyCode::ArrowDown => Some("ArrowDown".to_string()),
+        KeyCode::ArrowLeft => Some("ArrowLeft".to_string()),
+        KeyCode::ArrowRight => Some("ArrowRight".to_string()),
+        KeyCode::Digit0 => Some("Digit0".to_string()),
+        KeyCode::Digit1 => Some("Digit1".to_string()),
+        KeyCode::Digit2 => Some("Digit2".to_string()),
+        KeyCode::Digit3 => Some("Digit3".to_string()),
+        KeyCode::Digit4 => Some("Digit4".to_string()),
+        KeyCode::Digit5 => Some("Digit5".to_string()),
+        KeyCode::Digit6 => Some("Digit6".to_string()),
+        KeyCode::Digit7 => Some("Digit7".to_string()),
+        KeyCode::Digit8 => Some("Digit8".to_string()),
+        KeyCode::Digit9 => Some("Digit9".to_string()),
+        KeyCode::F1 => Some("F1".to_string()),
+        KeyCode::F2 => Some("F2".to_string()),
+        KeyCode::F3 => Some("F3".to_string()),
+        KeyCode::F4 => Some("F4".to_string()),
+        KeyCode::F5 => Some("F5".to_string()),
+        KeyCode::F6 => Some("F6".to_string()),
+        KeyCode::F7 => Some("F7".to_string()),
+        KeyCode::F8 => Some("F8".to_string()),
+        KeyCode::F9 => Some("F9".to_string()),
+        KeyCode::F10 => Some("F10".to_string()),
+        KeyCode::F11 => Some("F11".to_string()),
+        KeyCode::F12 => Some("F12".to_string()),
+        _ => None,
+    }
+}
+
+#[derive(Clone, Copy)]
+pub struct Key(pub KeyCode);
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting/lighting.rs b/eucalyptus-core/src/scripting/lighting.rs
new file mode 100644
index 0000000..0e416e3
--- /dev/null
+++ b/eucalyptus-core/src/scripting/lighting.rs
@@ -0,0 +1,11 @@
+use rustyscript::Runtime;
+
+use crate::scripting::ScriptableModule;
+
+pub struct Lighting;
+
+impl ScriptableModule for Lighting {
+    fn register(_runtime: &mut Runtime) -> anyhow::Result<()> {
+        Ok(())
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting/math.rs b/eucalyptus-core/src/scripting/math.rs
new file mode 100644
index 0000000..761f08d
--- /dev/null
+++ b/eucalyptus-core/src/scripting/math.rs
@@ -0,0 +1,166 @@
+use dropbear_engine::entity::Transform;
+use glam::{DQuat, DVec3};
+use rustyscript::{serde_json, Runtime};
+
+use crate::scripting::ScriptableModule;
+
+pub struct Math;
+
+impl ScriptableModule for Math {
+    fn register(runtime: &mut Runtime) -> anyhow::Result<()> {
+        runtime.register_function("createTransform", |_args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            let transform = Transform::new();
+            serde_json::to_value(transform)
+                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Transform: {}", e)))
+        })?;
+
+        runtime.register_function("transformTranslate", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 2 {
+                return Err(rustyscript::Error::Runtime("transformTranslate requires 2 arguments".to_string()));
+            }
+            
+            let mut transform: Transform = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid transform: {}", e)))?;
+            
+            let translation = if let Some(array) = args[1].as_array() {
+                if array.len() != 3 {
+                    return Err(rustyscript::Error::Runtime("Translation array must have 3 elements".to_string()));
+                }
+                DVec3::new(
+                    array[0].as_f64().unwrap_or(0.0),
+                    array[1].as_f64().unwrap_or(0.0),
+                    array[2].as_f64().unwrap_or(0.0),
+                )
+            } else {
+                return Err(rustyscript::Error::Runtime("Translation must be an array".to_string()));
+            };
+            
+            transform.position += translation;
+            serde_json::to_value(transform)
+                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Transform: {}", e)))
+        })?;
+
+        runtime.register_function("transformRotateX", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 2 {
+                return Err(rustyscript::Error::Runtime("transformRotateX requires 2 arguments".to_string()));
+            }
+            
+            let mut transform: Transform = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid transform: {}", e)))?;
+            
+            let angle = args[1].as_f64().unwrap_or(0.0);
+            let rotation = DQuat::from_rotation_x(angle);
+            transform.rotation = rotation * transform.rotation;
+            
+            serde_json::to_value(transform)
+                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Transform: {}", e)))
+        })?;
+
+        runtime.register_function("transformRotateY", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 2 {
+                return Err(rustyscript::Error::Runtime("transformRotateY requires 2 arguments".to_string()));
+            }
+            
+            let mut transform: Transform = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid transform: {}", e)))?;
+            
+            let angle = args[1].as_f64().unwrap_or(0.0);
+            let rotation = DQuat::from_rotation_y(angle);
+            transform.rotation = rotation * transform.rotation;
+            
+            serde_json::to_value(transform)
+                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Transform: {}", e)))
+        })?;
+
+        runtime.register_function("transformRotateZ", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 2 {
+                return Err(rustyscript::Error::Runtime("transformRotateZ requires 2 arguments".to_string()));
+            }
+            
+            let mut transform: Transform = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid transform: {}", e)))?;
+            
+            let angle = args[1].as_f64().unwrap_or(0.0);
+            let rotation = DQuat::from_rotation_z(angle);
+            transform.rotation = rotation * transform.rotation;
+            
+            serde_json::to_value(transform)
+                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Transform: {}", e)))
+        })?;
+
+        runtime.register_function("transformScale", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 2 {
+                return Err(rustyscript::Error::Runtime("transformScale requires 2 arguments".to_string()));
+            }
+            
+            let mut transform: Transform = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid transform: {}", e)))?;
+            
+            let scale = if let Some(num) = args[1].as_f64() {
+                DVec3::splat(num)
+            } else if let Some(array) = args[1].as_array() {
+                if array.len() != 3 {
+                    return Err(rustyscript::Error::Runtime("Scale array must have 3 elements".to_string()));
+                }
+                DVec3::new(
+                    array[0].as_f64().unwrap_or(1.0),
+                    array[1].as_f64().unwrap_or(1.0),
+                    array[2].as_f64().unwrap_or(1.0),
+                )
+            } else {
+                return Err(rustyscript::Error::Runtime("Scale must be a number or array".to_string()));
+            };
+            
+            transform.scale *= scale;
+            serde_json::to_value(transform)
+                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Transform: {}", e)))
+        })?;
+
+        // this shouldn't be here as there is no need for a matrix...
+        runtime.register_function("transformMatrix", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 1 {
+                return Err(rustyscript::Error::Runtime("transformMatrix requires 1 argument".to_string()));
+            }
+            
+            let transform: Transform = serde_json::from_value(args[0].clone())
+                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid transform: {}", e)))?;
+            
+            let matrix = transform.matrix();
+            serde_json::to_value(matrix)
+                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize matrix: {}", e)))
+        })?;
+
+        runtime.register_function("createVec3", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            let x = args.get(0).and_then(|v| v.as_f64()).unwrap_or(0.0);
+            let y = args.get(1).and_then(|v| v.as_f64()).unwrap_or(0.0);
+            let z = args.get(2).and_then(|v| v.as_f64()).unwrap_or(0.0);
+            
+            let vec3 = DVec3::new(x, y, z);
+            serde_json::to_value(vec3)
+                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Vec3: {}", e)))
+        })?;
+
+        runtime.register_function("createQuatIdentity", |_args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            let quat = DQuat::IDENTITY;
+            serde_json::to_value(quat)
+                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Quaternion: {}", e)))
+        })?;
+
+        runtime.register_function("createQuatFromEuler", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            if args.len() != 3 {
+                return Err(rustyscript::Error::Runtime("createQuatFromEuler requires 3 arguments".to_string()));
+            }
+            
+            let x = args[0].as_f64().unwrap_or(0.0);
+            let y = args[1].as_f64().unwrap_or(0.0);
+            let z = args[2].as_f64().unwrap_or(0.0);
+            
+            let quat = DQuat::from_euler(glam::EulerRot::XYZ, x, y, z);
+            serde_json::to_value(quat)
+                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Quaternion: {}", e)))
+        })?;
+
+        log::info!("[Script] Initialised math module");
+        Ok(())
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting/mod.rs b/eucalyptus-core/src/scripting/mod.rs
new file mode 100644
index 0000000..dbfb883
--- /dev/null
+++ b/eucalyptus-core/src/scripting/mod.rs
@@ -0,0 +1,833 @@
+pub mod camera;
+pub mod entity;
+pub mod math;
+pub mod input;
+pub mod lighting;
+
+use dropbear_engine::camera::Camera;
+use dropbear_engine::entity::{AdoptedEntity, Transform};
+use dropbear_engine::lighting::{Light, LightComponent};
+use glam::DVec3;
+use hecs::World;
+use rustyscript::{serde_json, Module, ModuleHandle, Runtime, RuntimeOptions};
+use std::path::PathBuf;
+use std::{collections::HashMap, fs};
+use crate::camera::CameraComponent;
+use crate::states::{EntityNode, ModelProperties, ScriptComponent, PROJECT, SOURCE};
+
+/// A trait that describes a module that can be registered. 
+pub trait ScriptableModule {
+    /// Registers the functions for the dropbear typescript API
+    fn register(runtime: &mut Runtime) -> anyhow::Result<()>;
+    // /// Gathers the information into a serializable format that can be sent over to the 
+    // /// dropbear typescript API
+    // fn gather(world: &World, entity_id: hecs::Entity);
+    // /// Fetches the mutated information from the dropbear typescript module and applys it to the world
+    // fn release(world: &mut World, entity_id: hecs::Entity, data: &serde_json::Value) -> anyhow::Result<()>;
+}
+
+pub const TEMPLATE_SCRIPT: &'static str = include_str!("../../../resources/template.ts");
+
+pub enum ScriptAction {
+    AttachScript {
+        script_path: PathBuf,
+        script_name: String,
+    },
+    CreateAndAttachScript {
+        script_path: PathBuf,
+        script_name: String,
+    },
+    RemoveScript,
+    EditScript,
+}
+
+pub fn move_script_to_src(script_path: &PathBuf) -> anyhow::Result<PathBuf> {
+    let project_path = {
+        let project = PROJECT.read();
+        project.project_path.clone()
+    };
+
+    let src_path = {
+        let source_config = SOURCE.read();
+        source_config.path.clone()
+    };
+
+    let scripts_dir = src_path;
+    fs::create_dir_all(&scripts_dir)?;
+
+    let filename = script_path
+        .file_name()
+        .ok_or_else(|| anyhow::anyhow!("Invalid script path: no filename"))?;
+
+    let dest_path = scripts_dir.join(filename);
+
+    if dest_path.exists() {
+        log::info!(
+            "Script file already exists at {:?}, returning existing path",
+            dest_path
+        );
+        return Ok(dest_path);
+    }
+
+    const MAX_RETRIES: usize = 5;
+    const RETRY_DELAY_MS: u64 = 60;
+
+    let mut last_err: Option<std::io::Error> = None;
+    for attempt in 0..=MAX_RETRIES {
+        match fs::copy(script_path, &dest_path) {
+            Ok(_) => {
+                log::info!("Copied script from {:?} to {:?}", script_path, dest_path);
+                last_err = None;
+                break;
+            }
+            Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
+                log::warn!(
+                    "Script file already exists at {:?}, continuing anyway",
+                    dest_path
+                );
+                last_err = None;
+                break;
+            }
+            Err(e) => {
+                if e.raw_os_error() == Some(32) && attempt < MAX_RETRIES {
+                    log::warn!(
+                        "Sharing violation copying script (attempt {}/{}). Retrying in {}ms: {}",
+                        attempt + 1,
+                        MAX_RETRIES,
+                        RETRY_DELAY_MS,
+                        e
+                    );
+                    std::thread::sleep(std::time::Duration::from_millis(RETRY_DELAY_MS));
+                    last_err = Some(e);
+                    continue;
+                } else {
+                    return Err(e.into());
+                }
+            }
+        }
+    }
+    if let Some(e) = last_err {
+        return Err(e.into());
+    }
+
+    {
+        let source_config = SOURCE.read();
+        source_config.write_to(&project_path)?;
+    }
+
+    log::info!("Moved script from {:?} to {:?}", script_path, dest_path);
+    Ok(dest_path)
+}
+
+pub fn convert_entity_to_group(
+    world: &World,
+    entity_id: hecs::Entity,
+) -> anyhow::Result<EntityNode> {
+    if let Ok(mut query) = world.query_one::<(&AdoptedEntity, &Transform)>(entity_id) {
+        if let Some((adopted, _transform)) = query.get() {
+            let entity_name = adopted.model().label.clone();
+
+            let script_node = if let Ok(script) = world.get::<&ScriptComponent>(entity_id) {
+                Some(EntityNode::Script {
+                    name: script.name.clone(),
+                    path: script.path.clone(),
+                })
+            } else {
+                None
+            };
+
+            let entity_node = EntityNode::Entity {
+                id: entity_id,
+                name: entity_name.clone(),
+            };
+
+            if let Some(script_node) = script_node {
+                Ok(EntityNode::Group {
+                    name: entity_name,
+                    children: vec![entity_node, script_node],
+                    collapsed: false,
+                })
+            } else {
+                Ok(entity_node)
+            }
+        } else {
+            Err(anyhow::anyhow!("Failed to get entity components"))
+        }
+    } else {
+        Err(anyhow::anyhow!("Failed to query entity {:?}", entity_id))
+    }
+}
+
+pub fn attach_script_to_entity(
+    world: &mut World,
+    entity_id: hecs::Entity,
+    script_component: ScriptComponent,
+) -> anyhow::Result<()> {
+    if let Err(e) = world.insert_one(entity_id, script_component) {
+        return Err(anyhow::anyhow!("Failed to attach script to entity: {}", e));
+    }
+
+    log::info!("Successfully attached script to entity {:?}", entity_id);
+    Ok(())
+}
+
+pub struct ScriptManager {
+    pub runtime: Runtime,
+    compiled_scripts: HashMap<String, ModuleHandle>,
+    entity_script_data: HashMap<hecs::Entity, serde_json::Value>,
+}
+
+impl ScriptManager {
+    pub fn new() -> anyhow::Result<Self> {
+        let mut runtime = Runtime::new(RuntimeOptions::default())?;
+        let dropbear_content = include_str!("../../../resources/dropbear.ts");
+        let dropbear_module = Module::new("./dropbear.ts", dropbear_content);
+        runtime.load_module(&dropbear_module)?;
+        log::debug!("Loaded dropbear module");
+
+        let mut result = Self {
+            runtime,
+            compiled_scripts: HashMap::new(),
+            entity_script_data: HashMap::new(),
+        };
+
+        result = result
+            .register_module::<input::InputState>()?
+            .register_module::<math::Math>()?
+            .register_module::<ModelProperties>()?
+            .register_module::<camera::SerializableCamera>()?
+            .register_module::<lighting::Lighting>()?;
+
+        // Register utility functions
+        result.runtime.register_function("log", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            let msg = args.get(0)
+                .and_then(|v| v.as_str())
+                .unwrap_or("undefined");
+            println!("[Script] {}", msg);
+            Ok(serde_json::Value::Null)
+        })?;
+
+        result.runtime.register_function("time", |_args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
+            let time = std::time::SystemTime::now()
+                .duration_since(std::time::UNIX_EPOCH)
+                .unwrap()
+                .as_secs_f64();
+            Ok(serde_json::Value::Number(serde_json::Number::from_f64(time).unwrap()))
+        })?;
+        log::debug!("Initialised ScriptManager");
+        Ok(result)
+    }
+
+    /// Allows for the registering of other modules to use with the dropbear typescript
+    /// API
+    /// 
+    /// # Parameters
+    /// * A turbofish to a struct that uses the ScriptableModule trait
+    /// 
+    /// # Examples
+    /// ```rust
+    /// use eucalyptus_core::scripting::{camera, input, lighting, ScriptManager};
+    /// let script_manager = ScriptManager::new()?
+    ///     .register_module::<input::InputState>()?
+    ///     .register_module::<camera::SerializableCamera>()?
+    ///     .register_module::<lighting::Lighting>()?;
+    /// ```
+    pub fn register_module<T: ScriptableModule>(mut self) -> anyhow::Result<Self> {
+        T::register(&mut self.runtime)?;
+        Ok(self)
+    }
+
+    pub fn init_entity_script(
+        &mut self,
+        entity_id: hecs::Entity,
+        script_name: &str,
+        world: &mut World,
+        input_state: &input::InputState,
+    ) -> anyhow::Result<()> {
+        log_once::debug_once!("init_entity_script: {} for {:?}", script_name, entity_id);
+
+        if let Some(module) = self.compiled_scripts.get(script_name).cloned() {
+            // construct RawSceneData-like payload
+            let mut scene_map = serde_json::Map::new();
+
+            // entities array
+            let mut entities_arr: Vec<serde_json::Value> = Vec::new();
+            for (id, (adopted, transform)) in world.query::<(&AdoptedEntity, &Transform)>().iter() {
+                // try to get ModelProperties for this entity
+                let props_value = if let Ok(mut pq) = world.query_one::<&ModelProperties>(id) {
+                    if let Some(p) = pq.get() {
+                        serde_json::to_value(p)?
+                    } else {
+                        serde_json::Value::Object(serde_json::Map::new())
+                    }
+                } else {
+                    serde_json::Value::Object(serde_json::Map::new())
+                };
+
+                let mut ent_obj = serde_json::Map::new();
+                ent_obj.insert("label".to_string(), serde_json::Value::String(adopted.label().clone()));
+
+                // properties object expected shape: { custom_properties: Record<string, any> } or full ModelProperties
+                ent_obj.insert("properties".to_string(), props_value);
+
+                ent_obj.insert("transform".to_string(), serde_json::to_value(transform)?);
+
+                entities_arr.push(serde_json::Value::Object(ent_obj));
+            }
+            scene_map.insert("entities".to_string(), serde_json::Value::Array(entities_arr));
+
+            // cameras snapshot (keep minimal fields)
+            let mut cameras_arr: Vec<serde_json::Value> = Vec::new();
+            for (_id, (cam, _comp)) in world.query::<(&Camera, &CameraComponent)>().iter() {
+                cameras_arr.push(serde_json::json!({
+                    "label": cam.label,
+                    "data": {
+                        "eye": cam.eye.to_array(),
+                        "target": cam.target.to_array(),
+                        "up": cam.up.to_array(),
+                        "aspect": cam.aspect,
+                        "fov": cam.fov_y,
+                        "near": cam.znear,
+                        "far": cam.zfar,
+                        "yaw": cam.yaw,
+                        "pitch": cam.pitch,
+                        "speed": cam.speed,
+                        "sensitivity": cam.sensitivity
+                    }
+                }));
+            }
+            scene_map.insert("cameras".to_string(), serde_json::Value::Array(cameras_arr));
+
+            // lights snapshot (label only)
+            let mut lights_arr: Vec<serde_json::Value> = Vec::new();
+            for (_id, (_transform, _light_comp, light)) in world.query::<(&Transform, &LightComponent, &Light)>().iter() {
+                lights_arr.push(serde_json::json!({
+                    "label": light.label(),
+                }));
+            }
+            scene_map.insert("lights".to_string(), serde_json::Value::Array(lights_arr));
+
+            // current entity label (if available)
+            if let Ok(mut q) = world.query_one::<&AdoptedEntity>(entity_id) {
+                if let Some(adopted) = q.get() {
+                    scene_map.insert("current_entity".to_string(), serde_json::Value::String(adopted.label().clone()));
+                }
+            }
+
+            // input state
+            let serializable_input = input::SerializableInputState::from(input_state);
+            let mut payload = serde_json::Map::new();
+            payload.insert("scene".to_string(), serde_json::Value::Object(scene_map));
+            payload.insert("input".to_string(), serde_json::to_value(&serializable_input)?);
+
+            let script_data_value = serde_json::Value::Object(payload);
+
+            // call onLoad (module scripts call dropbear.start(s) and return dropbear.end())
+            match self.runtime.call_function::<serde_json::Value>(Some(&module), "onLoad", &vec![script_data_value.clone()]) {
+                Ok(result) => {
+                    log::debug!("Called onLoad for entity {:?}", entity_id);
+                    // apply whatever the script returned (expect Partial RawSceneData shape)
+                    let _ = self.apply_script_data_to_world(entity_id, &result, world);
+                    // store initial data (script-managed)
+                    self.entity_script_data.insert(entity_id, result);
+                }
+                Err(e) => {
+                    log::warn!("onLoad missing/failed for entity {:?}: {}", entity_id, e);
+                    // store the payload we sent so future updates have context
+                    self.entity_script_data.insert(entity_id, script_data_value);
+                }
+            }
+
+            Ok(())
+        } else {
+            Err(anyhow::anyhow!("Script '{}' not found", script_name))
+        }
+    }
+
+    pub fn update_entity_script(
+        &mut self,
+        entity_id: hecs::Entity,
+        script_name: &str,
+        world: &mut World,
+        input_state: &input::InputState,
+        dt: f32,
+    ) -> anyhow::Result<()> {
+        log_once::debug_once!("Update entity script name: {}", script_name);
+
+        if let Some(module) = self.compiled_scripts.get(script_name).cloned() {
+            // Build the same RawSceneData-like payload as init
+            let mut scene_map = serde_json::Map::new();
+
+            let mut entities_arr: Vec<serde_json::Value> = Vec::new();
+            for (id, (adopted, transform)) in world.query::<(&AdoptedEntity, &Transform)>().iter() {
+                let props_value = if let Ok(mut pq) = world.query_one::<&ModelProperties>(id) {
+                    if let Some(p) = pq.get() {
+                        serde_json::to_value(p)?
+                    } else {
+                        serde_json::Value::Object(serde_json::Map::new())
+                    }
+                } else {
+                    serde_json::Value::Object(serde_json::Map::new())
+                };
+
+                let mut ent_obj = serde_json::Map::new();
+                ent_obj.insert("label".to_string(), serde_json::Value::String(adopted.label().clone()));
+                ent_obj.insert("properties".to_string(), props_value);
+                ent_obj.insert("transform".to_string(), serde_json::to_value(transform)?);
+                entities_arr.push(serde_json::Value::Object(ent_obj));
+            }
+            scene_map.insert("entities".to_string(), serde_json::Value::Array(entities_arr));
+
+            // cameras
+            let mut cameras_arr: Vec<serde_json::Value> = Vec::new();
+            for (_id, (cam, _comp)) in world.query::<(&Camera, &CameraComponent)>().iter() {
+                cameras_arr.push(serde_json::json!({
+                    "label": cam.label,
+                    "data": {
+                        "eye": cam.eye.to_array(),
+                        "target": cam.target.to_array(),
+                        "up": cam.up.to_array(),
+                        "aspect": cam.aspect,
+                        "fov": cam.fov_y,
+                        "near": cam.znear,
+                        "far": cam.zfar,
+                        "yaw": cam.yaw,
+                        "pitch": cam.pitch,
+                        "speed": cam.speed,
+                        "sensitivity": cam.sensitivity
+                    }
+                }));
+            }
+            scene_map.insert("cameras".to_string(), serde_json::Value::Array(cameras_arr));
+
+            // lights
+            let mut lights_arr: Vec<serde_json::Value> = Vec::new();
+            for (_id, (_transform, _light_comp, light)) in world.query::<(&Transform, &LightComponent, &Light)>().iter() {
+                lights_arr.push(serde_json::json!({
+                    "label": light.label(),
+                }));
+            }
+            scene_map.insert("lights".to_string(), serde_json::Value::Array(lights_arr));
+
+            // current entity label
+            if let Ok(mut q) = world.query_one::<&AdoptedEntity>(entity_id) {
+                if let Some(adopted) = q.get() {
+                    scene_map.insert("current_entity".to_string(), serde_json::Value::String(adopted.label().clone()));
+                }
+            }
+
+            let serializable_input = input::SerializableInputState::from(input_state);
+            let mut payload = serde_json::Map::new();
+            payload.insert("scene".to_string(), serde_json::Value::Object(scene_map));
+            payload.insert("input".to_string(), serde_json::to_value(&serializable_input)?);
+
+            let script_data_value = serde_json::Value::Object(payload);
+            let dt_value = serde_json::Value::Number(serde_json::Number::from_f64(dt as f64).unwrap());
+
+            // call onUpdate(s, dt)
+            match self.runtime.call_function::<serde_json::Value>(Some(&module), "onUpdate", &vec![script_data_value.clone(), dt_value]) {
+                Ok(result) => {
+                    log::trace!("Called update for entity {:?}", entity_id);
+                    // delegate applying of returned data to the helper
+                    let _ = self.apply_script_data_to_world(entity_id, &result, world);
+                    // store latest returned data
+                    self.entity_script_data.insert(entity_id, result);
+                }
+                Err(e) => {
+                    log_once::error_once!("Script execution error for entity {:?}: {}", entity_id, e);
+                }
+            }
+        } else {
+            log_once::error_once!("Unable to fetch compiled scripts for entity {:?}. Script Name: {}", entity_id, script_name);
+        }
+        Ok(())
+    }
+
+    fn apply_script_data_to_world(
+        &self,
+        entity_id: hecs::Entity,
+        script_data: &serde_json::Value,
+        world: &mut World,
+    ) -> anyhow::Result<()> {
+        if let Some(obj) = script_data.as_object() {
+            if let Some(entities_val) = obj.get("entities").and_then(|v| v.as_array()) {
+                for ent in entities_val {
+                    if let Some(ent_obj) = ent.as_object() {
+                        if let Some(label) = ent_obj.get("label").and_then(|l| l.as_str()) {
+                            if let Some(target_entity) = world
+                                .query_mut::<(&AdoptedEntity,)>()
+                                .into_iter()
+                                .find_map(|(e, (adopted,))| {
+                                    if adopted.label() == label {
+                                        Some(e)
+                                    } else {
+                                        None
+                                    }
+                                }) {
+                                if let Some(transform_val) = ent_obj.get("transform") {
+                                    if let Ok(new_t) = serde_json::from_value::<Transform>(transform_val.clone()) {
+                                        if let Ok(mut tq) = world.query_one::<&mut Transform>(target_entity) {
+                                            if let Some(tref) = tq.get() {
+                                                *tref = new_t;
+                                                log::trace!("apply: updated transform for {:?}", target_entity);
+                                            }
+                                        }
+                                    } else {
+                                        log::trace!("apply: failed to deserialize transform for label '{}'", label);
+                                    }
+                                }
+                                if let Some(props_val) = ent_obj.get("properties") {
+                                    if let Ok(new_props) = serde_json::from_value::<ModelProperties>(props_val.clone()) {
+                                        if let Ok(mut pq) = world.query_one::<&mut ModelProperties>(target_entity) {
+                                            if let Some(p) = pq.get() {
+                                                *p = new_props;
+                                                log::trace!("apply: updated properties for {:?}", target_entity);
+                                            }
+                                        } else {
+                                            if let Err(e) = world.insert_one(target_entity, new_props) {
+                                                log::warn!("apply: failed to insert properties for {:?}: {}", target_entity, e);
+                                            }
+                                        }
+                                    } else {
+                                        log::trace!("apply: failed to deserialize properties for label '{}'", label);
+                                    }
+                                }
+                            } else {
+                                log::trace!("apply: no entity in world matching label '{}'", label);
+                            }
+                        }
+                    }
+                }
+            } else {
+                // No entities array: treat the object as direct payload for the single entity
+                // apply transform if present
+                if let Some(transform_value) = obj.get("transform") {
+                    if let Ok(updated_transform) = serde_json::from_value::<Transform>(transform_value.clone()) {
+                        if let Ok(mut transform_query) = world.query_one::<&mut Transform>(entity_id) {
+                            if let Some(transform) = transform_query.get() {
+                                *transform = updated_transform;
+                                log::trace!("apply: Updated transform for entity {:?}", entity_id);
+                            }
+                        }
+                    }
+                }
+                // apply properties if present (support "properties" or legacy "entity")
+                if let Some(props_value) = obj.get("properties").or_else(|| obj.get("entity")) {
+                    if let Ok(updated_properties) = serde_json::from_value::<ModelProperties>(props_value.clone()) {
+                        if let Ok(mut properties_query) = world.query_one::<&mut ModelProperties>(entity_id) {
+                            if let Some(properties) = properties_query.get() {
+                                *properties = updated_properties;
+                                log::trace!("apply: Updated properties for entity {:?}", entity_id);
+                            }
+                        } else {
+                            if let Err(e) = world.insert_one(entity_id, updated_properties) {
+                                log::warn!("apply: Failed to insert updated properties for entity {:?}: {}", entity_id, e);
+                            }
+                        }
+                    }
+                }
+
+                // cameras: if script returned camera array with full data, apply by label
+                if let Some(cameras_val) = obj.get("cameras").and_then(|v| v.as_array()) {
+                    for cam in cameras_val {
+                        if let Some(cam_obj) = cam.as_object() {
+                            if let Some(label) = cam_obj.get("label").and_then(|l| l.as_str()) {
+                                if let Some(data_obj) = cam_obj.get("data").and_then(|d| d.as_object()) {
+                                    // find camera entity by label
+                                    if let Some(cam_entity) = world
+                                        .query::<&Camera>()
+                                        .iter()
+                                        .find_map(|(e, camera)| if camera.label == label { Some(e) } else { None }) {
+                                        if let Ok(mut cam_q) = world.query_one::<&mut Camera>(cam_entity) {
+                                            if let Some(cam_struct) = cam_q.get() {
+                                                // update common camera fields if present
+                                                if let Some(eye_val) = data_obj.get("eye") {
+                                                    if let Ok(arr) = serde_json::from_value::<[f64; 3]>(eye_val.clone()) {
+                                                        cam_struct.eye = DVec3::from_array(arr);
+                                                    }
+                                                }
+                                                if let Some(target_val) = data_obj.get("target") {
+                                                    if let Ok(arr) = serde_json::from_value::<[f64; 3]>(target_val.clone()) {
+                                                        cam_struct.target = DVec3::from_array(arr);
+                                                    }
+                                                }
+                                                if let Some(up_val) = data_obj.get("up") {
+                                                    if let Ok(arr) = serde_json::from_value::<[f64; 3]>(up_val.clone()) {
+                                                        cam_struct.up = DVec3::from_array(arr);
+                                                    }
+                                                }
+                                                if let Some(aspect) = data_obj.get("aspect").and_then(|v| v.as_f64()) {
+                                                    cam_struct.aspect = aspect;
+                                                }
+                                                if let Some(fov) = data_obj.get("fov").and_then(|v| v.as_f64()) {
+                                                    cam_struct.fov_y = fov;
+                                                }
+                                                if let Some(near) = data_obj.get("near").and_then(|v| v.as_f64()) {
+                                                    cam_struct.znear = near;
+                                                }
+                                                if let Some(far) = data_obj.get("far").and_then(|v| v.as_f64()) {
+                                                    cam_struct.zfar = far;
+                                                }
+                                                if let Some(yaw) = data_obj.get("yaw").and_then(|v| v.as_f64()) {
+                                                    cam_struct.yaw = yaw;
+                                                }
+                                                if let Some(pitch) = data_obj.get("pitch").and_then(|v| v.as_f64()) {
+                                                    cam_struct.pitch = pitch;
+                                                }
+                                                if let Some(speed) = data_obj.get("speed").and_then(|v| v.as_f64()) {
+                                                    cam_struct.speed = speed;
+                                                }
+                                                if let Some(sensitivity) = data_obj.get("sensitivity").and_then(|v| v.as_f64()) {
+                                                    cam_struct.sensitivity = sensitivity;
+                                                }
+                                                log::trace!("apply: Updated camera '{}'", label);
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        } else {
+            log::trace!("apply: script_data not an object for entity {:?}", entity_id);
+        }
+
+        Ok(())
+    }
+
+    pub fn load_script_from_source(&mut self, script_name: &String, script_content: &String) -> anyhow::Result<String> {
+        let module = Module::new(&script_name, script_content);
+        
+        match self.runtime.load_module(&module) {
+            Ok(module) => {
+                self.compiled_scripts.insert(script_name.clone(), module);
+                log::info!("Loaded script: {}", script_name);
+                Ok(script_name.to_string())
+            }
+            Err(e) => {
+                log::error!("Compiling module for script [{}] returned an error: {}", script_name, e);
+                Err(e.into())
+            }
+        }
+    }
+
+    pub fn load_script(&mut self, script_path: &PathBuf) -> anyhow::Result<String> {
+        log::debug!("Reading script content");
+        let script_content = fs::read_to_string(script_path)?;
+        log::debug!("Fetching script name");
+        let script_name = script_path
+            .file_name()
+            .unwrap_or_default()
+            .to_string_lossy()
+            .to_string();
+        log::debug!("Script name: {}", script_name);
+
+        log::debug!("Creating module for typescript runtime");
+        let module = Module::new(&script_name, &script_content);
+        
+        log::debug!("Loading module");
+        match self.runtime.load_module(&module) {
+            Ok(module) => {
+                self.compiled_scripts.insert(script_name.clone(), module);
+                log::info!("Loaded script: {}", script_name);
+                Ok(script_name)
+            }
+            Err(e) => {
+                log::error!("Compiling module for script path [{}] returned an error: {}", script_path.display(), e);
+                Err(e.into())
+            }
+        }
+    }
+
+    pub fn remove_entity_script(&mut self, entity_id: hecs::Entity) {
+        self.entity_script_data.remove(&entity_id);
+    }
+
+    pub fn handle_camera_actions(&self, world: &mut hecs::World, active_camera: &mut Option<hecs::Entity>, actions: Vec<serde_json::Value>) -> anyhow::Result<()> {
+        use crate::camera::{CameraComponent, CameraType};
+        use dropbear_engine::camera::Camera;
+
+        for action_value in actions {
+            if let Ok(action) = serde_json::from_value::<crate::scripting::camera::CameraAction>(action_value) {
+                match action.action.as_str() {
+                    "switch_camera" => {
+                        if let Some(label) = action.label {
+                            let camera_entity = world
+                                .query::<&Camera>()
+                                .iter()
+                                .find(|(_, camera)| camera.label == label)
+                                .map(|(entity, _)| entity);
+
+                            if let Some(entity) = camera_entity {
+                                *active_camera = Some(entity);
+                                log::info!("Switched to camera with label: {}", label);
+                            } else {
+                                log::warn!("Camera with label '{}' not found", label);
+                            }
+                        }
+                    }
+                    "get_active_camera" => {
+                        if let Some(active_entity) = active_camera {
+                            if let Ok(camera) = world.get::<&Camera>(*active_entity) {
+                                log::info!("Active camera label: {}", camera.label);
+                            }
+                        }
+                    }
+                    "get_all_cameras" => {
+                        let labels: Vec<String> = world
+                            .query::<&Camera>()
+                            .iter()
+                            .map(|(_, camera)| camera.label.clone())
+                            .collect();
+                        log::info!("All camera labels: {:?}", labels);
+                    }
+                    "get_cameras_by_type" => {
+                        if let Some(type_str) = action.camera_type {
+                            let target_type = match type_str.as_str() {
+                                "Normal" => CameraType::Normal,
+                                "Debug" => CameraType::Debug,
+                                "Player" => CameraType::Player,
+                                _ => continue,
+                            };
+
+                            let labels: Vec<String> = world
+                                .query::<(&Camera, &CameraComponent)>()
+                                .iter()
+                                .filter_map(|(_, (camera, component))| {
+                                    if component.camera_type == target_type {
+                                        Some(camera.label.clone())
+                                    } else {
+                                        None
+                                    }
+                                })
+                                .collect();
+                            log::info!("Cameras of type {:?}: {:?}", target_type, labels);
+                        }
+                    }
+                    "manipulate_camera" => {
+                        if let (Some(label), Some(property), Some(value)) = (action.label, action.property, action.value) {
+                            let camera_entity = world
+                                .query::<&Camera>()
+                                .iter()
+                                .find(|(_, camera)| camera.label == label)
+                                .map(|(entity, _)| entity);
+
+                            if let Some(entity) = camera_entity {
+                                match property.as_str() {
+                                    "position" => {
+                                        if let Ok(pos_array) = serde_json::from_value::<[f64; 3]>(value) {
+                                            if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+                                                camera.eye = DVec3::from_array(pos_array);
+                                                log::info!("Updated camera '{}' position to {:?}", label, pos_array);
+                                            }
+                                        }
+                                    }
+                                    "target" => {
+                                        if let Ok(target_array) = serde_json::from_value::<[f64; 3]>(value) {
+                                            if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+                                                camera.target = DVec3::from_array(target_array);
+                                                log::info!("Updated camera '{}' target to {:?}", label, target_array);
+                                            }
+                                        }
+                                    }
+                                    "speed" => {
+                                        if let Some(speed) = value.as_f64() {
+                                            if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+                                                camera.speed = speed;
+                                            }
+                                            if let Ok(mut component) = world.get::<&mut CameraComponent>(entity) {
+                                                component.speed = speed;
+                                            }
+                                            log::info!("Updated camera '{}' speed to {}", label, speed);
+                                        }
+                                    }
+                                    "sensitivity" => {
+                                        if let Some(sensitivity) = value.as_f64() {
+                                            if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+                                                camera.sensitivity = sensitivity;
+                                            }
+                                            if let Ok(mut component) = world.get::<&mut CameraComponent>(entity) {
+                                                component.sensitivity = sensitivity;
+                                            }
+                                            log::info!("Updated camera '{}' sensitivity to {}", label, sensitivity);
+                                        }
+                                    }
+                                    "fov" => {
+                                        if let Some(fov) = value.as_f64() {
+                                            if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+                                                camera.fov_y = fov;
+                                            }
+                                            if let Ok(mut component) = world.get::<&mut CameraComponent>(entity) {
+                                                component.fov_y = fov;
+                                            }
+                                            log::info!("Updated camera '{}' FOV to {}", label, fov);
+                                        }
+                                    }
+                                    "yaw" => {
+                                        if let Some(yaw) = value.as_f64() {
+                                            if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+                                                camera.yaw = yaw;
+                                            }
+                                            log::info!("Updated camera '{}' yaw to {}", label, yaw);
+                                        }
+                                    }
+                                    "pitch" => {
+                                        if let Some(pitch) = value.as_f64() {
+                                            if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+                                                camera.pitch = pitch;
+                                            }
+                                            log::info!("Updated camera '{}' pitch to {}", label, pitch);
+                                        }
+                                    }
+                                    _ => {
+                                        log::warn!("Unknown camera property: {}", property);
+                                    }
+                                }
+                            } else {
+                                log::warn!("Camera with label '{}' not found for manipulation", label);
+                            }
+                        }
+                    }
+                    "get_camera_by_label" => {
+                        if let Some(label) = action.label {
+                            log::info!("Getting camera data for label: {}", label);
+                        }
+                    }
+                    "set_camera_by_label" => {
+                        if let (Some(label), Some(camera_data)) = (action.label, action.camera_data) {
+                            // Find and update the entire camera
+                            let camera_entity = world
+                                .query::<&Camera>()
+                                .iter()
+                                .find(|(_, camera)| camera.label == label)
+                                .map(|(entity, _)| entity);
+
+                            if let Some(entity) = camera_entity {
+                                if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+                                    camera.eye = camera_data.eye;
+                                    camera.target = camera_data.target;
+                                    camera.up = camera_data.up;
+                                    camera.aspect = camera_data.aspect;
+                                    camera.fov_y = camera_data.fov;
+                                    camera.znear = camera_data.near;
+                                    camera.zfar = camera_data.far;
+                                    camera.yaw = camera_data.yaw;
+                                    camera.pitch = camera_data.pitch;
+                                    camera.speed = camera_data.speed;
+                                    camera.sensitivity = camera_data.sensitivity;
+                                    log::info!("Updated complete camera data for label: {}", label);
+                                }
+                            }
+                        }
+                    }
+                    _ => {
+                        log::warn!("Unknown camera action: {}", action.action);
+                    }
+                }
+            }
+        }
+        Ok(())
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/states.rs b/eucalyptus-core/src/states.rs
new file mode 100644
index 0000000..ac814c4
--- /dev/null
+++ b/eucalyptus-core/src/states.rs
@@ -0,0 +1,985 @@
+use crate::camera::DebugCamera;
+use crate::utils::PROTO_TEXTURE;
+use dropbear_engine::starter::plane::PlaneBuilder;
+use glam::DVec3;
+use std::fmt::{Display, Formatter};
+use std::{fmt, fs};
+use std::collections::HashMap;
+use std::path::PathBuf;
+use chrono::Utc;
+use egui_dock_fork::DockState;
+use parking_lot::RwLock;
+use once_cell::sync::Lazy;
+use ron::ser::PrettyConfig;
+use serde::{Deserialize, Serialize};
+use dropbear_engine::camera::Camera;
+use dropbear_engine::entity::{AdoptedEntity, Transform};
+use dropbear_engine::graphics::Graphics;
+use dropbear_engine::lighting::{Light, LightComponent};
+use dropbear_engine::model::Model;
+use dropbear_engine::utils::{ResourceReference, ResourceReferenceType};
+use crate::camera::{CameraComponent, CameraFollowTarget, CameraType};
+
+pub static PROJECT: Lazy<RwLock<ProjectConfig>> =
+    Lazy::new(|| RwLock::new(ProjectConfig::default()));
+
+pub static RESOURCES: Lazy<RwLock<ResourceConfig>> =
+    Lazy::new(|| RwLock::new(ResourceConfig::default()));
+
+pub static SOURCE: Lazy<RwLock<SourceConfig>> = Lazy::new(|| RwLock::new(SourceConfig::default()));
+
+pub static SCENES: Lazy<RwLock<Vec<SceneConfig>>> = Lazy::new(|| RwLock::new(Vec::new()));
+
+/// The root config file, responsible for building and other metadata.
+///
+/// # Location
+/// This file is {project_name}.eucp and is located at {project_dir}/
+#[derive(Debug, Deserialize, Serialize, Default)]
+pub struct ProjectConfig {
+    pub project_name: String,
+    pub project_path: PathBuf,
+    pub date_created: String,
+    pub date_last_accessed: String,
+    #[serde(default)]
+    pub dock_layout: Option<DockState<EditorTab>>,
+    #[serde(default)]
+    pub editor_settings: EditorSettings,
+}
+
+impl ProjectConfig {
+    /// Creates a new instance of the ProjectConfig. This function is typically used when creating
+    /// a new project, with it creating new defaults for everything.
+    pub fn new(project_name: String, project_path: &PathBuf) -> Self {
+        let date_created = format!("{}", Utc::now().format("%Y-%m-%d %H:%M:%S"));
+        let date_last_accessed = format!("{}", Utc::now().format("%Y-%m-%d %H:%M:%S"));
+        // #[cfg(not(feature = "editor"))]
+        // {
+        //     let mut result = Self {
+        //         project_name,
+        //         project_path: project_path.to_path_buf(),
+        //         date_created,
+        //         date_last_accessed,
+        //     };
+        //     let _ = result.load_config_to_memory(); // TODO: Deal with later...
+        //     result
+        // }
+
+        let mut result = Self {
+            project_name,
+            project_path: project_path.to_path_buf(),
+            date_created,
+            date_last_accessed,
+            editor_settings: Default::default(),
+            dock_layout: None,
+        };
+        let _ = result.load_config_to_memory();
+        result
+    }
+
+    /// This function writes the [`ProjectConfig`] struct (and other PathBufs) to a file of the choice
+    /// under the PathBuf path parameter.
+    ///
+    /// # Parameters
+    /// * path - The root **folder** of the project.
+
+    pub fn write_to(&mut self, path: &PathBuf) -> anyhow::Result<()> {
+        self.load_config_to_memory()?;
+        self.date_last_accessed = format!("{}", Utc::now().format("%Y-%m-%d %H:%M:%S"));
+        // self.assets = Assets::walk(path);
+        let ron_str = ron::ser::to_string_pretty(&self, PrettyConfig::default())
+            .map_err(|e| anyhow::anyhow!("RON serialization error: {}", e))?;
+        let config_path = path.join(format!("{}.eucp", self.project_name.clone().to_lowercase()));
+        self.project_path = path.to_path_buf();
+
+        fs::write(&config_path, ron_str).map_err(|e| anyhow::anyhow!(e.to_string()))?;
+        Ok(())
+    }
+
+    /// This function reads from the RON and traverses down the different folders to add more information
+    /// to the ProjectConfig, such as Assets location and other stuff.
+    ///
+    /// # Parameters
+    /// * path - The root config **file** for the project
+    pub fn read_from(path: &PathBuf) -> anyhow::Result<Self> {
+        let ron_str = fs::read_to_string(path)?;
+        let mut config: ProjectConfig = ron::de::from_str(&ron_str.as_str())?;
+        config.project_path = path.parent().unwrap().to_path_buf();
+        log::info!("Loaded project!");
+        log::debug!("Loaded config info");
+        log::debug!("Updating with new content");
+        config.load_config_to_memory()?;
+        config.write_to_all()?;
+        log::debug!("Successfully updated!");
+        Ok(config)
+    }
+
+    /// This function loads a `source.eucc`, `resources.eucc` or a `{scene}.eucs` config file into memory, allowing
+    /// you to reference and load the nodes located inside them.
+    pub fn load_config_to_memory(&mut self) -> anyhow::Result<()> {
+        let project_root = PathBuf::from(&self.project_path);
+
+        // resource config
+        match ResourceConfig::read_from(&project_root) {
+            Ok(resources) => {
+                let mut cfg = RESOURCES.write();
+                *cfg = resources;
+            }
+            Err(e) => {
+                if let Some(io_err) = e.downcast_ref::<std::io::Error>() {
+                    if io_err.kind() == std::io::ErrorKind::NotFound {
+                        log::warn!("resources.eucc not found, creating default.");
+                        let default = ResourceConfig {
+                            path: project_root.join("resources"),
+                            nodes: vec![],
+                        };
+                        default.write_to(&project_root)?;
+                        {
+                            let mut cfg = RESOURCES.write();
+                            *cfg = default;
+                        }
+                    } else {
+                        log::warn!("Failed to load resources.eucc: {}", e);
+                    }
+                } else {
+                    log::warn!("Failed to load resources.eucc: {}", e);
+                }
+            }
+        }
+
+        // src config
+        let mut source_config = SOURCE.write();
+        match SourceConfig::read_from(&project_root) {
+            Ok(source) => *source_config = source,
+            Err(e) => {
+                if let Some(io_err) = e.downcast_ref::<std::io::Error>() {
+                    if io_err.kind() == std::io::ErrorKind::NotFound {
+                        log::warn!("source.eucc not found, creating default.");
+                        let default = SourceConfig {
+                            path: project_root.join("src"),
+                            nodes: vec![],
+                        };
+                        default.write_to(&project_root)?;
+                        *source_config = default;
+                    } else {
+                        log::warn!("Failed to load source.eucc: {}", e);
+                    }
+                } else {
+                    log::warn!("Failed to load source.eucc: {}", e);
+                }
+            }
+        }
+
+        // scenes
+        let mut scene_configs = SCENES.write();
+        scene_configs.clear();
+
+        // iterate through each scene file in the folder
+        let scene_folder = &project_root.join("scenes");
+
+        if !scene_folder.exists() {
+            fs::create_dir_all(scene_folder)?;
+        }
+
+        for scene_entry in fs::read_dir(scene_folder)? {
+            let scene_entry = scene_entry?;
+            let path = scene_entry.path();
+
+            if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("eucs") {
+                match SceneConfig::read_from(&path) {
+                    Ok(scene) => {
+                        log::debug!("Loaded scene: {}", scene.scene_name);
+                        scene_configs.push(scene);
+                    }
+                    Err(e) => {
+                        if let Some(io_err) = e.downcast_ref::<std::io::Error>() {
+                            if io_err.kind() == std::io::ErrorKind::NotFound {
+                                log::warn!("Scene file {:?} not found", path);
+                            } else {
+                                log::warn!("Failed to load scene file {:?}: {}", path, e);
+                            }
+                        } else {
+                            log::warn!("Failed to load scene file {:?}: {}", path, e);
+                        }
+                    }
+                }
+            }
+        }
+
+        if scene_configs.is_empty() {
+            log::info!("No scenes found, creating default scene");
+            let default_scene =
+                SceneConfig::new("Default".to_string(), scene_folder.join("default.eucs"));
+            default_scene.write_to(&project_root)?;
+            scene_configs.push(default_scene);
+        }
+
+        Ok(())
+    }
+
+    /// # Parameters
+    /// * path - The root folder of the project
+    pub fn write_to_all(&mut self) -> anyhow::Result<()> {
+        let path = PathBuf::from(self.project_path.clone());
+
+        {
+            let resources_config = RESOURCES.read();
+            resources_config.write_to(&path)?;
+        }
+
+        {
+            let source_config = SOURCE.read();
+            source_config.write_to(&path)?;
+        }
+
+        {
+            let scene_configs = SCENES.read();
+            for scene in scene_configs.iter() {
+                scene.write_to(&path)?;
+            }
+        }
+
+        self.write_to(&path)?;
+        Ok(())
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub enum Node {
+    File(File),
+    Folder(Folder),
+}
+
+#[derive(Default, Debug, Serialize, Deserialize, Clone)]
+pub struct File {
+    pub name: String,
+    pub path: PathBuf,
+    pub resource_type: Option<ResourceType>,
+}
+
+#[derive(Default, Debug, Serialize, Deserialize, Clone)]
+pub struct Folder {
+    pub name: String,
+    pub path: PathBuf,
+    pub nodes: Vec<Node>,
+}
+
+/// The type of resource
+#[derive(Debug, Serialize, Deserialize, Clone, Hash)]
+pub enum ResourceType {
+    Unknown,
+    Model,
+    Thumbnail,
+    Texture,
+    Shader,
+}
+
+impl Display for ResourceType {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        let str = match self {
+            ResourceType::Unknown => "unknown",
+            ResourceType::Model => "model",
+            ResourceType::Texture => "texture",
+            ResourceType::Shader => "shader",
+            ResourceType::Thumbnail => "thumbnail",
+        };
+        write!(f, "{}", str)
+    }
+}
+
+/// The resource config.
+#[derive(Default, Debug, Serialize, Deserialize)]
+pub struct ResourceConfig {
+    /// The path to the resource folder.
+    pub path: PathBuf,
+    /// The files and folders of the assets
+    pub nodes: Vec<Node>,
+}
+
+impl ResourceConfig {
+    /// Builds a resource path from the ProjectConfiguration's project_path (or a string)
+    #[allow(dead_code)]
+    pub fn build_path(project_path: String) -> PathBuf {
+        PathBuf::from(project_path).join("resources/resources.eucc")
+    }
+
+    /// # Parameters
+    /// - path: The root **folder** of the project
+    pub fn write_to(&self, path: &PathBuf) -> anyhow::Result<()> {
+        let resource_dir = path.join("resources");
+        let updated_config = ResourceConfig {
+            path: resource_dir.clone(),
+            nodes: collect_nodes(&resource_dir, path, vec!["thumbnails"].as_slice()),
+        };
+        let ron_str = ron::ser::to_string_pretty(&updated_config, PrettyConfig::default())
+            .map_err(|e| anyhow::anyhow!("RON serialization error: {}", e))?;
+        let config_path = path.join("resources").join("resources.eucc");
+        fs::create_dir_all(config_path.parent().unwrap())?;
+        fs::write(&config_path, ron_str).map_err(|e| anyhow::anyhow!(e.to_string()))?;
+        Ok(())
+    }
+
+    /// Updates the in-memory ResourceConfig by re-scanning the resource directory.
+    pub fn update_mem(&mut self) -> anyhow::Result<ResourceConfig> {
+        let resource_dir = self.path.clone();
+        let project_path = resource_dir.parent().unwrap_or(&resource_dir).to_path_buf();
+        let updated_config = ResourceConfig {
+            path: resource_dir.clone(),
+            nodes: collect_nodes(&resource_dir, &project_path, vec!["thumbnails"].as_slice()),
+        };
+        Ok(updated_config)
+    }
+
+    /// # Parameters
+    /// - path: The location to the **resources.eucc** file
+    pub fn read_from(path: &PathBuf) -> anyhow::Result<Self> {
+        let config_path = path.join("resources").join("resources.eucc");
+        let ron_str = fs::read_to_string(&config_path)?;
+        let config: ResourceConfig = ron::de::from_str(&ron_str)
+            .map_err(|e| anyhow::anyhow!("RON deserialization error: {}", e))?;
+        Ok(config)
+    }
+}
+
+#[derive(Default, Debug, Serialize, Deserialize, Clone)]
+pub struct SourceConfig {
+    /// The path to the resource folder.
+    pub path: PathBuf,
+    /// The files and folders of the assets
+    pub nodes: Vec<Node>,
+}
+
+impl SourceConfig {
+    /// Builds a source path from the ProjectConfiguration's project_path (or a string)
+    #[allow(dead_code)]
+    pub fn build_path(project_path: String) -> PathBuf {
+        PathBuf::from(project_path).join("src/source.eucc")
+    }
+
+    /// # Parameters
+    /// - path: The root **folder** of the project
+    pub fn write_to(&self, path: &PathBuf) -> anyhow::Result<()> {
+        let resource_dir = path.join("src");
+        let updated_config = SourceConfig {
+            path: resource_dir.clone(),
+            nodes: collect_nodes(&resource_dir, path, vec!["scripts"].as_slice()),
+        };
+
+        let ron_str = ron::ser::to_string_pretty(&updated_config, PrettyConfig::default())
+            .map_err(|e| anyhow::anyhow!("RON serialisation error: {}", e))?;
+        let config_path = path.join("src").join("source.eucc");
+        fs::create_dir_all(config_path.parent().unwrap())?;
+        fs::write(&config_path, ron_str).map_err(|e| anyhow::anyhow!(e.to_string()))?;
+        Ok(())
+    }
+
+    /// # Parameters
+    /// - path: The location to the **source.eucc** file
+    pub fn read_from(path: &PathBuf) -> anyhow::Result<Self> {
+        let config_path = path.join("src").join("source.eucc");
+        let ron_str = fs::read_to_string(&config_path)?;
+        let config: SourceConfig = ron::de::from_str(&ron_str)
+            .map_err(|e| anyhow::anyhow!("RON deserialization error: {}", e))?;
+        Ok(config)
+    }
+}
+
+fn collect_nodes(dir: &PathBuf, project_path: &PathBuf, exclude_list: &[&str]) -> Vec<Node> {
+    let mut nodes = Vec::new();
+    if let Ok(entries) = fs::read_dir(dir) {
+        for entry in entries.flatten() {
+            let entry_path = entry.path();
+            let name = entry_path
+                .file_name()
+                .unwrap_or_default()
+                .to_string_lossy()
+                .to_string();
+
+            if entry_path.is_dir() && exclude_list.iter().any(|ex| &ex.to_string() == &name) {
+                log::debug!("Skipped past folder {:?}", name);
+                continue;
+            }
+
+            if entry_path.is_dir() {
+                let folder_nodes = collect_nodes(&entry_path, project_path, exclude_list);
+                nodes.push(Node::Folder(Folder {
+                    name,
+                    path: entry_path.clone(),
+                    nodes: folder_nodes,
+                }));
+            } else {
+                let parent_folder = entry_path
+                    .parent()
+                    .and_then(|p| p.file_name())
+                    .map(|n| n.to_string_lossy().to_lowercase())
+                    .unwrap_or_default();
+
+                let resource_type = if parent_folder.contains("model") {
+                    Some(ResourceType::Model)
+                } else if parent_folder.contains("texture") {
+                    Some(ResourceType::Texture)
+                } else if parent_folder.contains("shader") {
+                    Some(ResourceType::Shader)
+                } else {
+                    Some(ResourceType::Unknown)
+                };
+
+                nodes.push(Node::File(File {
+                    name,
+                    path: entry_path.clone(),
+                    resource_type,
+                }));
+            }
+        }
+    }
+    nodes
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, Hash)]
+pub enum EntityNode {
+    Entity {
+        id: hecs::Entity,
+        name: String,
+    },
+    Script {
+        name: String,
+        path: PathBuf,
+    },
+    Light {
+        id: hecs::Entity,
+        name: String,
+    },
+    Camera {
+        id: hecs::Entity,
+        name: String,
+        camera_type: CameraType,
+    },
+    Group {
+        name: String,
+        children: Vec<EntityNode>,
+        collapsed: bool,
+    },
+}
+
+#[derive(Default, Debug, Serialize, Deserialize, Clone)]
+pub struct ScriptComponent {
+    pub name: String,
+    pub path: PathBuf,
+}
+
+impl EntityNode {
+    pub fn from_world(world: &hecs::World) -> Vec<Self> {
+        let mut nodes = Vec::new();
+        let mut handled = std::collections::HashSet::new();
+
+        for (id, (script, _transform, adopted)) in world
+            .query::<(
+                &ScriptComponent,
+                &dropbear_engine::entity::Transform,
+                &dropbear_engine::entity::AdoptedEntity,
+            )>()
+            .iter()
+        {
+            let name = adopted.model().label.clone();
+
+            // grouped entity (entity and script)
+            nodes.push(EntityNode::Group {
+                name: name.clone(),
+                children: vec![
+                    EntityNode::Entity {
+                        id,
+                        name: name.clone(),
+                    },
+                    EntityNode::Script {
+                        name: script.name.clone(),
+                        path: script.path.clone(),
+                    },
+                ],
+                collapsed: false,
+            });
+            handled.insert(id);
+        }
+
+        // single entity
+        for (id, (_, adopted)) in world
+            .query::<(
+                &dropbear_engine::entity::Transform,
+                &dropbear_engine::entity::AdoptedEntity,
+            )>()
+            .iter()
+        {
+            if handled.contains(&id) {
+                continue;
+            }
+            let name = adopted.model().label.clone();
+
+            nodes.push(EntityNode::Entity { id, name });
+        }
+
+        // lights
+        for (id, (_transform, _light_comp, light)) in world
+            .query::<(&dropbear_engine::entity::Transform, &LightComponent, &Light)>()
+            .iter()
+        {
+            if handled.contains(&id) {
+                continue;
+            }
+            nodes.push(EntityNode::Light {
+                id,
+                name: light.label().to_string(),
+            });
+            handled.insert(id);
+        }
+
+        for (entity, (camera, component)) in world
+            .query::<(&Camera, &CameraComponent)>()
+            .iter()
+        {
+            if world.get::<&AdoptedEntity>(entity).is_err() {
+                nodes.push(EntityNode::Camera {
+                    id: entity,
+                    name: camera.label.clone(),
+                    camera_type: component.camera_type,
+                });
+            }
+        }
+
+        nodes
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct CameraConfig {
+    pub label: String,
+    pub camera_type: CameraType,
+
+    pub position: [f64; 3],
+    pub target: [f64; 3],
+    pub up: [f64; 3],
+    pub aspect: f64,
+    pub fov: f32,
+    pub near: f32,
+    pub far: f32,
+
+    pub speed: f32,
+    pub sensitivity: f32,
+
+    pub follow_target_entity_label: Option<String>,
+    pub follow_offset: Option<[f64; 3]>,
+}
+
+impl Default for CameraConfig {
+    fn default() -> Self {
+        let default = CameraComponent::new();
+        Self {
+            position: [0.0, 1.0, 2.0],
+            target: [0.0, 0.0, 0.0],
+            up: [0.0, 1.0, 0.0],
+            aspect: 16.0 / 9.0,
+            fov: 45.0,
+            near: 0.1,
+            far: 100.0,
+            follow_target_entity_label: None,
+            follow_offset: None,
+            label: String::new(),
+            camera_type: CameraType::Normal,
+            speed: default.speed as f32,
+            sensitivity: default.sensitivity as f32,
+        }
+    }
+}
+
+impl CameraConfig {
+    pub fn from_ecs_camera(camera: &Camera, component: &CameraComponent, follow_target: Option<&CameraFollowTarget>) -> Self {
+        Self {
+            position: camera.position().to_array(),
+            target: camera.target.to_array(),
+            label: camera.label.clone(),
+            camera_type: component.camera_type,
+            up: camera.up.to_array(),
+            aspect: camera.aspect,
+            fov: camera.fov_y as f32,
+            near: camera.znear as f32,
+            far: camera.zfar as f32,
+            speed: component.speed as f32,
+            sensitivity: component.sensitivity as f32,
+            follow_target_entity_label: if let Some(target) = follow_target { Some(target.follow_target.clone()) } else { None },
+            follow_offset: if let Some(target) = follow_target { Some(target.offset.to_array()) } else { None },
+        }
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone, Default)]
+pub struct SceneEntity {
+    pub model_path: ResourceReference,
+    pub label: String,
+    pub transform: Transform,
+    pub properties: ModelProperties,
+    pub script: Option<ScriptComponent>,
+
+    #[serde(skip)]
+    #[allow(dead_code)]
+    pub entity_id: Option<hecs::Entity>,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
+pub struct ModelProperties {
+    pub custom_properties: HashMap<String, PropertyValue>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub enum PropertyValue {
+    String(String),
+    Int(i64),
+    Float(f64),
+    Bool(bool),
+    Vec3([f32; 3]),
+}
+
+impl ModelProperties {
+    pub fn new() -> Self {
+        Self {
+            custom_properties: HashMap::new(),
+        }
+    }
+
+    pub fn set_property(&mut self, key: String, value: PropertyValue) {
+        self.custom_properties.insert(key, value);
+    }
+
+    pub fn get_property(&self, key: &str) -> Option<&PropertyValue> {
+        self.custom_properties.get(key)
+    }
+}
+
+impl Default for ModelProperties {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+#[derive(Default, Debug, Serialize, Deserialize, Clone)]
+pub struct SceneConfig {
+    pub scene_name: String,
+    pub entities: Vec<SceneEntity>,
+    pub cameras: Vec<CameraConfig>,
+    pub lights: Vec<LightConfig>,
+    // todo later
+    // pub settings: SceneSettings,
+    #[serde(skip)]
+    pub path: PathBuf,
+}
+
+impl SceneConfig {
+    /// Creates a new instance of the scene config
+    pub fn new(scene_name: String, path: PathBuf) -> Self {
+        Self {
+            scene_name,
+            path,
+            entities: Vec::new(),
+            cameras: Vec::new(),
+            lights: Vec::new(),
+        }
+    }
+
+    /// Write the scene config to a .eucs file
+    pub fn write_to(&self, project_path: &PathBuf) -> anyhow::Result<()> {
+        let ron_str = ron::ser::to_string_pretty(&self, PrettyConfig::default())
+            .map_err(|e| anyhow::anyhow!("RON serialization error: {}", e))?;
+
+        let scenes_dir = project_path.join("scenes");
+        fs::create_dir_all(&scenes_dir)?;
+
+        let config_path = scenes_dir.join(format!("{}.eucs", self.scene_name));
+        fs::write(&config_path, ron_str).map_err(|e| anyhow::anyhow!(e.to_string()))?;
+        Ok(())
+    }
+
+    /// Read a scene config from a .eucs file
+    pub fn read_from(scene_path: &PathBuf) -> anyhow::Result<Self> {
+        let ron_str = fs::read_to_string(scene_path)?;
+        let mut config: SceneConfig = ron::de::from_str(&ron_str)
+            .map_err(|e| anyhow::anyhow!("RON deserialization error: {}", e))?;
+
+        config.path = scene_path.clone();
+        Ok(config)
+    }
+
+    pub fn load_into_world(
+        &self,
+        world: &mut hecs::World,
+        graphics: &Graphics,
+    ) -> anyhow::Result<hecs::Entity> {
+        log::info!(
+            "Loading scene [{}], clearing world with {} entities",
+            self.scene_name,
+            world.len()
+        );
+        world.clear();
+
+        #[allow(unused_variables)]
+        let project_config = if cfg!(feature = "editor") {
+            let cfg = PROJECT.read();
+            {
+                cfg.project_path.clone()
+            }
+        } else {
+            log::debug!("Not using the editor feature, returning empty pathbuffer");
+            PathBuf::new()
+        };
+
+        log::info!("World cleared, now has {} entities", world.len());
+
+        for entity_config in &self.entities {
+            log::debug!("Loading entity: {}", entity_config.label);
+            match &entity_config.model_path.ref_type {
+                ResourceReferenceType::File(reference) => {
+                    let path: PathBuf = {
+                        if cfg!(feature = "editor") {
+                            log::debug!("Using feature editor");
+                            entity_config.model_path.to_project_path(project_config.clone())
+                                .ok_or_else(|| anyhow::anyhow!("Unable to convert resource reference [{}] to project path", reference))?
+                        } else {
+                            log::debug!("Using feature data-only");
+                            entity_config.model_path.to_executable_path()?
+                        }
+                    };
+                    log::debug!("Path for entity {} is {} from reference {}", entity_config.label, path.display(), reference);
+                    let adopted = AdoptedEntity::new(
+                        graphics,
+                        &path,
+                        Some(&entity_config.label),
+                    )?;
+
+                    let transform = entity_config.transform;
+
+                    if let Some(script_config) = &entity_config.script {
+                        let script = ScriptComponent {
+                            name: script_config.name.clone(),
+                            path: script_config.path.clone(),
+                        };
+                        world.spawn((adopted, transform, script, entity_config.properties.clone()));
+                    } else {
+                        world.spawn((adopted, transform, entity_config.properties.clone()));
+                    }
+                    log::debug!("Loaded!");
+                }
+                ResourceReferenceType::Bytes(bytes) => {
+                    log::info!("Loading entity from bytes [Len: {}]", bytes.len());
+                    let bytes = bytes.to_owned();
+
+                    let model = Model::load_from_memory(graphics, bytes, Some(&entity_config.label))?;
+                    let transform = entity_config.transform;
+
+                    let adopted = AdoptedEntity::adopt(
+                        graphics,
+                        model,
+                        Some(&entity_config.label),
+                    );
+
+                    if let Some(script_config) = &entity_config.script {
+                        let script = ScriptComponent {
+                            name: script_config.name.clone(),
+                            path: script_config.path.clone(),
+                        };
+                        world.spawn((adopted, transform, script, entity_config.properties.clone()));
+                    } else {
+                        world.spawn((adopted, transform, entity_config.properties.clone()));
+                    }
+                    log::debug!("Loaded!");
+                }
+                ResourceReferenceType::Plane => {
+                    let width = entity_config.properties.custom_properties.get("width").ok_or_else(|| anyhow::anyhow!("Entity has no width property"))?;
+                    let width = match width {
+                        PropertyValue::Float(width) => width,
+                        _ => panic!("Entity has a width property that is not a float"),
+                    };
+                    let height = entity_config.properties.custom_properties.get("height").ok_or_else(|| anyhow::anyhow!("Entity has no height property"))?;
+                    let height = match height {
+                        PropertyValue::Float(height) => height,
+                        _ => panic!("Entity has a height property that is not a float"),
+                    };
+                    let tiles_x = entity_config.properties.custom_properties.get("tiles_x").ok_or_else(|| anyhow::anyhow!("Entity has no tiles_x property"))?;
+                    let tiles_x = match tiles_x {
+                        PropertyValue::Int(tiles_x) => tiles_x,
+                        _ => panic!("Entity has a tiles_x property that is not an int"),
+                    };
+                    let tiles_z = entity_config.properties.custom_properties.get("tiles_z").ok_or_else(|| anyhow::anyhow!("Entity has no tiles_z property"))?;
+                    let tiles_z = match tiles_z {
+                        PropertyValue::Int(tiles_z) => tiles_z,
+                        _ => panic!("Entity has a tiles_z property that is not an int"),
+                    };
+
+                    let plane = PlaneBuilder::new().with_size(*width as f32, *height as f32).with_tiles(*tiles_x as u32, *tiles_z as u32).build(graphics, PROTO_TEXTURE, Some(entity_config.label.clone().as_str()))?;
+                    let transform = entity_config.transform;
+
+                    if let Some(script_config) = &entity_config.script {
+                        let script = ScriptComponent {
+                            name: script_config.name.clone(),
+                            path: script_config.path.clone(),
+                        };
+                        world.spawn((plane, transform, script, entity_config.properties.clone()));
+                    } else {
+                        world.spawn((plane, transform, entity_config.properties.clone()));
+                    }
+                }
+                ResourceReferenceType::None => panic!("Entity has a resource reference of None, which cannot be loaded or referenced"),
+            }
+        }
+
+        for light_config in &self.lights {
+            log::debug!("Loading light: {}", light_config.label);
+
+            let light = Light::new(
+                graphics,
+                &light_config.light_component,
+                &light_config.transform,
+                Some(&light_config.label),
+            );
+
+            world.spawn((light_config.light_component.clone(), light_config.transform, light, ModelProperties::default()));
+        }
+
+        for camera_config in &self.cameras {
+            log::debug!("Loading camera {} of type {:?}", camera_config.label, camera_config.camera_type);
+
+            let camera = Camera::new(
+                graphics,
+                DVec3::from_array(camera_config.position),
+                DVec3::from_array(camera_config.target),
+                DVec3::from_array(camera_config.up),
+                camera_config.aspect,
+                camera_config.fov as f64,
+                camera_config.near as f64,
+                camera_config.far as f64,
+                camera_config.speed as f64,
+                camera_config.sensitivity as f64,
+                Some(&camera_config.label),
+            );
+
+            let component = CameraComponent {
+                speed: camera_config.speed as f64,
+                sensitivity: camera_config.sensitivity as f64,
+                fov_y: camera_config.fov as f64,
+                camera_type: camera_config.camera_type,
+            };
+
+            if let (Some(target_label), Some(offset)) = (&camera_config.follow_target_entity_label, &camera_config.follow_offset) {
+                let follow_target = CameraFollowTarget {
+                    follow_target: target_label.clone(),
+                    offset: DVec3::from_array(*offset),
+                };
+                world.spawn((camera, component, follow_target));
+            } else {
+                world.spawn((camera, component));
+            }
+        }
+
+        if world.query::<(&LightComponent, &Light)>().iter().next().is_none() {
+            log::info!("No lights in scene, spawning default light");
+            let default_transform = Transform {
+                position: glam::DVec3::new(2.0, 4.0, 2.0),
+                ..Default::default()
+            };
+            let default_component = LightComponent::directional(glam::DVec3::ONE, 1.0);
+            let default_light = Light::new(graphics, &default_component, &default_transform, Some("Default Light"));
+            world.spawn((default_component, default_transform, default_light, ModelProperties::default()));
+        }
+
+        log::info!("Loaded {} entities, {} lights and {} cameras", self.entities.len(), self.lights.len(), self.cameras.len());
+        #[cfg(feature = "editor")]
+        {
+            // Editor mode - look for debug camera, create one if none exists
+            let debug_camera = world
+                .query::<(&Camera, &CameraComponent)>()
+                .iter()
+                .find_map(|(entity, (_, component))| {
+                    if matches!(component.camera_type, CameraType::Debug) {
+                        Some(entity)
+                    } else {
+                        None
+                    }
+                });
+
+            if let Some(camera_entity) = debug_camera {
+                log::info!("Using existing debug camera for editor");
+                Ok(camera_entity)
+            } else {
+                log::info!("No debug camera found, creating viewport camera for editor");
+                let camera = Camera::predetermined(graphics, Some("Viewport Camera"));
+                let component = DebugCamera::new();
+                let camera_entity = world.spawn((camera, component));
+                Ok(camera_entity)
+            }
+        }
+
+        #[cfg(not(feature = "editor"))]
+        {
+            // Runtime mode - look for player camera, panic if none exists
+            let player_camera = world
+                .query::<(&Camera, &CameraComponent)>()
+                .iter()
+                .find_map(|(entity, (_, component))| {
+                    if matches!(component.camera_type, CameraType::Player) {
+                        Some(entity)
+                    } else {
+                        None
+                    }
+                });
+
+            if let Some(camera_entity) = player_camera {
+                log::info!("Using player camera for runtime");
+                Ok(camera_entity)
+            } else {
+                panic!("Runtime mode requires a player camera, but none was found in the scene!");
+            }
+        }
+    }
+}
+
+#[derive(bincode::Decode, bincode::Encode, serde::Serialize, serde::Deserialize, Debug)]
+pub struct RuntimeData {
+    #[bincode(with_serde)]
+    pub project_config: ProjectConfig,
+    #[bincode(with_serde)]
+    pub source_config: SourceConfig,
+    #[bincode(with_serde)]
+    pub scene_data: Vec<SceneConfig>,
+    #[bincode(with_serde)]
+    pub scripts: HashMap<String, String>, // name, script_content
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct LightConfig {
+    pub label: String,
+    pub transform: Transform,
+    pub light_component: LightComponent,
+    pub enabled: bool,
+
+    #[serde(skip)]
+    pub entity_id: Option<hecs::Entity>,
+}
+
+impl Default for LightConfig {
+    fn default() -> Self {
+        Self {
+            label: "New Light".to_string(),
+            transform: Transform::default(),
+            light_component: LightComponent::default(),
+            enabled: true,
+            entity_id: None,
+        }
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone, Default)]
+pub struct EditorSettings {
+    pub is_debug_menu_shown: bool,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+pub enum EditorTab {
+    AssetViewer,       // bottom side,
+    ResourceInspector, // left side,
+    ModelEntityList,   // right side,
+    Viewport,          // middle,
+}
diff --git a/eucalyptus-core/src/utils.rs b/eucalyptus-core/src/utils.rs
new file mode 100644
index 0000000..bee0716
--- /dev/null
+++ b/eucalyptus-core/src/utils.rs
@@ -0,0 +1,48 @@
+use dropbear_engine::entity::Transform;
+use std::path::PathBuf;
+
+use crate::states::{ModelProperties, Node};
+
+pub const PROTO_TEXTURE: &[u8] = include_bytes!("../../resources/proto.png");
+
+pub fn search_nodes_recursively<'a, F>(nodes: &'a [Node], matcher: &F, results: &mut Vec<&'a Node>)
+where
+    F: Fn(&Node) -> bool,
+{
+    for node in nodes {
+        if matcher(node) {
+            results.push(node);
+        }
+        match node {
+            Node::File(_) => {}
+            Node::Folder(folder) => {
+                search_nodes_recursively(&folder.nodes, matcher, results);
+            }
+        }
+    }
+}
+
+/// Progress events for project creation
+pub enum ProjectProgress {
+    Step {
+        _progress: f32,
+        _message: String,
+    },
+    #[allow(dead_code)] // idk why its giving me this warning :(
+    Error(String),
+    Done,
+}
+
+pub enum ViewportMode {
+    None,
+    CameraMove,
+    Gizmo,
+}
+
+#[derive(Clone, Debug)]
+pub struct PendingSpawn {
+    pub asset_path: PathBuf,
+    pub asset_name: String,
+    pub transform: Transform,
+    pub properties: ModelProperties,
+}
diff --git a/eucalyptus-editor/Cargo.toml b/eucalyptus-editor/Cargo.toml
new file mode 100644
index 0000000..1bf1c2a
--- /dev/null
+++ b/eucalyptus-editor/Cargo.toml
@@ -0,0 +1,48 @@
+[package]
+name = "eucalyptus-editor"
+version.workspace = true
+edition.workspace = true
+license-file.workspace = true
+repository.workspace = true
+readme.workspace = true
+
+[dependencies]
+eucalyptus-core = { path = "../eucalyptus-core", features = ["editor"] }
+anyhow.workspace = true
+app_dirs2.workspace = true
+bincode.workspace = true
+bytemuck.workspace = true
+dropbear-engine.workspace = true
+egui-toast-fork.workspace = true
+egui.workspace = true
+egui_dnd.workspace = true
+egui_dock-fork.workspace = true
+egui_extras.workspace = true
+gilrs.workspace = true
+git2 = { workspace = true, features = ["vendored-openssl"]}
+glam.workspace = true
+hecs.workspace = true
+log.workspace = true
+log-once.workspace = true
+model_to_image.workspace = true
+open.workspace = true
+parking_lot.workspace = true
+transform-gizmo-egui.workspace = true
+wgpu.workspace = true
+winit.workspace = true
+clap.workspace = true
+walkdir.workspace = true
+zip.workspace = true
+
+[target.'cfg(not(target_os = "android"))'.dependencies]
+rfd.workspace = true
+
+[features]
+default = ["editor"]
+editor = ["eucalyptus-core/editor"]
+
+[build-dependencies]
+anyhow = "1.0"
+app_dirs2 = "2.5"
+reqwest = { version = "0.12", features = ["blocking"] }
+zip = "4.3"
\ No newline at end of file
diff --git a/eucalyptus-editor/build.rs b/eucalyptus-editor/build.rs
new file mode 100644
index 0000000..8966f89
--- /dev/null
+++ b/eucalyptus-editor/build.rs
@@ -0,0 +1,67 @@
+use std::fs::{self, File};
+use std::io::Cursor;
+
+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 response = reqwest::blocking::get(repo_zip_url)
+        .map_err(|e| anyhow::anyhow!("Failed to download repo zip: {}", e))?
+        .bytes()
+        .map_err(|e| anyhow::anyhow!("Failed to read zip bytes: {}", e))?;
+
+    let reader = Cursor::new(response);
+    let mut zip = zip::ZipArchive::new(reader)
+        .map_err(|e| anyhow::anyhow!("Failed to read zip archive: {}", e))?;
+
+    let app_info = app_dirs2::AppInfo {
+        name: "Eucalyptus",
+        author: "4tkbytes",
+    };
+    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))?;
+
+    fs::create_dir_all(&app_data_dir)
+        .map_err(|e| anyhow::anyhow!("Failed to create app data directory: {}", e))?;
+
+    let resource_prefix = "dropbear-main/resources/";
+    let mut found_resource = false;
+    for i in 0..zip.len() {
+        let mut file = zip.by_index(i).unwrap();
+        let name = file.name();
+
+        if name.starts_with(resource_prefix) && !name.ends_with('/') {
+            found_resource = true;
+            let rel_path = &name[resource_prefix.len()..];
+            let rel_path = rel_path.strip_prefix('/').unwrap_or(rel_path);
+            let dest_path = app_data_dir.join(rel_path);
+
+            if let Some(parent) = dest_path.parent() {
+                fs::create_dir_all(parent)
+                    .map_err(|e| anyhow::anyhow!("Failed to create parent directory: {}", e))?;
+            }
+
+            println!("Copying {} to {:?}", name, dest_path);
+
+            let mut outfile = File::create(&dest_path)
+                .map_err(|e| anyhow::anyhow!("Failed to create file: {}", e))?;
+            std::io::copy(&mut file, &mut outfile)
+                .map_err(|e| anyhow::anyhow!("Failed to copy file: {}", e))?;
+        }
+    }
+
+    if !found_resource {
+        return Err(anyhow::anyhow!(
+            "No resources folder found in the github repository [4tkbytes/dropbear] :("
+        ));
+    }
+
+    // fuck you windows :(
+    #[cfg(target_os = "windows")]
+    {
+        println!("cargo:rustc-link-arg=/FORCE:MULTIPLE");
+        println!("cargo:rustc-link-arg=/NODEFAULTLIB:libcmt.lib");
+    }
+
+    println!("cargo:rerun-if-changed=build.rs");
+    Ok(())
+}
diff --git a/eucalyptus-editor/src/build/mod.rs b/eucalyptus-editor/src/build/mod.rs
new file mode 100644
index 0000000..e7f6a31
--- /dev/null
+++ b/eucalyptus-editor/src/build/mod.rs
@@ -0,0 +1,469 @@
+use std::{collections::HashMap, fs, path::PathBuf, process::Command};
+
+use clap::ArgMatches;
+use zip::write::SimpleFileOptions;
+
+use eucalyptus_core::states::{ProjectConfig, RuntimeData, SCENES, SOURCE};
+
+pub fn package(project_path: PathBuf, _sub_matches: &ArgMatches) -> anyhow::Result<()> {
+    if !project_path.exists() {
+        return Err(anyhow::anyhow!("Unable to locate project config file"));
+    }
+
+    let build_dir = project_path.parent().ok_or(anyhow::anyhow!("Unable to get parent"))?.join("build");
+
+    // check health
+    println!("Checking health (checking if commands exist)");
+    health()?;
+    println!("Health check completed!");
+
+    let clone_dir = build_dir.join("redback-runtime");
+    
+    if clone_dir.exists() {
+        println!("Repository directory exists, checking for updates...");
+        if should_update_repository(&clone_dir)? {
+            println!("Repository has changes or is outdated, removing and re-cloning...");
+            std::fs::remove_dir_all(&clone_dir)?;
+            clone_repository(&build_dir)?;
+        } else {
+            println!("Repository is up to date, skipping clone");
+        }
+    } else {
+        println!("Cloning repository");
+        clone_repository(&build_dir)?;
+    }
+
+    let project_config = ProjectConfig::read_from(&project_path)?;
+    let project_name = project_config.project_name.clone();
+
+    // cd into redback-runtime folder and compile redback-runtime using cargo
+    let runtime_dir = build_dir.join("redback-runtime");
+    if !runtime_dir.exists() {
+        return Err(anyhow::anyhow!("redback-runtime directory not found after cloning"));
+    }
+
+    let cargo_toml_path = runtime_dir.join("Cargo.toml");
+    if cargo_toml_path.exists() {
+        let cargo_toml_content = std::fs::read_to_string(&cargo_toml_path)?;
+        let modified_content = cargo_toml_content.replace(
+            r#"name = "redback-runtime""#, 
+            &format!(r#"name = "{}""#, project_name)
+        );
+        std::fs::write(&cargo_toml_path, modified_content)?;
+        println!("Updated Cargo.toml with project name: {}", project_name);
+    }
+
+    println!("Building {} for release", project_name);
+    let mut cargo_build = Command::new("cargo")
+        .args(&["build", "--release"])
+        .current_dir(&runtime_dir)
+        .stdout(std::process::Stdio::inherit())
+        .stderr(std::process::Stdio::inherit())
+        .spawn()?;
+
+    let exit_status = cargo_build.wait()?;
+
+    if !exit_status.success() {
+        return Err(anyhow::anyhow!("Failed to build {}", project_name));
+    }
+    println!("{} built successfully!", project_name);
+
+    let target_dir = runtime_dir.join("target").join("release");
+    let exe_name = if cfg!(target_os = "windows") {
+        format!("{}.exe", project_name)
+    } else {
+        project_name.clone()
+    };
+    
+    let built_exe = target_dir.join(&exe_name);
+    if !built_exe.exists() {
+        return Err(anyhow::anyhow!("Built executable not found at: {}", built_exe.display()));
+    }
+
+    println!("Building project data (.eupak file)");
+    build(project_path.clone())?;
+
+    let output_dir = project_path.parent().ok_or(anyhow::anyhow!("Unable to get parent"))?.join("build").join("package");
+    std::fs::create_dir_all(&output_dir)?;
+
+    let output_exe = output_dir.join(&exe_name);
+    
+    println!("Copying executable to: {}", output_exe.display());
+    std::fs::copy(&built_exe, &output_exe)?;
+
+    let eupak_source = project_path.parent().ok_or(anyhow::anyhow!("Unable to get parent"))?.join("build").join("output").join(format!("{}.eupak", project_name));
+    let eupak_dest = output_dir.join(format!("{}.eupak", project_name));
+    
+    if !eupak_source.exists() {
+        return Err(anyhow::anyhow!("Expected .eupak file not found at: {}", eupak_source.display()));
+    }
+    
+    println!("Copying .eupak file to: {}", eupak_dest.display());
+    std::fs::copy(&eupak_source, &eupak_dest)?;
+
+    let project_resources = project_path.parent().ok_or(anyhow::anyhow!("Unable to get parent"))?.join("resources");
+    if project_resources.exists() {
+        println!("Copying resources folder...");
+        let output_resources = output_dir.join("resources");
+        copy_resources_folder(&project_resources, &output_resources)?;
+    }
+
+    #[cfg(unix)]
+    {
+        use std::os::unix::fs::PermissionsExt;
+        let mut perms = std::fs::metadata(&output_exe)?.permissions();
+        perms.set_mode(0o755); // rwxr-xr-x
+        std::fs::set_permissions(&output_exe, perms)?;
+    }
+
+    copy_system_libraries(&output_dir)?;
+
+    println!("Creating zip package...");
+    let zip_path = project_path.parent().ok_or(anyhow::anyhow!("Unable to get parent"))?.join("build").join(format!("{}.zip", project_name));
+    create_zip_package(&output_dir, &zip_path)?;
+
+    println!("Cleaning up temporary files");
+    if build_dir.join("redback-runtime").exists() {
+        std::fs::remove_dir_all(build_dir.join("redback-runtime"))?;
+    }
+    if build_dir.join("output").exists() {
+        std::fs::remove_dir_all(build_dir.join("output"))?;
+    }
+
+    println!("\n✓ Package completed successfully!");
+    println!("Output directory: {}", output_dir.display());
+    println!("Zip package: {}", zip_path.display());
+    println!("Executable: {}", exe_name);
+    println!("Data file: {}.eupak", project_name);
+
+    Ok(())
+}
+
+fn clone_repository(build_dir: &PathBuf) -> anyhow::Result<()> {
+    git2::build::RepoBuilder::new()
+        .clone("https://github.com/4tkbytes/redback-runtime", &build_dir.join("redback-runtime"))?;
+    println!("Repository cloned successfully!");
+    Ok(())
+}
+
+fn should_update_repository(repo_dir: &PathBuf) -> anyhow::Result<bool> {
+    let repo = match git2::Repository::open(repo_dir) {
+        Ok(repo) => repo,
+        Err(_) => {
+            return Ok(true);
+        }
+    };
+
+    let statuses = repo.statuses(None)?;
+    if !statuses.is_empty() {
+        println!("Found local changes in repository");
+        return Ok(true);
+    }
+
+    let mut remote = repo.find_remote("origin")?;
+    remote.fetch(&["refs/heads/*:refs/remotes/origin/*"], None, None)?;
+
+    let head = repo.head()?.target().ok_or(anyhow::anyhow!("No HEAD commit"))?;
+    
+    let remote_ref = if let Ok(remote_main) = repo.find_reference("refs/remotes/origin/main") {
+        remote_main
+    } else if let Ok(remote_master) = repo.find_reference("refs/remotes/origin/master") {
+        remote_master
+    } else {
+        return Ok(true);
+    };
+
+    let remote_commit = remote_ref.target().ok_or(anyhow::anyhow!("No remote commit"))?;
+
+    Ok(head != remote_commit)
+}
+
+fn copy_resources_folder(src: &PathBuf, dest: &PathBuf) -> anyhow::Result<()> {
+    std::fs::create_dir_all(dest)?;
+
+    for entry in fs::read_dir(src)? {
+        let entry = entry?;
+        let src_path = entry.path();
+        let file_name = entry.file_name();
+
+        if file_name == "resources.eucc" {
+            continue;
+        }
+
+        let dest_path = dest.join(&file_name);
+
+        if src_path.is_dir() {
+            copy_resources_folder(&src_path, &dest_path)?;
+        } else {
+            std::fs::copy(&src_path, &dest_path)?;
+        }
+    }
+
+    Ok(())
+}
+
+fn copy_system_libraries(output_dir: &PathBuf) -> anyhow::Result<()> {
+    #[cfg(target_os = "windows")]
+    {
+        let dll_paths = vec![
+            "C:\\vcpkg\\installed\\x64-windows\\bin\\assimp-vc143-mt.dll",
+            "C:\\vcpkg\\installed\\x64-windows\\bin\\assimp.dll",
+            "C:\\Program Files\\Assimp\\bin\\assimp.dll",
+            "C:\\Program Files (x86)\\Assimp\\bin\\assimp.dll",
+        ];
+
+        for dll_path in dll_paths {
+            if std::path::Path::new(dll_path).exists() {
+                let dll_name = std::path::Path::new(dll_path).file_name().unwrap();
+                let dest = output_dir.join(dll_name);
+                std::fs::copy(dll_path, dest)?;
+                println!("Copied system library: {}", dll_name.to_string_lossy());
+                break;
+            }
+        }
+    }
+
+    #[cfg(any(target_os = "linux", target_os = "macos"))]
+    {
+        let lib_dir = output_dir.join("lib");
+        std::fs::create_dir_all(&lib_dir)?;
+
+        let lib_paths = vec![
+            "/usr/lib/libassimp.so",
+            "/usr/lib/x86_64-linux-gnu/libassimp.so",
+            "/usr/lib64/libassimp.so",
+            "/usr/local/lib/libassimp.so",
+            "/opt/homebrew/lib/libassimp.dylib",
+            "/usr/local/lib/libassimp.dylib",
+        ];
+
+        for lib_path in lib_paths {
+            if std::path::Path::new(lib_path).exists() {
+                let lib_name = std::path::Path::new(lib_path).file_name().unwrap();
+                let dest = lib_dir.join(lib_name);
+                std::fs::copy(lib_path, dest)?;
+                println!("Copied system library: {}", lib_name.to_string_lossy());
+                break;
+            }
+        }
+    }
+
+    Ok(())
+}
+
+fn create_zip_package(source_dir: &PathBuf, zip_path: &PathBuf) -> anyhow::Result<()> {
+    let file = fs::File::create(zip_path)?;
+    let mut zip = zip::ZipWriter::new(file);
+
+    let walkdir = walkdir::WalkDir::new(source_dir);
+    for entry in walkdir {
+        let entry = entry?;
+        let path = entry.path();
+        
+        if path.is_file() {
+            let relative_path = path.strip_prefix(source_dir)?;
+            let name = relative_path.to_string_lossy();
+            
+            let options: SimpleFileOptions = Default::default();
+            zip.start_file(name, options.into())?;
+            let mut file = std::fs::File::open(path)?;
+            std::io::copy(&mut file, &mut zip)?;
+        }
+    }
+
+    zip.finish()?;
+    println!("Created zip package: {}", zip_path.display());
+    Ok(())
+}
+
+pub fn read_from_eupak(eupak_path: PathBuf) -> anyhow::Result<()> {
+    let bytes = std::fs::read(&eupak_path)?;
+    let (content, _): (RuntimeData, usize) = bincode::decode_from_slice(&bytes, bincode::config::standard())?;
+    println!("{} contents: {:#?}", eupak_path.display(), content);
+    Ok(())
+}
+
+pub fn build(
+        project_path: PathBuf,
+        // _sub_matches: &ArgMatches
+    ) -> anyhow::Result<PathBuf> {
+    println!(" > Starting build");
+    if !project_path.exists() {
+        return Err(anyhow::anyhow!(format!("Unable to locate project config file: [{}]", project_path.display())));
+    }
+    // ProjectConfig::read_from(&project_path)?.load_config_to_memory()?;
+    
+    let mut project_config = ProjectConfig::read_from(&project_path)?;
+    log::info!(" > Reading from project config");
+    project_config.load_config_to_memory()?;
+    log::info!(" > Loading config to memory");
+
+    let source_config = {
+        let source_guard = SOURCE.read();
+        source_guard.clone()
+    };
+    log::info!(" > Copied source config");
+
+    let scene_data = {
+        let scenes_guard = SCENES.read();
+        scenes_guard.clone()
+    };
+    log::info!(" > Copied scene data");
+
+    let build_dir = project_path.parent().unwrap().join("build").join("output");
+    fs::create_dir_all(&build_dir)?;
+    log::info!(" > Created build dir");
+
+    let project_name = project_config.project_name.clone();
+
+    let mut scripts = HashMap::new();
+    let script_dir = project_path.parent().unwrap().join("src");
+    if script_dir.exists() {
+        for entry in fs::read_dir(&script_dir)? {
+            let entry = entry?;
+            let path = entry.path();
+            if let Some(ext) = path.extension() {
+                if ext == "rhai" {
+                    let name = path.file_name().unwrap().to_string_lossy().to_string();
+                    let contents = fs::read_to_string(&path)?;
+                    println!(" > Copied script info from [{}]", name);
+                    scripts.insert(name, contents);
+                }
+            }
+        }
+    }
+
+    let runtime_data = RuntimeData {
+        project_config,
+        source_config,
+        scene_data,
+        scripts
+    };
+    log::info!(" > Created runtime data structures");
+
+    let runtime_file = build_dir.join(format!("{}.eupak", project_name));
+    let serialized = bincode::serde::encode_to_vec(runtime_data, bincode::config::standard())?;
+    std::fs::write(&runtime_file, serialized)?;
+    log::info!(" > Written the file to build location");
+
+    println!("Build completed successfully. Output at {:?}", runtime_file.display());
+    Ok(runtime_file)
+}
+
+pub fn health() -> anyhow::Result<()> {
+    let mut all_healthy = true;
+    
+    match Command::new("cargo").arg("--version").output() {
+        Ok(output) => {
+            if output.status.success() {
+                let version = String::from_utf8_lossy(&output.stdout).trim().to_string();
+                println!("Does cargo exist? ✓ YES - {}", version);
+                
+                match Command::new("rustc").arg("--version").output() {
+                    Ok(rustc_output) => {
+                        if rustc_output.status.success() {
+                            let rustc_version = String::from_utf8_lossy(&rustc_output.stdout).trim().to_string();
+                            println!("Does rustc compiler exist? ✓ YES - {}", rustc_version);
+                        } else {
+                            println!("Does rustc compiler exist? ✗ NO - rustc command failed");
+                            all_healthy = false;
+                        }
+                    }
+                    Err(_) => {
+                        println!("Does rustc compiler exist? ✗ NO - rustc not found in PATH");
+                        all_healthy = false;
+                    }
+                }
+            } else {
+                println!("Does cargo exist? ✗ NO - cargo command failed");
+                println!("Does rustc compiler exist? ✗ NO - cargo failed, cannot check rustc");
+                all_healthy = false;
+            }
+        }
+        Err(_) => {
+            println!("Does cargo exist? ✗ NO - cargo not found in PATH");
+            println!("Does rustc compiler exist? ✗ NO - cargo not found, cannot check rustc");
+            all_healthy = false;
+        }
+    }
+    
+    let assimp_found = check_assimp_availability();
+    if assimp_found {
+        println!("Does assimp lib exist? ✓ YES - Found assimp library");
+    } else {
+        println!("Does assimp lib exist? ⚠ MAYBE - Could not definitively locate assimp, but it may be available through system package manager or vcpkg");
+    }
+    
+    if all_healthy {
+        println!("\n✓ All core tools are available!");
+    } else {
+        println!("\n✗ Some required tools are missing. Please install Rust and Cargo.");
+        return Err(anyhow::anyhow!("Health check failed - missing required tools"));
+    }
+    
+    Ok(())
+}
+
+fn check_assimp_availability() -> bool {
+    #[cfg(target_os = "windows")]
+    {
+        let common_paths = vec![
+            "C:\\vcpkg\\installed\\x64-windows\\lib\\assimp-vc143-mt.lib",
+            "C:\\vcpkg\\installed\\x64-windows\\lib\\assimp.lib",
+            "C:\\vcpkg\\installed\\x86-windows\\lib\\assimp-vc143-mt.lib",
+            "C:\\vcpkg\\installed\\x86-windows\\lib\\assimp.lib",
+            "C:\\Program Files\\Assimp\\lib\\assimp.lib",
+            "C:\\Program Files (x86)\\Assimp\\lib\\assimp.lib",
+        ];
+        
+        for path in common_paths {
+            if std::path::Path::new(path).exists() {
+                return true;
+            }
+        }
+        
+        if let Ok(output) = Command::new("where").arg("assimp.dll").output() {
+            if output.status.success() {
+                return true;
+            }
+        }
+    }
+    
+    #[cfg(any(target_os = "linux", target_os = "macos"))]
+    {
+        let common_paths = vec![
+            "/usr/lib/libassimp.so",
+            "/usr/lib/x86_64-linux-gnu/libassimp.so",
+            "/usr/lib64/libassimp.so",
+            "/usr/local/lib/libassimp.so",
+            "/opt/homebrew/lib/libassimp.dylib",
+            "/usr/local/lib/libassimp.dylib",
+        ];
+        
+        for path in common_paths {
+            if std::path::Path::new(path).exists() {
+                return true;
+            }
+        }
+        
+        if let Ok(output) = Command::new("pkg-config").args(&["--exists", "assimp"]).output() {
+            if output.status.success() {
+                return true;
+            }
+        }
+        
+        #[cfg(target_os = "linux")]
+        {
+            if let Ok(output) = Command::new("ldconfig").args(&["-p"]).output() {
+                if output.status.success() {
+                    let output_str = String::from_utf8_lossy(&output.stdout);
+                    if output_str.contains("libassimp") {
+                        return true;
+                    }
+                }
+            }
+        }
+    }
+    
+    false
+}
diff --git a/eucalyptus-editor/src/camera.rs b/eucalyptus-editor/src/camera.rs
new file mode 100644
index 0000000..ea22896
--- /dev/null
+++ b/eucalyptus-editor/src/camera.rs
@@ -0,0 +1,148 @@
+use std::time::Instant;
+use egui::{CollapsingHeader, Ui};
+use hecs::Entity;
+use dropbear_engine::camera::Camera;
+use eucalyptus_core::camera::{CameraAction, CameraComponent, CameraFollowTarget, CameraType};
+use crate::editor::component::Component;
+use crate::editor::{EntityType, Signal, StaticallyKept, UndoableAction};
+
+impl Component for Camera {
+    fn inspect(&mut self, entity: &mut Entity, cfg: &mut StaticallyKept, ui: &mut Ui, undo_stack: &mut Vec<UndoableAction>, _signal: &mut Signal, _label: &mut String) {
+        let _ = _signal;
+        ui.group(|ui| {
+            CollapsingHeader::new("Camera").default_open(true).show(ui, |ui| {
+                ui.horizontal(|ui| {
+                    ui.label("Name: ");
+                    let resp = ui.text_edit_singleline(&mut self.label);
+
+                    if resp.changed() {
+                        if cfg.old_label_entity.is_none() {
+                            cfg.old_label_entity = Some(entity.clone());
+                            cfg.label_original = Some(self.label.clone());
+                        }
+                        cfg.label_last_edit = Some(Instant::now());
+                    }
+
+                    if resp.lost_focus() {
+                        if let Some(ent) = cfg.old_label_entity.take() {
+                            if ent == *entity {
+                                if let Some(orig) = cfg.label_original.take() {
+                                    UndoableAction::push_to_undo(
+                                        undo_stack,
+                                        UndoableAction::Label(ent, orig, EntityType::Entity),
+                                    );
+                                    log::debug!("Pushed camera label change to undo stack");
+                                }
+                            } else {
+                                cfg.label_original = None;
+                            }
+                        }
+                        cfg.label_last_edit = None;
+                    }
+                });
+
+                ui.horizontal(|ui| {
+                    ui.label("Position:");
+                    ui.label(format!("{:.2}, {:.2}, {:.2}", self.eye.x, self.eye.y, self.eye.z));
+                });
+
+                ui.horizontal(|ui| {
+                    ui.label("Target:");
+                    ui.label(format!("{:.2}, {:.2}, {:.2}", self.target.x, self.target.y, self.target.z));
+                });
+            });
+        });
+    }
+}
+
+#[derive(Debug)]
+pub enum UndoableCameraAction {
+    Speed(Entity, f64),
+    Sensitivity(Entity, f64),
+    FOV(Entity, f64),
+    Type(Entity, CameraType),
+}
+
+impl Component for CameraComponent {
+    fn inspect(&mut self, _entity: &mut Entity, _cfg: &mut StaticallyKept, ui: &mut Ui, _undo_stack: &mut Vec<UndoableAction>, _signal: &mut Signal, _label: &mut String) {
+        ui.group(|ui| {
+            CollapsingHeader::new("Camera Component").default_open(true).show(ui, |ui| {
+                ui.horizontal(|ui| {
+                    ui.label("Type:");
+                    egui::ComboBox::from_label("")
+                        .selected_text(format!("{:?}", self.camera_type))
+                        .show_ui(ui, |ui| {
+                            ui.selectable_value(&mut self.camera_type, CameraType::Normal, "Normal");
+                            if !matches!(self.camera_type, CameraType::Player) {
+                                ui.selectable_value(&mut self.camera_type, CameraType::Debug, "Debug");
+                            } else {
+                                ui.add_enabled(false, egui::Button::new("Debug"));
+                                ui.label("Debug not available for player cameras");
+                            }
+                            ui.selectable_value(&mut self.camera_type, CameraType::Player, "Player");
+                        });
+                });
+
+                ui.horizontal(|ui| {
+                    ui.label("Speed:");
+                    ui.add(egui::DragValue::new(&mut self.speed).speed(0.1).range(0.1..=20.0));
+                });
+
+                ui.horizontal(|ui| {
+                    ui.label("Sensitivity:");
+                    ui.add(egui::DragValue::new(&mut self.sensitivity).speed(0.0001).range(0.0001..=1.0));
+                });
+
+                ui.horizontal(|ui| {
+                    ui.label("FOV:");
+                    ui.add(egui::Slider::new(&mut self.fov_y, 10.0..=120.0).suffix("°"));
+                });
+
+                if matches!(self.camera_type, CameraType::Player) {
+                    ui.separator();
+                    ui.label("Player Camera Controls:");
+                    if ui.button("Set as Active Camera").clicked() {
+                        // This would need to be implemented via signal
+                        log::info!("Set player camera as active (not implemented)");
+                    }
+                }
+            });
+        });
+    }
+}
+
+impl Component for CameraFollowTarget {
+    fn inspect(&mut self, _entity: &mut Entity, _cfg: &mut StaticallyKept, ui: &mut Ui, _undo_stack: &mut Vec<UndoableAction>, signal: &mut Signal, _label: &mut String) {
+        ui.group(|ui| {
+            CollapsingHeader::new("Camera Follow Target").default_open(true).show(ui, |ui| {
+                ui.horizontal(|ui| {
+                    ui.label("Target Entity:");
+                    ui.text_edit_singleline(&mut self.follow_target);
+                });
+
+                ui.horizontal(|ui| {
+                    ui.label("Offset:");
+                });
+
+                ui.horizontal(|ui| {
+                    ui.label("X:");
+                    ui.add(egui::DragValue::new(&mut self.offset.x).speed(0.1));
+                });
+
+                ui.horizontal(|ui| {
+                    ui.label("Y:");
+                    ui.add(egui::DragValue::new(&mut self.offset.y).speed(0.1));
+                });
+
+                ui.horizontal(|ui| {
+                    ui.label("Z:");
+                    ui.add(egui::DragValue::new(&mut self.offset.z).speed(0.1));
+                });
+
+                if ui.button("Clear Target").clicked() {
+                    *signal = Signal::CameraAction(CameraAction::ClearPlayerTarget);
+                }
+            });
+        });
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-editor/src/debug.rs b/eucalyptus-editor/src/debug.rs
new file mode 100644
index 0000000..690a47f
--- /dev/null
+++ b/eucalyptus-editor/src/debug.rs
@@ -0,0 +1,18 @@
+//! Used to aid with debugging any issues with the editor.
+use egui::Ui;
+use crate::editor::Signal;
+
+/// Show a menu bar for debug. A new "Debug" menu button will show up on the editors menu bar.
+pub(crate) fn show_menu_bar(ui: &mut Ui, signal: &mut Signal) {
+    ui.menu_button("Debug", |ui_debug| {
+        if ui_debug.button("Panic").clicked() {
+            log::warn!("Panic caused on purpose from Menu Button Click");
+            panic!("Testing out panicking with new panic module, this is a test")
+        }
+
+        if ui_debug.button("Show Entities Loaded").clicked() {
+            log::info!("Show Entities Loaded under Debug Menu is clicked");
+            *signal = Signal::LogEntities;
+        }
+    });
+}
\ No newline at end of file
diff --git a/eucalyptus-editor/src/editor/component.rs b/eucalyptus-editor/src/editor/component.rs
new file mode 100644
index 0000000..60f165b
--- /dev/null
+++ b/eucalyptus-editor/src/editor/component.rs
@@ -0,0 +1,666 @@
+//! This module should describe the different components that are editable in the resource inspector.
+
+use std::time::Instant;
+use egui::{CollapsingHeader, ComboBox, Ui};
+use glam::Vec3;
+use hecs::Entity;
+use dropbear_engine::attenuation::ATTENUATION_PRESETS;
+use dropbear_engine::entity::{AdoptedEntity, Transform};
+use dropbear_engine::lighting::{Light, LightComponent, LightType};
+use crate::editor::{EntityType, Signal, StaticallyKept, UndoableAction};
+use eucalyptus_core::scripting::{ScriptAction, TEMPLATE_SCRIPT};
+use eucalyptus_core::states::ScriptComponent;
+use eucalyptus_core::warn;
+
+pub trait Component {
+    fn inspect(&mut self, entity: &mut hecs::Entity, cfg: &mut StaticallyKept, ui: &mut Ui, undo_stack: &mut Vec<UndoableAction>, signal: &mut Signal, label: &mut String);
+}
+
+impl Component for Transform {
+    fn inspect(&mut self, entity: &mut Entity, cfg: &mut StaticallyKept, ui: &mut Ui, undo_stack: &mut Vec<UndoableAction>, _signal: &mut Signal, _label: &mut String) {
+        ui.group(|ui| {
+            CollapsingHeader::new("Transform").default_open(true).show(
+                ui,
+                |ui| {
+                    ui.horizontal(|ui| {
+                        ui.label("Position:");
+                    });
+
+                    ui.horizontal(|ui| {
+                        ui.label("X:");
+                        let response = ui.add(
+                            egui::DragValue::new(&mut self.position.x)
+                                .speed(0.1)
+                                .fixed_decimals(3),
+                        );
+
+                        if response.drag_started() {
+                            cfg.transform_old_entity = Some(entity.clone());
+                            cfg.transform_original_transform = Some((*self).clone());
+                            cfg.transform_in_progress = true;
+                        }
+
+                        if response.drag_stopped() && cfg.transform_in_progress {
+                            if let Some(ent) = cfg.transform_old_entity.take() {
+                                if let Some(orig) = cfg.transform_original_transform.take() {
+                                    UndoableAction::push_to_undo(
+                                        undo_stack,
+                                        UndoableAction::Transform(ent, orig),
+                                    );
+                                    log::debug!("Pushed X transform change to undo stack");
+                                }
+                            }
+                            cfg.transform_in_progress = false;
+                        }
+                    });
+                    ui.horizontal(|ui| {
+                        ui.label("Y:");
+                        let response = ui.add(
+                            egui::DragValue::new(&mut self.position.y)
+                                .speed(0.1)
+                                .fixed_decimals(3),
+                        );
+
+                        if response.drag_started() {
+                            cfg.transform_old_entity = Some(entity.clone());
+                            cfg.transform_original_transform = Some((*self).clone());
+                            cfg.transform_in_progress = true;
+                        }
+
+                        if response.drag_stopped() && cfg.transform_in_progress {
+                            if let Some(ent) = cfg.transform_old_entity.take() {
+                                if let Some(orig) = cfg.transform_original_transform.take() {
+                                    UndoableAction::push_to_undo(
+                                        undo_stack,
+                                        UndoableAction::Transform(ent, orig),
+                                    );
+                                    log::debug!("Pushed Y transform change to undo stack");
+                                }
+                            }
+                            cfg.transform_in_progress = false;
+                        }
+                    });
+
+                    ui.horizontal(|ui| {
+                        ui.label("Z:");
+                        let response = ui.add(
+                            egui::DragValue::new(&mut self.position.z)
+                                .speed(0.1)
+                                .fixed_decimals(3),
+                        );
+
+                        if response.drag_started() {
+                            cfg.transform_old_entity = Some(entity.clone());
+                            cfg.transform_original_transform = Some((*self).clone());
+                            cfg.transform_in_progress = true;
+                        }
+
+                        if response.drag_stopped() && cfg.transform_in_progress {
+                            if let Some(ent) = cfg.transform_old_entity.take() {
+                                if let Some(orig) = cfg.transform_original_transform.take() {
+                                    UndoableAction::push_to_undo(
+                                        undo_stack,
+                                        UndoableAction::Transform(ent, orig),
+                                    );
+                                    log::debug!("Pushed Z transform change to undo stack");
+                                }
+                            }
+                            cfg.transform_in_progress = false;
+                        }
+                    });
+                    if ui.button("Reset Position").clicked() {
+                        self.position = glam::DVec3::ZERO;
+                    }
+
+                    ui.add_space(5.0);
+
+                    ui.horizontal(|ui| {
+                        ui.label("Rotation:");
+                    });
+
+                    let mut rotation_changed = false;
+                    let (mut x_rot, mut y_rot, mut z_rot) =
+                        self.rotation.to_euler(glam::EulerRot::XYZ);
+
+                    ui.horizontal(|ui| {
+                        ui.label("X:");
+                        let response = ui.add(
+                            egui::Slider::new(
+                                &mut x_rot,
+                                -std::f64::consts::PI
+                                    ..=std::f64::consts::PI,
+                            )
+                                .step_by(0.01)
+                                .custom_formatter(|n, _| {
+                                    format!("{:.1}°", n.to_degrees())
+                                })
+                                .custom_parser(|s| {
+                                    s.trim_end_matches('°')
+                                        .parse::<f64>()
+                                        .ok()
+                                        .map(|v| v.to_radians())
+                                }),
+                        );
+
+                        if response.drag_started() {
+                            cfg.transform_old_entity = Some(entity.clone());
+                            cfg.transform_original_transform = Some((*self).clone());
+                            cfg.transform_in_progress = true;
+                        }
+
+                        if response.changed() {
+                            rotation_changed = true;
+                        }
+
+                        if response.drag_stopped() && cfg.transform_in_progress {
+                            if let Some(ent) = cfg.transform_old_entity.take() {
+                                if let Some(orig) = cfg.transform_original_transform.take() {
+                                    UndoableAction::push_to_undo(
+                                        undo_stack,
+                                        UndoableAction::Transform(ent, orig),
+                                    );
+                                    log::debug!("Pushed X rotation change to undo stack");
+                                }
+                            }
+                            cfg.transform_in_progress = false;
+                        }
+                    });
+
+                    ui.horizontal(|ui| {
+                        ui.label("Y:");
+                        let response = ui.add(
+                            egui::Slider::new(
+                                &mut y_rot,
+                                -std::f64::consts::PI
+                                    ..=std::f64::consts::PI,
+                            )
+                                .step_by(0.01)
+                                .custom_formatter(|n, _| {
+                                    format!("{:.1}°", n.to_degrees())
+                                })
+                                .custom_parser(|s| {
+                                    s.trim_end_matches('°')
+                                        .parse::<f64>()
+                                        .ok()
+                                        .map(|v| v.to_radians())
+                                }),
+                        );
+
+                        if response.drag_started() {
+                            cfg.transform_old_entity = Some(entity.clone());
+                            cfg.transform_original_transform = Some((*self).clone());
+                            cfg.transform_in_progress = true;
+                        }
+
+                        if response.changed() {
+                            rotation_changed = true;
+                        }
+
+                        if response.drag_stopped() && cfg.transform_in_progress {
+                            if let Some(ent) = cfg.transform_old_entity.take() {
+                                if let Some(orig) = cfg.transform_original_transform.take() {
+                                    UndoableAction::push_to_undo(
+                                        undo_stack,
+                                        UndoableAction::Transform(ent, orig),
+                                    );
+                                    log::debug!("Pushed Y rotation change to undo stack");
+                                }
+                            }
+                            cfg.transform_in_progress = false;
+                        }
+                    });
+
+                    ui.horizontal(|ui| {
+                        ui.label("Z:");
+                        let response = ui.add(
+                            egui::Slider::new(
+                                &mut z_rot,
+                                -std::f64::consts::PI
+                                    ..=std::f64::consts::PI,
+                            )
+                                .step_by(0.01)
+                                .custom_formatter(|n, _| {
+                                    format!("{:.1}°", n.to_degrees())
+                                })
+                                .custom_parser(|s| {
+                                    s.trim_end_matches('°')
+                                        .parse::<f64>()
+                                        .ok()
+                                        .map(|v| v.to_radians())
+                                }),
+                        );
+
+                        if response.drag_started() {
+                            cfg.transform_old_entity = Some(entity.clone());
+                            cfg.transform_original_transform = Some((*self).clone());
+                            cfg.transform_in_progress = true;
+                        }
+
+                        if response.changed() {
+                            rotation_changed = true;
+                        }
+
+                        if response.drag_stopped() && cfg.transform_in_progress {
+                            if let Some(ent) = cfg.transform_old_entity.take() {
+                                if let Some(orig) = cfg.transform_original_transform.take() {
+                                    UndoableAction::push_to_undo(
+                                        undo_stack,
+                                        UndoableAction::Transform(ent, orig),
+                                    );
+                                    log::debug!("Pushed Z rotation change to undo stack");
+                                }
+                            }
+                            cfg.transform_in_progress = false;
+                        }
+                    });
+
+                    if rotation_changed {
+                        self.rotation = glam::DQuat::from_euler(
+                            glam::EulerRot::XYZ,
+                            x_rot,
+                            y_rot,
+                            z_rot,
+                        );
+                    }
+                    if ui.button("Reset Rotation").clicked() {
+                        self.rotation = glam::DQuat::IDENTITY;
+                    }
+                    ui.add_space(5.0);
+
+                    ui.horizontal(|ui| {
+                        ui.label("Scale:");
+                        ui.with_layout(
+                            egui::Layout::right_to_left(egui::Align::Center),
+                            |ui| {
+                                let lock_icon = if cfg.scale_locked {
+                                    "🔒"
+                                } else {
+                                    "🔓"
+                                };
+                                if ui
+                                    .button(lock_icon)
+                                    .on_hover_text("Lock uniform scaling")
+                                    .clicked()
+                                {
+                                    cfg.scale_locked = !cfg.scale_locked;
+                                }
+                            },
+                        );
+                    });
+
+                    let mut scale_changed = false;
+                    let mut new_scale = self.scale;
+
+                    ui.horizontal(|ui| {
+                        ui.label("X:");
+                        let response = ui.add(
+                            egui::DragValue::new(&mut new_scale.x)
+                                .speed(0.01)
+                                .fixed_decimals(3),
+                        );
+
+                        if response.drag_started() {
+                            cfg.transform_old_entity = Some(entity.clone());
+                            cfg.transform_original_transform = Some((*self).clone());
+                            cfg.transform_in_progress = true;
+                        }
+
+                        if response.changed() {
+                            scale_changed = true;
+                            if cfg.scale_locked {
+                                let scale_factor = new_scale.x / self.scale.x;
+                                new_scale.y = self.scale.y * scale_factor;
+                                new_scale.z = self.scale.z * scale_factor;
+                            }
+                        }
+
+                        if response.drag_stopped() && cfg.transform_in_progress {
+                            if let Some(ent) = cfg.transform_old_entity.take() {
+                                if let Some(orig) = cfg.transform_original_transform.take() {
+                                    UndoableAction::push_to_undo(
+                                        undo_stack,
+                                        UndoableAction::Transform(ent, orig),
+                                    );
+                                    log::debug!("Pushed X scale change to undo stack");
+                                }
+                            }
+                            cfg.transform_in_progress = false;
+                        }
+                    });
+
+                    ui.horizontal(|ui| {
+                        ui.label("Y:");
+                        let y_slider = egui::DragValue::new(&mut new_scale.y)
+                            .speed(0.01)
+                            .fixed_decimals(3);
+
+                        let response = ui.add_enabled(!cfg.scale_locked, y_slider);
+
+                        if response.drag_started() && !cfg.scale_locked {
+                            cfg.transform_old_entity = Some(entity.clone());
+                            cfg.transform_original_transform = Some((*self).clone());
+                            cfg.transform_in_progress = true;
+                        }
+
+                        if response.changed() {
+                            scale_changed = true;
+                        }
+
+                        if response.drag_stopped() && cfg.transform_in_progress {
+                            if let Some(ent) = cfg.transform_old_entity.take() {
+                                if let Some(orig) = cfg.transform_original_transform.take() {
+                                    UndoableAction::push_to_undo(
+                                        undo_stack,
+                                        UndoableAction::Transform(ent, orig),
+                                    );
+                                    log::debug!("Pushed Y scale change to undo stack");
+                                }
+                            }
+                            cfg.transform_in_progress = false;
+                        }
+                    });
+
+                    ui.horizontal(|ui| {
+                        ui.label("Z:");
+                        let z_slider = egui::DragValue::new(&mut new_scale.z)
+                            .speed(0.01)
+                            .fixed_decimals(3);
+
+                        let response = ui.add_enabled(!cfg.scale_locked, z_slider);
+
+                        if response.drag_started() && !cfg.scale_locked {
+                            cfg.transform_old_entity = Some(entity.clone());
+                            cfg.transform_original_transform = Some((*self).clone());
+                            cfg.transform_in_progress = true;
+                        }
+
+                        if response.changed() {
+                            scale_changed = true;
+                        }
+
+                        if response.drag_stopped() && cfg.transform_in_progress {
+                            if let Some(ent) = cfg.transform_old_entity.take() {
+                                if let Some(orig) = cfg.transform_original_transform.take() {
+                                    UndoableAction::push_to_undo(
+                                        undo_stack,
+                                        UndoableAction::Transform(ent, orig),
+                                    );
+                                    log::debug!("Pushed Z scale change to undo stack");
+                                }
+                            }
+                            cfg.transform_in_progress = false;
+                        }
+                    });
+
+                    if scale_changed {
+                        self.scale = new_scale;
+                    }
+                    if ui.button("Reset Scale").clicked() {
+                        self.scale = glam::DVec3::ONE;
+                    }
+                    ui.add_space(5.0);
+
+                    // maybe use? probs not :/
+                    // if pos_changed || rotation_changed || scale_changed {
+                    //     ui.colored_label(egui::Color32::YELLOW, "Transform modified");
+                    // }
+                },
+            );
+        });
+    }
+}
+
+impl Component for ScriptComponent {
+    fn inspect(&mut self, _entity: &mut Entity, _cfg: &mut StaticallyKept, ui: &mut Ui, _undo_stack: &mut Vec<UndoableAction>, signal: &mut Signal, label: &mut String) {
+        let script_loc = self.path.to_str().unwrap_or("").to_string();
+
+        ui.group(|ui| {
+            CollapsingHeader::new("Scripting")
+                .default_open(true)
+                .show(ui, |ui| {
+                    ui.horizontal(|ui| {
+                        if ui.button("Browse").clicked() {
+                            if let Some(script_file) = rfd::FileDialog::new()
+                                .add_filter("Typescript", &["ts"])
+                                .pick_file()
+                            {
+                                let script_name = script_file
+                                    .file_stem()
+                                    .unwrap_or_default()
+                                    .to_string_lossy()
+                                    .to_string();
+                                let lib_path = &script_file.clone().parent().unwrap().join("dropbear.ts");
+                                if let Err(_) = std::fs::read(lib_path) {
+                                    log::warn!("dropbear.ts library does not exist in project source directory, copying...");
+                                    if let Err(e) = std::fs::write(lib_path, include_str!("../../../resources/dropbear.ts")) {
+                                        log::error!("Non-fatal error: Creating library file failed: {}", e);
+                                    } else {
+                                        log::info!("Wrote dropbear.ts library file!");
+                                    }
+                                };
+                                *signal = Signal::ScriptAction(ScriptAction::AttachScript {
+                                    script_path: script_file,
+                                    script_name,
+                                });
+                            }
+                        }
+
+                        if ui.button("New").clicked() {
+                            if let Some(script_path) = rfd::FileDialog::new()
+                                .add_filter("TypeScript", &["ts"])
+                                .set_file_name(format!("{}_script.ts", label))
+                                .save_file()
+                            {
+                                // check if dropbear module exists
+                                // todo: change this to an %APPDATA% file instead of to memory. 
+                                let lib_path = &script_path.clone().parent().unwrap().join("dropbear.ts");
+                                if let Err(_) = std::fs::read(lib_path) {
+                                    log::warn!("dropbear.ts library does not exist in project source directory, copying...");
+                                    if let Err(e) = std::fs::write(lib_path, include_str!("../../../resources/dropbear.ts")) {
+                                        log::error!("Non-fatal error: Creating library file failed: {}", e);
+                                    } else {
+                                        log::info!("Wrote dropbear.ts library file!");
+                                    }
+                                };
+                                match std::fs::write(&script_path, TEMPLATE_SCRIPT) {
+                                    Ok(_) => {
+                                        let script_name = script_path
+                                            .file_stem()
+                                            .unwrap_or_default()
+                                            .to_string_lossy()
+                                            .to_string();
+                                        *signal = Signal::ScriptAction(ScriptAction::CreateAndAttachScript {
+                                            script_path,
+                                            script_name,
+                                        });
+                                    },
+                                    Err(e) => {
+                                        warn!("Failed to create script file: {}", e);
+                                    },
+                                }
+                            }
+                        }
+                    });
+
+                    ui.separator();
+
+                    ui.horizontal_wrapped(|ui| {
+                        ui.label("Script Location:");
+                        ui.label(script_loc);
+                    });
+
+                    if ui.button("Remove").clicked() {
+                        *signal = Signal::ScriptAction(ScriptAction::RemoveScript);
+                    }
+                    ui.separator();
+                    ui.horizontal(|ui| {
+                        if ui.button("Edit Script").clicked() {
+                            *signal = Signal::ScriptAction(ScriptAction::EditScript);
+                        }
+                    });
+                });
+        });
+    }
+}
+
+impl Component for AdoptedEntity {
+    fn inspect(&mut self, entity: &mut Entity, cfg: &mut StaticallyKept, ui: &mut Ui, undo_stack: &mut Vec<UndoableAction>, _signal: &mut Signal, _label: &mut String) {
+        // label
+        ui.group(|ui| {
+            ui.horizontal(|ui| {
+                ui.label("Name: ");
+
+                let resp = ui.text_edit_singleline(self.label_mut());
+
+                if resp.changed() {
+                    if cfg.old_label_entity.is_none() {
+                        cfg.old_label_entity = Some(entity.clone());
+                        cfg.label_original = Some(self.label().clone());
+                    }
+                    cfg.label_last_edit = Some(Instant::now());
+                }
+
+                if resp.lost_focus() {
+                    if let Some(ent) = cfg.old_label_entity.take() {
+                        if ent == *entity {
+                            if let Some(orig) = cfg.label_original.take() {
+                                UndoableAction::push_to_undo(
+                                    undo_stack,
+                                    UndoableAction::Label(ent, orig, EntityType::Entity),
+                                );
+                                log::debug!("Pushed label change to undo stack (immediate)");
+                            }
+                        } else {
+                            cfg.label_original = None;
+                        }
+                    }
+                    cfg.label_last_edit = None;
+                }
+            })
+        });
+    }
+}
+
+impl Component for Light {
+    fn inspect(&mut self, entity: &mut Entity, cfg: &mut StaticallyKept, ui: &mut Ui, undo_stack: &mut Vec<UndoableAction>, _signal: &mut Signal, _label: &mut String) {
+        ui.group(|ui| {
+            ui.horizontal(|ui| {
+                ui.label("Name: ");
+
+                let resp = ui.text_edit_singleline(&mut self.label);
+
+                if resp.changed() {
+                    if cfg.old_label_entity.is_none() {
+                        cfg.old_label_entity = Some(entity.clone());
+                        cfg.label_original = Some(self.label.clone().to_string());
+                    }
+                    cfg.label_last_edit = Some(Instant::now());
+                }
+
+                if resp.lost_focus() {
+                    if let Some(ent) = cfg.old_label_entity.take() {
+                        if ent == *entity {
+                            if let Some(orig) = cfg.label_original.take() {
+                                UndoableAction::push_to_undo(
+                                    undo_stack,
+                                    UndoableAction::Label(ent, orig, EntityType::Light),
+                                );
+                                log::debug!("Pushed label change to undo stack (immediate)");
+                            }
+                        } else {
+                            cfg.label_original = None;
+                        }
+                    }
+                    cfg.label_last_edit = None;
+                }
+            })
+        });
+    }
+}
+
+impl Component for LightComponent {
+    fn inspect(&mut self, _entity: &mut Entity, _cfg: &mut StaticallyKept, ui: &mut Ui, _undo_stack: &mut Vec<UndoableAction>, _signal: &mut Signal, _label: &mut String) {
+        ui.group(|ui| {
+            ui.horizontal(|ui| {
+                ComboBox::new("light_type", "Light Type")
+                    // .width(ui.available_width())
+                    .selected_text(self.light_type.to_string())
+                    .show_ui(ui, |ui| {
+                        ui.selectable_value(&mut self.light_type, LightType::Directional, "Directional");
+                        ui.selectable_value(&mut self.light_type, LightType::Point, "Point");
+                        ui.selectable_value(&mut self.light_type, LightType::Spot, "Spot");
+                    });
+            });
+
+            // let is_dir = matches!(self.light_type, LightType::Directional);
+            let is_point = matches!(self.light_type, LightType::Point);
+            let is_spot = matches!(self.light_type, LightType::Spot);
+
+            // colour
+            ui.separator();
+            let mut colour = self.colour.clone().as_vec3().to_array();
+            ui.horizontal(|ui| {
+                ui.label("Colour");
+                egui::color_picker::color_edit_button_rgb(ui, &mut colour)
+            });
+            self.colour = Vec3::from_array(colour).as_dvec3();
+
+            // intensity
+            ui.separator();
+            ui.horizontal(|ui| {
+                ui.label("Intensity");
+                ui.add(egui::Slider::new(&mut self.intensity, 0.0..=1.0));
+            });
+
+            // enabled and visible
+            ui.separator();
+            ui.horizontal(|ui| {
+                ui.checkbox(&mut self.enabled, "Enabled");
+                ui.checkbox(&mut self.visible, "Visible");
+            });
+
+            if is_spot || is_point {
+                // attenuation
+                ui.separator();
+                ui.horizontal(|ui| {
+                    ComboBox::new("Range", "Range")
+                        // .width(ui.available_width())
+                        .selected_text(format!("Range {}", self.attenuation.range.to_string()))
+                        .show_ui(ui, |ui| {
+                            for (preset, label) in ATTENUATION_PRESETS {
+                                ui.selectable_value(&mut self.attenuation, preset.clone(), *label);
+                            }
+                        });
+                });
+            }
+
+            if is_spot {
+                // cutoff angles
+                ui.horizontal(|ui| {
+                    ui.add(
+                        egui::Slider::new(&mut self.cutoff_angle, 1.0..=89.0)
+                            .text("Inner")
+                            .suffix("°")
+                            .step_by(0.1)
+                    );
+                });
+
+                ui.horizontal(|ui| {
+                    ui.add(
+                        egui::Slider::new(&mut self.outer_cutoff_angle, 1.0..=90.0)
+                            .text("Outer")
+                            .suffix("°")
+                            .step_by(0.1)
+                    );
+                });
+
+                if self.outer_cutoff_angle <= self.cutoff_angle {
+                    self.outer_cutoff_angle = self.cutoff_angle + 1.0;
+                }
+
+                let cone_softness = self.outer_cutoff_angle - self.cutoff_angle;
+                ui.label(format!("Soft edge: {:.1}°", cone_softness));
+            }
+        });
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-editor/src/editor/dock.rs b/eucalyptus-editor/src/editor/dock.rs
new file mode 100644
index 0000000..113fe2d
--- /dev/null
+++ b/eucalyptus-editor/src/editor/dock.rs
@@ -0,0 +1,1024 @@
+use crate::editor::ViewportMode;
+use super::*;
+use std::{collections::HashSet, sync::LazyLock};
+
+use dropbear_engine::{entity::Transform, lighting::{Light, LightComponent}};
+use egui;
+use egui_dock_fork::TabViewer;
+use egui_extras;
+use log;
+use parking_lot::Mutex;
+use transform_gizmo_egui::{
+    math::{DMat4, DVec3}, EnumSet, Gizmo, GizmoConfig, GizmoExt, GizmoMode
+};
+use eucalyptus_core::states::{Node, ResourceType, RESOURCES};
+use eucalyptus_core::utils::PendingSpawn;
+use crate::APP_INFO;
+use crate::editor::component::Component;
+use crate::editor::scene::PENDING_SPAWNS;
+
+pub struct EditorTabViewer<'a> {
+    pub view: egui::TextureId,
+    pub nodes: Vec<EntityNode>,
+    pub tex_size: Extent3d,
+    pub gizmo: &'a mut Gizmo,
+    pub world: &'a mut World,
+    pub selected_entity: &'a mut Option<hecs::Entity>,
+    pub viewport_mode: &'a mut ViewportMode,
+    pub undo_stack: &'a mut Vec<UndoableAction>,
+    pub signal: &'a mut Signal,
+    pub gizmo_mode: &'a mut EnumSet<GizmoMode>,
+    pub editor_mode: &'a mut EditorState,
+    pub active_camera: &'a mut Option<hecs::Entity>,
+}
+
+impl<'a> EditorTabViewer<'a> {
+    fn spawn_entity_at_pos(
+        &mut self,
+        asset: &DraggedAsset,
+        position: DVec3,
+        properties: Option<ModelProperties>,
+    ) -> anyhow::Result<()> {
+        let mut transform = Transform::default();
+        transform.position = position;
+        {
+            let mut pending_spawns = PENDING_SPAWNS.lock();
+            if let Some(props) = properties {
+                pending_spawns.push(PendingSpawn {
+                    asset_path: asset.path.clone(),
+                    asset_name: asset.name.clone(),
+                    transform,
+                    properties: props,
+                });
+            } else {
+                pending_spawns.push(PendingSpawn {
+                    asset_path: asset.path.clone(),
+                    asset_name: asset.name.clone(),
+                    transform,
+                    properties: ModelProperties::default(),
+                });
+            }
+            Ok(())
+        }
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct DraggedAsset {
+    pub name: String,
+    pub path: PathBuf,
+}
+
+pub static TABS_GLOBAL: LazyLock<Mutex<StaticallyKept>> =
+    LazyLock::new(|| Mutex::new(StaticallyKept::default()));
+
+
+/// Variables kept statically. 
+/// 
+/// The entire module (including the tab viewer) due to it
+/// being part of an update/render function, therefore this is used to ensure
+/// progress is not lost. 
+#[derive(Default)]
+pub struct StaticallyKept {
+    show_context_menu: bool,
+    context_menu_pos: egui::Pos2,
+    context_menu_tab: Option<EditorTab>,
+    is_focused: bool,
+    old_pos: Transform,
+    pub(crate) scale_locked: bool,
+
+    pub(crate) old_label_entity: Option<hecs::Entity>,
+    pub(crate) label_original: Option<String>,
+    pub(crate) label_last_edit: Option<Instant>,
+
+    pub(crate) transform_old_entity: Option<hecs::Entity>,
+    pub(crate) transform_original_transform: Option<Transform>,
+
+    pub(crate) transform_in_progress: bool,
+}
+
+impl StaticallyKept {}
+
+impl<'a> TabViewer for EditorTabViewer<'a> {
+    type Tab = EditorTab;
+
+    fn title(&mut self, tab: &mut Self::Tab) -> egui::WidgetText {
+        match tab {
+            EditorTab::Viewport => "Viewport".into(),
+            EditorTab::ModelEntityList => "Model/Entity List".into(),
+            EditorTab::AssetViewer => "Asset Viewer".into(),
+            EditorTab::ResourceInspector => "Resource Inspector".into(),
+        }
+    }
+
+    fn ui(&mut self, ui: &mut egui::Ui, tab: &mut Self::Tab) {
+        let mut cfg = TABS_GLOBAL.lock();
+        
+        ui.ctx().input(|i| {
+            if i.pointer.button_pressed(egui::PointerButton::Secondary) {
+                if let Some(pos) = i.pointer.hover_pos() {
+                    if ui.available_rect_before_wrap().contains(pos) {
+                        cfg.show_context_menu = true;
+                        cfg.context_menu_pos = pos;
+                        cfg.context_menu_tab = Some(tab.clone());
+                    }
+                }
+            }
+        });
+
+        match tab {
+            EditorTab::Viewport => {
+                // ------------------- Template for querying active camera -----------------
+                // if let Some(active_camera) = self.active_camera {
+                //     if let Ok(mut q) = self.world.query_one::<(&Camera, &CameraComponent, Option<&CameraFollowTarget>)>(*active_camera) {
+                //         if let Some((camera, component, follow_target)) = q.get() {
+
+                //         } else {
+                //             log_once::warn_once!("Unable to fetch the query result of camera: {:?}", active_camera)
+                //         }
+                //     } else {
+                //         log_once::warn_once!("Unable to query camera, component and option<camerafollowtarget> for active camera: {:?}", active_camera);
+                //     }
+                // } else {
+                //     log_once::warn_once!("No active camera found");
+                // }
+                // -------------------------------------------------------------------------
+
+                let available_rect = ui.available_rect_before_wrap();
+                let available_size = available_rect.size();
+                
+                let tex_aspect = self.tex_size.width as f32 / self.tex_size.height as f32;
+                let available_aspect = available_size.x / available_size.y;
+                
+                let (display_width, display_height) = if available_aspect > tex_aspect {
+                    let height = available_size.y * 0.95;
+                    let width = height * tex_aspect;
+                    (width, height)
+                } else {
+                    let width = available_size.x * 0.95;
+                    let height = width / tex_aspect;
+                    (width, height)
+                };
+
+                let center_x = available_rect.center().x;
+                let center_y = available_rect.center().y;
+                
+                let image_rect = egui::Rect::from_center_size(
+                    egui::pos2(center_x, center_y),
+                    egui::vec2(display_width, display_height)
+                );
+
+                let (_rect, _response) = ui.allocate_exact_size(
+                    available_size,
+                    egui::Sense::click_and_drag()
+                );
+
+                let _image_response = ui.allocate_rect(image_rect, egui::Sense::click_and_drag());
+
+                ui.scope_builder(egui::UiBuilder::new().max_rect(image_rect), |ui| {
+                    ui.add_sized(
+                        [display_width, display_height],
+                        egui::Image::new((
+                            self.view,
+                            [display_width, display_height].into(),
+                        ))
+                        .fit_to_exact_size([display_width, display_height].into())
+                    )
+                });
+
+                let image_response = ui.interact(image_rect, ui.id().with("viewport_image"), egui::Sense::click_and_drag());
+
+                if image_response.clicked() {
+                    if let Some(active_camera) = self.active_camera {
+                        if let Ok(mut q) = self.world.query_one::<(&Camera, &CameraComponent, Option<&CameraFollowTarget>)>(*active_camera) {
+                            if let Some((camera, _, _)) = q.get() {
+                                if let Some(click_pos) = ui.ctx().input(|i| i.pointer.interact_pos()) {
+                                let viewport_rect = image_response.rect;
+                                let local_pos = click_pos - viewport_rect.min;
+
+                                let ndc_x = (2.0 * local_pos.x / viewport_rect.width()) - 1.0;
+                                let ndc_y = 1.0 - (2.0 * local_pos.y / viewport_rect.height());
+
+                                let view_matrix =
+                                    glam::DMat4::look_at_lh(camera.eye, camera.target, camera.up);
+
+                                let proj_matrix = glam::DMat4::perspective_lh(
+                                    camera.fov_y.to_radians(),
+                                    camera.aspect,
+                                    camera.znear,
+                                    camera.zfar,
+                                );
+
+                                if !view_matrix.is_finite() {
+                                    log::error!("Invalid view matrix");
+                                    return;
+                                }
+                                if !proj_matrix.is_finite() {
+                                    log::error!("Invalid projection matrix");
+                                    return;
+                                }
+
+                                let view_proj = proj_matrix * view_matrix;
+                                let inv_view_proj = view_proj.inverse();
+
+                                if !inv_view_proj.is_finite() {
+                                    log::error!("Cannot invert view-projection matrix");
+                                    return;
+                                }
+
+                                let ray_start_ndc = glam::DVec4::new(ndc_x as f64, ndc_y as f64, 0.0, 1.0);
+                                let ray_end_ndc = glam::DVec4::new(ndc_x as f64, ndc_y as f64, 1.0, 1.0);
+
+                                let ray_start_world = inv_view_proj * ray_start_ndc;
+                                let ray_end_world = inv_view_proj * ray_end_ndc;
+
+                                if ray_start_world.w == 0.0 || ray_end_world.w == 0.0 {
+                                    log::error!("Invalid homogeneous coordinates");
+                                    return;
+                                }
+
+                                let ray_start = ray_start_world.truncate() / ray_start_world.w;
+                                let ray_end = ray_end_world.truncate() / ray_end_world.w;
+
+                                if !ray_start.is_finite() || !ray_end.is_finite() {
+                                    log::error!(
+                                        "Invalid ray points - start: {:?}, end: {:?}",
+                                        ray_start,
+                                        ray_end
+                                    );
+                                    return;
+                                }
+
+                                let ray_direction = (ray_end - ray_start).normalize();
+
+                                if !ray_direction.is_finite() {
+                                    log::error!("Invalid ray direction: {:?}", ray_direction);
+                                    return;
+                                }
+
+                                let mut closest_distance = f64::INFINITY;
+                                let mut selected_entity_id: Option<hecs::Entity> = None;
+                                let mut entity_count = 0;
+
+                                for (entity_id, (transform, _)) in
+                                    self.world.query::<(&Transform, &AdoptedEntity)>().iter()
+                                {
+                                    entity_count += 1;
+                                    let entity_pos = transform.position;
+                                    let sphere_radius = transform.scale.max_element() * 1.5;
+
+                                    let to_sphere = entity_pos - ray_start;
+                                    let projection = to_sphere.dot(ray_direction);
+
+                                    if projection > 0.0 {
+                                        let closest_point = ray_start + ray_direction * projection;
+                                        let distance_to_sphere = (closest_point - entity_pos).length();
+
+                                        if distance_to_sphere <= sphere_radius {
+                                            let discriminant = sphere_radius * sphere_radius
+                                                - distance_to_sphere * distance_to_sphere;
+                                            if discriminant >= 0.0 {
+                                                let intersection_distance =
+                                                    projection - discriminant.sqrt();
+
+                                                if intersection_distance < closest_distance {
+                                                    closest_distance = intersection_distance;
+                                                    selected_entity_id = Some(entity_id);
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+
+                                log::debug!("Total entities checked: {}", entity_count);
+
+                                if !matches!(self.editor_mode, EditorState::Playing) {
+                                    if let Some(entity_id) = selected_entity_id {
+                                        *self.selected_entity = Some(entity_id);
+                                        log::debug!("Selected entity: {:?}", entity_id);
+                                    } else {
+                                        // *self.selected_entity = None;
+                                        if entity_count == 0 {
+                                            log::debug!("No entities in world to select");
+                                        } else {
+                                            log::debug!(
+                                                "No entity hit by ray (checked {} entities)",
+                                                entity_count
+                                            );
+                                        }
+                                    }
+                                }
+                            }
+                            } else {
+                                log_once::warn_once!("Unable to fetch the query result of camera: {:?}", active_camera)
+                            }
+                        } else {
+                            log_once::warn_once!("Unable to query camera, component and option<camerafollowtarget> for active camera: {:?}", active_camera);
+                        }
+                    } else {
+                        log_once::warn_once!("No active camera found");
+                    }
+                    
+                }
+
+                let snapping = ui.input(|input| input.modifiers.shift);
+
+                // Note to self: fuck you >:(
+                // Note to self: ok wow thats pretty rude im trying my best >﹏<
+                // Note to self: finally holy shit i got it working
+
+                if let Some(active_camera) = self.active_camera {
+                    if let Ok(mut q) = self.world.query_one::<(&Camera, &CameraComponent, Option<&CameraFollowTarget>)>(*active_camera) {
+                        if let Some((camera, _, _)) = q.get() {
+                            self.gizmo.update_config(GizmoConfig {
+                            view_matrix: DMat4::look_at_lh(
+                                DVec3::new(
+                                    camera.eye.x as f64,
+                                    camera.eye.y as f64,
+                                    camera.eye.z as f64,
+                                ),
+                                DVec3::new(
+                                    camera.target.x as f64,
+                                    camera.target.y as f64,
+                                    camera.target.z as f64,
+                                ),
+                                DVec3::new(camera.up.x as f64, camera.up.y as f64, camera.up.z as f64),
+                            )
+                            .into(),
+                            projection_matrix: DMat4::perspective_infinite_reverse_lh(
+                                camera.fov_y as f64,
+                                display_width as f64 / display_height as f64,
+                                camera.znear as f64,
+                            )
+                            .into(),
+                            viewport: image_rect,
+                            modes: *self.gizmo_mode,
+                            orientation: transform_gizmo_egui::GizmoOrientation::Global,
+                            snapping,
+                            snap_distance: 1.0,
+                            ..Default::default()
+                        });
+                        } else {
+                            log_once::warn_once!("Unable to fetch the query result of camera: {:?}", active_camera)
+                        }
+                    } else {
+                        log_once::warn_once!("Unable to query camera, component and option<camerafollowtarget> for active camera: {:?}", active_camera);
+                    }
+                } else {
+                    log_once::warn_once!("No active camera found");
+                }
+
+                if !matches!(self.viewport_mode, ViewportMode::None) {
+                    if let Some(entity_id) = self.selected_entity {
+                        if let Ok(transform) =
+                            self.world.query_one_mut::<&mut Transform>(*entity_id)
+                        {
+                            let was_focused = cfg.is_focused;
+                            cfg.is_focused = self.gizmo.is_focused();
+
+                            if cfg.is_focused && !was_focused {
+                                cfg.old_pos = *transform;
+                            }
+
+                            let gizmo_transform =
+                                transform_gizmo_egui::math::Transform::from_scale_rotation_translation(
+                                    transform.scale,
+                                    transform.rotation,
+                                    transform.position,
+                                );
+
+                            if let Some((_result, new_transforms)) =
+                                self.gizmo.interact(ui, &[gizmo_transform])
+                            {
+                                if let Some(new_transform) = new_transforms.first() {
+                                    transform.position = new_transform.translation.into();
+                                    transform.rotation = new_transform.rotation.into();
+                                    transform.scale = new_transform.scale.into();
+                                }
+                            }
+
+                            if was_focused && !cfg.is_focused {
+                                let transform_changed = cfg.old_pos.position != transform.position
+                                    || cfg.old_pos.rotation != transform.rotation
+                                    || cfg.old_pos.scale != transform.scale;
+
+                                if transform_changed {
+                                    UndoableAction::push_to_undo(
+                                        &mut self.undo_stack,
+                                        UndoableAction::Transform(
+                                            entity_id.clone(),
+                                            cfg.old_pos.clone(),
+                                        ),
+                                    );
+                                    log::debug!("Pushed transform action to stack");
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            EditorTab::ModelEntityList => {
+                ui.label("Model/Entity List");
+                show_entity_tree(
+                    ui,
+                    &mut self.nodes,
+                    &mut self.selected_entity,
+                    "Model Entity Asset List",
+                );
+            }
+            EditorTab::AssetViewer => {
+                egui_extras::install_image_loaders(ui.ctx());
+
+                let mut assets: Vec<(String, String, PathBuf, ResourceType)> = Vec::new();
+                {
+                    let res = RESOURCES.read();
+                    egui_extras::install_image_loaders(ui.ctx());
+
+                    fn recursive_search_nodes_and_attach_thumbnail(
+                        res: &Vec<Node>,
+                        assets: &mut Vec<(String, String, PathBuf, ResourceType)>,
+                        logged: &mut HashSet<String>,
+                    ) {
+                        for node in res {
+                            match node {
+                                Node::File(file) => {
+                                    if !logged.contains(&file.name) {
+                                        logged.insert(file.name.clone());
+                                        log::debug!(
+                                            "Adding image for {} of type {}",
+                                            file.name,
+                                            file.resource_type.as_ref().unwrap()
+                                        );
+                                    }
+                                    if let Some(ref res_type) = file.resource_type {
+                                        match res_type {
+                                            ResourceType::Model => {
+                                                let ad_dir = app_dirs2::get_app_root(
+                                                    app_dirs2::AppDataType::UserData,
+                                                    &APP_INFO,
+                                                )
+                                                .unwrap();
+
+                                                let model_thumbnail =
+                                                    ad_dir.join(format!("{}.png", file.name));
+
+                                                if !model_thumbnail.exists() {
+                                                    // gen image
+                                                    log::debug!(
+                                                        "Model thumbnail [{}] does not exist, generating one now",
+                                                        file.name
+                                                    );
+                                                    let mut model = match model_to_image::ModelToImageBuilder::new(&file.path)
+                                                    .with_size((600, 600))
+                                                    .build() {
+                                                        Ok(v) => v,
+                                                        Err(e) => panic!("Error occurred while loading file from path: {}", e),
+                                                    };
+                                                    if let Err(e) =
+                                                        model.render().unwrap().write_to(Some(
+                                                            &ad_dir
+                                                                .join(format!("{}.png", file.name)),
+                                                        ))
+                                                    {
+                                                        log::error!(
+                                                            "Failed to write model thumbnail for {}: {}",
+                                                            file.name,
+                                                            e
+                                                        );
+                                                    }
+                                                }
+
+                                                let image_uri =
+                                                    model_thumbnail.to_string_lossy().to_string();
+
+                                                assets.push((
+                                                    format!("file://{}", image_uri),
+                                                    file.name.clone(),
+                                                    file.path.clone(),
+                                                    res_type.clone(),
+                                                ))
+                                            }
+                                            ResourceType::Texture => assets.push((
+                                                format!(
+                                                    "file://{}",
+                                                    file.path.to_string_lossy().to_string()
+                                                ),
+                                                file.name.clone(),
+                                                file.path.clone(),
+                                                res_type.clone(),
+                                            )),
+                                            _ => {
+                                                if file
+                                                    .path
+                                                    .clone()
+                                                    .extension()
+                                                    .unwrap()
+                                                    .to_str()
+                                                    .unwrap()
+                                                    .contains("euc")
+                                                {
+                                                    continue;
+                                                }
+                                                assets.push((
+                                                    "NO_TEXTURE".into(),
+                                                    file.name.clone(),
+                                                    file.path.clone(),
+                                                    res_type.clone(),
+                                                ))
+                                            }
+                                        }
+                                    }
+                                }
+                                Node::Folder(folder) => {
+                                    recursive_search_nodes_and_attach_thumbnail(
+                                        &folder.nodes,
+                                        assets,
+                                        logged,
+                                    )
+                                }
+                            }
+                        }
+                    }
+
+                    let mut logged = LOGGED.lock();
+                    recursive_search_nodes_and_attach_thumbnail(
+                        &res.nodes,
+                        &mut assets,
+                        &mut logged,
+                    );
+                }
+
+                egui::ScrollArea::vertical().show(ui, |ui| {
+                    let max_columns = 6;
+                    let available_width = ui.clip_rect().width() - ui.spacing().indent;
+                    let margin = 16.0;
+                    let usable_width = available_width - margin;
+                    let label_height = 20.0;
+                    let padding = 8.0;
+                    let min_card_width = 60.0;
+
+                    let mut columns = max_columns;
+                    for test_columns in (1..=max_columns).rev() {
+                        let card_width = usable_width / test_columns as f32;
+                        if card_width >= min_card_width {
+                            columns = test_columns;
+                            break;
+                        }
+                    }
+
+                    let card_width = usable_width / columns as f32;
+                    let image_size = (card_width - label_height - padding).max(32.0);
+                    let card_height = image_size + label_height + padding;
+
+                    for row_start in (0..assets.len()).step_by(columns) {
+                        let row_end = (row_start + columns).min(assets.len());
+                        let row_items = &mut assets[row_start..row_end];
+
+                        ui.horizontal(|ui| {
+                            ui.set_max_width(usable_width);
+
+                            egui_dnd::dnd(ui, format!("asset_row_{}", row_start / columns))
+                                .show_vec(
+                                    row_items,
+                                    |ui, (image, asset_name, asset_path, asset_type), handle, state| {
+                                        let card_size = egui::vec2(card_width, card_height);
+                                        handle.ui(ui, |ui| {
+                                            let (rect, card_response) = ui.allocate_exact_size(
+                                                card_size,
+                                                egui::Sense::click(),
+                                            );
+
+                                            let mut card_ui = ui.new_child(
+                                                egui::UiBuilder::new().max_rect(rect).layout(
+                                                    egui::Layout::top_down(egui::Align::Center),
+                                                ),
+                                            );
+
+                                            let image_response = card_ui.add_sized(
+                                                [image_size, image_size],
+                                                egui::ImageButton::new(image.clone()).frame(false),
+                                            );
+
+                                            let is_hovered = card_response.hovered() || image_response.hovered() || state.dragged;
+                                            let is_d_clicked = card_response.double_clicked() || image_response.double_clicked();
+
+                                            if is_d_clicked {
+                                                if matches!(asset_type, ResourceType::Model) {
+                                                    let mut spawn_position = DVec3::default();
+                                                    if let Some(active_camera) = self.active_camera {
+                                                        if let Ok(mut q) = self.world.query_one::<(&Camera, &CameraComponent, Option<&CameraFollowTarget>)>(*active_camera) {
+                                                            if let Some((camera, _, _)) = q.get() {
+                                                                spawn_position = camera.eye;
+                                                            } else {
+                                                                log_once::warn_once!("Unable to fetch the query result of camera: {:?}", active_camera)
+                                                            }
+                                                        } else {
+                                                            log_once::warn_once!("Unable to query camera, component and option<camerafollowtarget> for active camera: {:?}", active_camera);
+                                                        }
+                                                    } else {
+                                                        log_once::warn_once!("No active camera found");
+                                                    }
+
+                                                    let asset = DraggedAsset {
+                                                        name: asset_name.clone(),
+                                                        path: asset_path.clone(),
+                                                        // asset_type: asset_type.clone(),
+                                                    };
+
+                                                    match self.spawn_entity_at_pos(&asset, spawn_position, None) {
+                                                        Ok(()) => {
+                                                            log::debug!("double click spawned {} at camera pos {:?}",
+                                                                asset.name, spawn_position
+                                                            );
+
+                                                            success!("Spawned {} at camera", asset.name);
+                                                        }
+                                                        Err(e) => {
+                                                            log::error!(
+                                                            "Failed to spawn {} at camera: {}",
+                                                            asset.name,
+                                                            e);
+
+                                                            fatal!("Failed to spawn {}: {}",
+                                                                        asset.name, e);
+                                                        }
+                                                    }
+                                                }
+                                            }
+
+                                            if is_hovered || state.dragged {
+                                                ui.painter().rect_filled(
+                                                    rect,
+                                                    6.0,
+                                                    if state.dragged {
+                                                        egui::Color32::from_rgb(80, 80, 100)
+                                                    } else {
+                                                        egui::Color32::from_rgb(60, 60, 80)
+                                                    },
+                                                );
+                                            }
+
+                                            card_ui.vertical_centered(|ui| {
+                                                ui.label(
+                                                    egui::RichText::new(asset_name.clone())
+                                                        .strong()
+                                                        .color(egui::Color32::WHITE),
+                                                );
+                                            });
+                                        });
+                                    },
+                                );
+                        });
+                        ui.add_space(8.0);
+                    }
+                });
+            }
+            EditorTab::ResourceInspector => {
+                if let Some(entity) = self.selected_entity {
+                    if let Ok((e, transform, _props, script, camera, camera_component, follow_target)) = self
+                    .world
+                    .query_one_mut::<(&mut AdoptedEntity, Option<&mut Transform>, Option<&ModelProperties>, Option<&mut ScriptComponent>, Option<&mut Camera>, Option<&mut CameraComponent>, Option<&mut CameraFollowTarget>)>(*entity) {
+                        e.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, &mut String::new());
+                        if let Some(t) = transform {
+                            t.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, e.label_mut());
+                        }
+
+                        if let (Some(camera), Some(camera_component)) = (camera, camera_component) {
+                            ui.separator();
+                            ui.label("Camera Components:");
+                            camera.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, &mut String::new());
+                            camera_component.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, &mut camera.label.clone());
+                            
+                            if let Some(target) = follow_target {
+                                target.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, &mut camera.label.clone());
+                            }
+                        }
+
+                        // if let Some(props) = _props {
+                        //     props.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, e.label_mut());
+                        // }
+
+                        if let Some(script) = script {
+                            script.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, e.label_mut());
+                        }
+
+                        if let Some(t) = cfg.label_last_edit {
+                            if t.elapsed() >= Duration::from_millis(500) {
+                                if let Some(ent) = cfg.old_label_entity.take() {
+                                    if let Some(orig) = cfg.label_original.take() {
+                                        UndoableAction::push_to_undo(
+                                            &mut self.undo_stack,
+                                            UndoableAction::Label(ent, orig, EntityType::Entity),
+                                        );
+                                        log::debug!("Pushed label change to undo stack after 500ms debounce period");
+                                    }
+                                }
+                                cfg.label_last_edit = None;
+                            }
+                        }
+                    } else {
+                        log_once::debug_once!("Unable to query entity inside resource inspector");
+                    }
+                    
+                    if let Ok((light, transform, props)) = self.world.query_one_mut::<(&mut Light, &mut Transform, &mut LightComponent)>(*entity) {
+                            light.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, &mut String::new());
+                            transform.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, &mut light.label);
+                            props.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, &mut light.label);
+                            if let Some(t) = cfg.label_last_edit {
+                                if t.elapsed() >= Duration::from_millis(500) {
+                                    if let Some(ent) = cfg.old_label_entity.take() {
+                                        if let Some(orig) = cfg.label_original.take() {
+                                            UndoableAction::push_to_undo(
+                                                &mut self.undo_stack,
+                                                UndoableAction::Label(ent, orig, EntityType::Light),
+                                            );
+                                            log::debug!("Pushed label change to undo stack after 500ms debounce period");
+                                        }
+                                    }
+                                    cfg.label_last_edit = None;
+                                }
+                            }
+                    } else {
+                        log_once::debug_once!("Unable to query light inside resource inspector");
+                    }
+
+                    if let Ok((camera, camera_component, follow_target)) = self.world.query_one_mut::<(&mut Camera, &mut CameraComponent, Option<&mut CameraFollowTarget>)>(*entity) {
+                        camera.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, &mut String::new());
+                        camera_component.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, &mut camera.label.clone());
+                        if let Some(target) = follow_target {
+                            target.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, &mut camera.label.clone());
+                        }
+
+                        ui.separator();
+                        ui.label("Camera Controls:");
+                        if ui.button("Set as Active Camera").clicked() {
+                            *self.active_camera = Some(*entity);
+                            log::info!("Set camera '{}' as active", camera.label);
+                        }
+                        
+                        if matches!(self.editor_mode, EditorState::Editing) {
+                            if ui.button("Switch to This Camera").clicked() {
+                                *self.active_camera = Some(*entity);
+                                log::info!("Switched to camera '{}'", camera.label);
+                            }
+                        }
+                    }
+                } else {
+                    ui.label("No entity selected, therefore no info to provide. Go on, what are you waiting for? Click an entity!");
+                }
+            }
+        }
+
+        let mut menu_action: Option<EditorTabMenuAction> = None;
+        let area = egui::Area::new("context_menu".into())
+            .fixed_pos(cfg.context_menu_pos)
+            .order(egui::Order::Foreground);
+
+        if cfg.show_context_menu {
+            let menu_tab = cfg
+                .context_menu_tab
+                .clone()
+                .unwrap_or(EditorTab::ModelEntityList);
+
+            let mut popup_rect = None;
+
+            area.show(ui.ctx(), |ui| {
+                egui::Frame::popup(ui.style()).show(ui, |ui| {
+                    popup_rect.replace(ui.max_rect());
+
+                    match menu_tab {
+                        EditorTab::AssetViewer => {
+                            ui.set_min_width(150.0);
+                            if ui.selectable_label(false, "Import resource").clicked() {
+                                menu_action = Some(EditorTabMenuAction::ImportResource);
+                            }
+                            if ui.selectable_label(false, "Refresh assets").clicked() {
+                                menu_action = Some(EditorTabMenuAction::RefreshAssets);
+                            }
+                        }
+                        EditorTab::ModelEntityList => {
+                            ui.set_min_width(150.0);
+                            if ui.selectable_label(false, "Add Entity").clicked() {
+                                menu_action = Some(EditorTabMenuAction::AddEntity);
+                            }
+                            if ui.selectable_label(false, "Delete Entity").clicked() {
+                                menu_action = Some(EditorTabMenuAction::DeleteEntity);
+                            }
+                        }
+                        EditorTab::ResourceInspector => {
+                            ui.set_min_width(150.0);
+                            if ui.selectable_label(false, "Add Component").clicked() {
+                                menu_action = Some(EditorTabMenuAction::AddComponent);
+                            }
+                        }
+                        EditorTab::Viewport => {
+                            ui.set_min_width(150.0);
+                            if ui.selectable_label(false, "Viewport Option").clicked() {
+                                menu_action = Some(EditorTabMenuAction::ViewportOption);
+                            }
+                        }
+                    }
+                })
+            });
+
+            if let Some(action) = menu_action {
+                if Some(tab.clone()) == cfg.context_menu_tab {
+                    match action {
+                        EditorTabMenuAction::ImportResource => {
+                            log::debug!("Import Resource clicked");
+
+                            match import_object() {
+                                Ok(_) => {
+                                    success!("Resource(s) imported successfully!");
+                                }
+                                Err(e) => {
+                                    warn!("Failed to import resource(s): {e}");
+                                }
+                            }
+                            cfg.show_context_menu = false;
+                            cfg.context_menu_tab = None;
+                            return;
+                        }
+                        EditorTabMenuAction::RefreshAssets => {
+                            log::debug!("Refresh assets clicked");
+                            {
+                                let mut res = RESOURCES.write();
+                                match res.update_mem() {
+                                    Ok(res_cfg) => {
+                                        *res = res_cfg;
+                                        success!("Assets refreshed successfully!");
+                                    }
+                                    Err(e) => {
+                                        fatal!("Failed to refresh assets: {}", e);
+                                    }
+                                }
+                            }
+                            cfg.show_context_menu = false;
+                            cfg.context_menu_tab = None;
+                            return;
+                        }
+                        EditorTabMenuAction::AddEntity => {
+                            log::debug!("Add Entity clicked");
+                            *self.signal = Signal::CreateEntity;
+                            cfg.show_context_menu = false;
+                            cfg.context_menu_tab = None;
+                            return;
+                        }
+                        EditorTabMenuAction::DeleteEntity => {
+                            log::debug!("Delete Entity clicked");
+                            *self.signal = Signal::Delete;
+                            cfg.show_context_menu = false;
+                            cfg.context_menu_tab = None;
+                            return;
+                        }
+                        EditorTabMenuAction::AddComponent => {
+                            log::debug!("Add Component clicked");
+                            if let Some(entity) = self.selected_entity {
+                                if let Ok(..) = self.world.query_one_mut::<&AdoptedEntity>(*entity) {
+                                    log::debug!("Queried selected entity, it is an entity");
+                                    *self.signal = Signal::AddComponent(*entity, EntityType::Entity);
+                                }
+
+                                if let Ok(..) = self.world.query_one_mut::<&Light>(*entity) {
+                                    log::debug!("Queried selected entity, it is a light");
+                                    *self.signal = Signal::AddComponent(*entity, EntityType::Light);
+                                }
+                            } else {
+                                warn!("What are you adding a component to? Theres no entity selected...");
+                            }
+
+                            cfg.show_context_menu = false;
+                            cfg.context_menu_tab = None;
+                            return;
+                        }
+                        EditorTabMenuAction::ViewportOption => {
+                            log::debug!("Viewport Option clicked");
+                            cfg.show_context_menu = false;
+                            cfg.context_menu_tab = None;
+                            return;
+                        },
+                        EditorTabMenuAction::RemoveComponent => {
+                            log::debug!("Remove Component clicked");
+                            if let Some(entity) = self.selected_entity {
+                                if let Ok(script) = self.world.query_one_mut::<&ScriptComponent>(*entity) {
+                                    log::debug!("Queried selected entity, it has a script component");
+                                    *self.signal = Signal::RemoveComponent(*entity, ComponentType::Script(script.clone()));
+                                } else {
+                                    warn!("Selected entity does not have a script component to remove");
+                                }
+                            } else {
+                                panic!("Paradoxical error: Cannot remove a component when its not selected...");
+                            }
+
+                            cfg.show_context_menu = false;
+                            cfg.context_menu_tab = None;
+                            return;
+                        }
+                    }
+                }
+            }
+
+            if let Some(rect) = popup_rect {
+                if cfg.show_context_menu && Some(tab.clone()) == cfg.context_menu_tab {
+                    if ui
+                        .ctx()
+                        .input(|i| i.pointer.button_clicked(egui::PointerButton::Primary))
+                    {
+                        if let Some(pos) = ui.ctx().input(|i| i.pointer.interact_pos()) {
+                            if !rect.contains(pos) {
+                                cfg.show_context_menu = false;
+                                cfg.context_menu_tab = None;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+
+pub(crate) fn import_object() -> anyhow::Result<()> {
+    let model_ext = vec!["glb", "fbx", "obj"];
+    let texture_ext = vec!["png"];
+
+    let files = rfd::FileDialog::new()
+        .add_filter("All Files", &["*"])
+        .add_filter("Model", &model_ext)
+        .add_filter("Texture", &texture_ext)
+        .pick_files();
+    if let Some(files) = files {
+        for file in files {
+            let ext = file.extension().unwrap().to_str().unwrap();
+            let mut copied = false;
+            for mde in model_ext.iter() {
+                if ext.contains(mde) {
+                    // copy over to models folder
+                    {
+                        let project = PROJECT.read();
+                        let models_dir = PathBuf::from(project.project_path.clone())
+                            .join("resources")
+                            .join("models");
+                        if !models_dir.exists() {
+                            std::fs::create_dir_all(&models_dir)?;
+                        }
+                        let dest = models_dir.join(file.file_name().unwrap());
+                        std::fs::copy(&file, &dest)?;
+                        log::info!("Copied model file to {:?}", dest);
+                        copied = true;
+                    }
+                }
+            }
+            for tex in texture_ext.iter() {
+                if ext.contains(tex) {
+                    // copy over to textures folder
+                    {
+                        let project = PROJECT.read();
+                        let textures_dir = PathBuf::from(project.project_path.clone())
+                            .join("resources")
+                            .join("textures");
+                        if !textures_dir.exists() {
+                            std::fs::create_dir_all(&textures_dir)?;
+                        }
+                        let dest = textures_dir.join(file.file_name().unwrap());
+                        std::fs::copy(&file, &dest)?;
+                        log::info!("Copied texture file to {:?}", dest);
+                        copied = true;
+                    }
+                }
+            }
+
+            if !copied {
+                {
+                    let project = PROJECT.read();
+                    // everything else copies over to resources root dir
+                    let resources_dir =
+                        PathBuf::from(project.project_path.clone()).join("resources");
+                    if !resources_dir.exists() {
+                        std::fs::create_dir_all(&resources_dir)?;
+                    }
+                    let dest = resources_dir.join(file.file_name().unwrap());
+                    std::fs::copy(&file, &dest)?;
+                    log::info!("Copied other resource file to {:?}", dest);
+                }
+            }
+        }
+        // save it all to ensure the eucc recognises it
+        let mut proj = PROJECT.write();
+        proj.write_to_all()?;
+        Ok(())
+    } else {
+        return Err(anyhow::anyhow!("File dialogue returned None"));
+    }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum EditorTabMenuAction {
+    ImportResource,
+    RefreshAssets,
+    AddEntity,
+    DeleteEntity,
+    AddComponent,
+    RemoveComponent,
+    ViewportOption,
+}
diff --git a/eucalyptus-editor/src/editor/input.rs b/eucalyptus-editor/src/editor/input.rs
new file mode 100644
index 0000000..13b8204
--- /dev/null
+++ b/eucalyptus-editor/src/editor/input.rs
@@ -0,0 +1,287 @@
+use super::*;
+use dropbear_engine::{
+    entity::{AdoptedEntity, Transform},
+    input::{Controller, Keyboard, Mouse},
+};
+use gilrs::{Button, GamepadId};
+use log;
+use transform_gizmo_egui::GizmoMode;
+use winit::{
+    dpi::PhysicalPosition, event::MouseButton, event_loop::ActiveEventLoop, keyboard::KeyCode,
+};
+use eucalyptus_core::success_without_console;
+
+impl Keyboard for Editor {
+    fn key_down(&mut self, key: KeyCode, _event_loop: &ActiveEventLoop) {
+        #[cfg(not(target_os = "macos"))]
+        let ctrl_pressed = self.input_state.pressed_keys.contains(&KeyCode::ControlLeft)
+            || self.input_state.pressed_keys.contains(&KeyCode::ControlRight);
+        #[cfg(target_os = "macos")]
+        let ctrl_pressed = self.input_state.pressed_keys.contains(&KeyCode::SuperLeft)
+            || self.input_state.pressed_keys.contains(&KeyCode::SuperRight);
+
+        let _alt_pressed = self.input_state.pressed_keys.contains(&KeyCode::AltLeft)
+            || self.input_state.pressed_keys.contains(&KeyCode::AltRight);
+
+        let shift_pressed = self.input_state.pressed_keys.contains(&KeyCode::ShiftLeft)
+            || self.input_state.pressed_keys.contains(&KeyCode::ShiftRight);
+
+        let is_double_press = self.double_key_pressed(key);
+
+        let is_playing = matches!(self.editor_state, EditorState::Playing);
+
+        match key {
+            KeyCode::KeyG => {
+                if self.is_viewport_focused && !is_playing {
+                    self.viewport_mode = ViewportMode::Gizmo;
+                    info!("Switched to Viewport::Gizmo");
+
+                    if let Some(window) = &self.window {
+                        window.set_cursor_visible(true);
+                    }
+                } else {
+                    self.input_state.pressed_keys.insert(key);
+                }
+            }
+            KeyCode::KeyF => {
+                if self.is_viewport_focused && !is_playing {
+                    self.viewport_mode = ViewportMode::CameraMove;
+                    info!("Switched to Viewport::CameraMove");
+                    if let Some(window) = &self.window {
+                        window.set_cursor_visible(false);
+
+                        let size = window.inner_size();
+                        let center = winit::dpi::PhysicalPosition::new(
+                            size.width as f64 / 2.0,
+                            size.height as f64 / 2.0,
+                        );
+                        let _ = window.set_cursor_position(center);
+                    }
+                } else {
+                    self.input_state.pressed_keys.insert(key);
+                }
+            }
+            KeyCode::Delete => {
+                if !is_playing {
+                    if let Some(_) = &self.selected_entity {
+                        self.signal = Signal::Delete;
+                    } else {
+                        warn!("Failed to delete: No entity selected");
+                    }
+                } else {
+                    self.input_state.pressed_keys.insert(key);
+                }
+            }
+            KeyCode::Escape => {
+                if is_double_press {
+                    if let Some(_) = &self.selected_entity {
+                        self.selected_entity = None;
+                        log::debug!("Deselected entity");
+                    }
+                } else if self.is_viewport_focused && !is_playing {
+                    self.viewport_mode = ViewportMode::None;
+                    info!("Switched to Viewport::None");
+                    if let Some(window) = &self.window {
+                        window.set_cursor_visible(true);
+                    }
+                } else {
+                    self.input_state.pressed_keys.insert(key);
+                }
+            }
+            KeyCode::KeyQ => {
+                if ctrl_pressed && !is_playing {
+                    match self.save_project_config() {
+                        Ok(_) => {}
+                        Err(e) => {
+                            fatal!("Error saving project: {}", e);
+                        }
+                    }
+                    log::info!("Successfully saved project, about to quit...");
+                    success_without_console!("Successfully saved project");
+                    self.scene_command = SceneCommand::Quit;
+                } else if is_playing {
+                    warn!("Unable to save-quit project, please pause your playing state, then try again");
+                }
+            }
+            KeyCode::KeyC => {
+                if ctrl_pressed && !is_playing {
+                    if let Some(entity) = &self.selected_entity {
+                        let query = self
+                            .world
+                            .query_one::<(&AdoptedEntity, &Transform, &ModelProperties)>(*entity);
+                        if let Ok(mut q) = query {
+                            if let Some((e, t, props)) = q.get() {
+                                let s_entity = SceneEntity {
+                                    model_path: e.model().path.clone(),
+                                    label: e.model().label.clone(),
+                                    transform: *t,
+                                    properties: props.clone(),
+                                    script: None,
+                                    entity_id: None,
+                                };
+                                self.signal = Signal::Copy(s_entity);
+
+                                info!("Copied!");
+
+                                log::debug!("Copied selected entity");
+                            } else {
+                                warn!(
+                                    "Unable to copy entity: Unable to fetch world entity properties"
+                                );
+                            }
+                        } else {
+                            warn!("Unable to copy entity: Unable to obtain lock");
+                        }
+                    } else {
+                        warn!("Unable to copy entity: None selected");
+                    }
+                } else if matches!(self.viewport_mode, ViewportMode::Gizmo) {
+                    info!("GizmoMode set to scale");
+                    self.gizmo_mode = GizmoMode::all_scale();
+                } else {
+                    self.input_state.pressed_keys.insert(key);
+                }
+            }
+            KeyCode::KeyV => {
+                if ctrl_pressed && !is_playing {
+                    match &self.signal {
+                        Signal::Copy(entity) => {
+                            self.signal = Signal::Paste(entity.clone());
+                        }
+                        _ => {
+                            warn!("Unable to paste: You haven't selected anything!");
+                        }
+                    }
+                } 
+                else {
+                    self.input_state.pressed_keys.insert(key);
+                }
+            }
+            KeyCode::KeyS => {
+                if ctrl_pressed {
+                    if !is_playing {
+                        match self.save_project_config() {
+                            Ok(_) => {
+                                success!("Successfully saved project");
+                            }
+                            Err(e) => {
+                                fatal!("Error saving project: {}", e);
+                            }
+                        }
+                    } else {
+                        warn!("Unable to save project config, please quit your playing and try again");
+                    }
+                    
+                } else {
+                    self.input_state.pressed_keys.insert(key);
+                }
+            }
+            KeyCode::KeyZ => {
+                if ctrl_pressed && !is_playing {
+                    if shift_pressed {
+                        // redo
+                    } else {
+                        // undo
+                        log::debug!("Undo signal sent");
+                        self.signal = Signal::Undo;
+                    }
+                } else if matches!(self.viewport_mode, ViewportMode::Gizmo) && !is_playing {
+                    info!("GizmoMode set to translate");
+                    self.gizmo_mode = GizmoMode::all_translate();
+                } else {
+                    self.input_state.pressed_keys.insert(key);
+                }
+            }
+            KeyCode::F1 => {
+                if !is_playing {
+                    if self.is_using_debug_camera() {
+                        self.switch_to_player_camera();
+                    } else {
+                        self.switch_to_debug_camera();
+                    }
+                }
+            }
+            KeyCode::KeyX => {
+                if matches!(self.viewport_mode, ViewportMode::Gizmo) && !is_playing {
+                    info!("GizmoMode set to rotate");
+                    self.gizmo_mode = GizmoMode::all_rotate();
+                } else {
+                    self.input_state.pressed_keys.insert(key);
+                }
+            }
+            KeyCode::KeyP => {
+                if !is_playing {
+                    if ctrl_pressed {
+                        self.signal = Signal::Play
+                    }
+                }
+            }
+            _ => {
+                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 Editor {
+    fn mouse_move(&mut self, position: PhysicalPosition<f64>) {
+        if (self.is_viewport_focused
+            && matches!(self.viewport_mode, ViewportMode::CameraMove))
+            || (matches!(self.editor_state, EditorState::Playing) && !self.input_state.is_cursor_locked)
+        {
+            if let Some(window) = &self.window {
+                let size = window.inner_size();
+                let center =
+                    PhysicalPosition::new(size.width as f64 / 2.0, size.height as f64 / 2.0);
+
+                let dx = position.x - center.x;
+                let dy = position.y - center.y;
+                if let Some(active_camera) = self.active_camera {
+                    if let Ok(mut q) = self.world.query_one::<(&mut Camera, &CameraComponent, Option<&CameraFollowTarget>)>(active_camera) {
+                        if let Some((camera, _, _)) = q.get() {
+                            camera.track_mouse_delta(dx, dy);
+                        } else {
+                            log_once::warn_once!("Unable to fetch the query result of camera: {:?}", active_camera)
+                        }
+                    } else {
+                        log_once::warn_once!("Unable to query camera, component and option<camerafollowtarget> for active camera: {:?}", active_camera);
+                    }
+                } else {
+                    log_once::warn_once!("No active camera found");
+                }
+
+                let _ = window.set_cursor_position(center);
+                window.set_cursor_visible(false);
+            }
+        }
+        self.input_state.mouse_pos = (position.x, position.y);
+    }
+
+    fn mouse_down(&mut self, button: MouseButton) {
+        match button {
+            _ => { self.input_state.mouse_button.insert(button); }
+        }
+    }
+
+    fn mouse_up(&mut self, button: MouseButton) {
+        self.input_state.mouse_button.remove(&button);
+    }
+}
+
+impl Controller for Editor {
+    fn button_down(&mut self, _button: Button, _id: GamepadId) {}
+
+    fn button_up(&mut self, _button: Button, _id: GamepadId) {}
+
+    fn left_stick_changed(&mut self, _x: f32, _y: f32, _id: GamepadId) {}
+
+    fn right_stick_changed(&mut self, _x: f32, _y: f32, _id: GamepadId) {}
+
+    fn on_connect(&mut self, _id: GamepadId) {}
+
+    fn on_disconnect(&mut self, _id: GamepadId) {}
+}
diff --git a/eucalyptus-editor/src/editor/mod.rs b/eucalyptus-editor/src/editor/mod.rs
new file mode 100644
index 0000000..bc5e8ee
--- /dev/null
+++ b/eucalyptus-editor/src/editor/mod.rs
@@ -0,0 +1,892 @@
+pub mod dock;
+pub mod input;
+pub mod scene;
+pub mod component;
+
+pub(crate) use crate::editor::dock::*;
+
+use std::{
+    collections::{HashMap, HashSet},
+    path::PathBuf,
+    sync::{Arc, LazyLock},
+    time::{Duration, Instant},
+};
+
+use dropbear_engine::{
+    camera::Camera, entity::{AdoptedEntity, Transform}, graphics::Graphics, lighting::{Light, LightManager}, scene::SceneCommand
+};
+use egui::{self, Context};
+use egui_dock_fork::{DockArea, DockState, NodeIndex, Style};
+use hecs::{World};
+use log;
+use parking_lot::Mutex;
+use transform_gizmo_egui::{EnumSet, Gizmo, GizmoMode};
+use wgpu::{Color, Extent3d, RenderPipeline};
+use winit::{keyboard::KeyCode, window::Window};
+use eucalyptus_core::camera::{CameraAction, CameraComponent, CameraFollowTarget, CameraType, DebugCamera};
+use eucalyptus_core::{fatal, info, states, success, warn};
+use eucalyptus_core::scripting::input::InputState;
+use eucalyptus_core::scripting::{ScriptAction, ScriptManager};
+use eucalyptus_core::states::{CameraConfig, EditorTab, EntityNode, LightConfig, ModelProperties, SceneEntity, ScriptComponent, PROJECT, SCENES};
+use eucalyptus_core::utils::ViewportMode;
+use crate::build::build;
+use crate::camera::UndoableCameraAction;
+use crate::debug;
+
+pub struct Editor {
+    scene_command: SceneCommand,
+    world: World,
+    dock_state: DockState<EditorTab>,
+    texture_id: Option<egui::TextureId>,
+    size: Extent3d,
+    render_pipeline: Option<RenderPipeline>,
+    light_manager: LightManager,
+    color: Color,
+
+    active_camera: Option<hecs::Entity>,
+
+    is_viewport_focused: bool,
+    // is_cursor_locked: bool,
+    window: Option<Arc<Window>>,
+
+    show_new_project: bool,
+    project_name: String,
+    project_path: Option<PathBuf>,
+    pending_scene_switch: bool,
+
+    gizmo: Gizmo,
+    selected_entity: Option<hecs::Entity>,
+    viewport_mode: ViewportMode,
+
+    signal: Signal,
+    undo_stack: Vec<UndoableAction>,
+    // todo: add redo (later)
+    // redo_stack: Vec<UndoableAction>,
+
+    editor_state: EditorState,
+    gizmo_mode: EnumSet<GizmoMode>,
+
+    script_manager: ScriptManager,
+    play_mode_backup: Option<PlayModeBackup>,
+
+    input_state: InputState,
+}
+
+impl Editor {
+    pub fn new() -> Self {
+        let tabs = vec![EditorTab::Viewport];
+        let mut dock_state = DockState::new(tabs);
+
+        let surface = dock_state.main_surface_mut();
+        let [_old, right] =
+            surface.split_right(NodeIndex::root(), 0.25, vec![EditorTab::ModelEntityList]);
+        let [_old, _] =
+            surface.split_left(NodeIndex::root(), 0.20, vec![EditorTab::ResourceInspector]);
+        let [_old, _] = surface.split_below(right, 0.5, vec![EditorTab::AssetViewer]);
+
+        // this shit doesnt work :(
+        // nvm it works (sorta)
+        std::thread::spawn(move || {
+            loop {
+                std::thread::sleep(Duration::from_secs(1));
+                let deadlocks = parking_lot::deadlock::check_deadlock();
+                if deadlocks.is_empty() {
+                    continue;
+                }
+
+                log::error!("{} deadlocks detected", deadlocks.len());
+                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());
+                    }
+                }
+            }
+        });
+
+        Self {
+            scene_command: SceneCommand::None,
+            dock_state,
+            texture_id: None,
+            size: Extent3d::default(),
+            render_pipeline: None,
+            color: Color::default(),
+            is_viewport_focused: false,
+            // is_cursor_locked: false,
+            window: None,
+            world: World::new(),
+            show_new_project: false,
+            project_name: String::new(),
+            project_path: None,
+            pending_scene_switch: false,
+            gizmo: Gizmo::default(),
+            selected_entity: None,
+            viewport_mode: ViewportMode::None,
+            signal: Signal::None,
+            undo_stack: Vec::new(),
+            script_manager: ScriptManager::new().unwrap(),
+            editor_state: EditorState::Editing,
+            gizmo_mode: EnumSet::empty(),
+            play_mode_backup: None,
+            input_state: InputState::new(),
+            light_manager: LightManager::new(),
+            active_camera: None,
+            // ..Default::default()
+            // note to self: DO NOT USE ..DEFAULT::DEFAULT(), IT WILL CAUSE OVERFLOW
+        }
+    }
+
+    fn double_key_pressed(&mut self, key: KeyCode) -> bool {
+        let now = Instant::now();
+
+        if let Some(last_time) = self.input_state.last_key_press_times.get(&key) {
+            let time_diff = now.duration_since(*last_time);
+
+            if time_diff <= self.input_state.double_press_threshold {
+                self.input_state.last_key_press_times.remove(&key);
+                return true;
+            }
+        }
+
+        self.input_state.last_key_press_times.insert(key, now);
+        false
+    }
+
+    /// Save the current world state to the active scene
+    pub fn save_current_scene(&mut self) -> anyhow::Result<()> {
+        let mut scenes = SCENES.write();
+
+        let scene_index = if scenes.is_empty() {
+            return Err(anyhow::anyhow!("No scenes loaded to save"));
+        } else {
+            0
+        };
+
+        let scene = &mut scenes[scene_index];
+        scene.entities.clear();
+        scene.lights.clear();
+        scene.cameras.clear();
+
+        for (id, (adopted, transform, properties, script)) in self
+            .world
+            .query::<(
+                &AdoptedEntity,
+                Option<&Transform>,
+                &ModelProperties,
+                Option<&ScriptComponent>,
+            )>()
+            .iter()
+        {
+            let transform = transform.unwrap_or(&Transform::default()).clone();
+
+            let scene_entity = SceneEntity {
+                model_path: adopted.model().path.clone(),
+                label: adopted.model().label.clone(),
+                transform,
+                properties: properties.clone(),
+                script: script.cloned(),
+                entity_id: Some(id),
+            };
+
+            scene.entities.push(scene_entity);
+            log::debug!("Pushed entity: {}", adopted.label());
+        }
+
+        for (id, (light_component, transform, light)) in self
+            .world
+            .query::<(
+                &dropbear_engine::lighting::LightComponent,
+                &Transform,
+                &Light,
+            )>()
+            .iter()
+        {
+            let light_config = LightConfig {
+                label: light.label().to_string(),
+                transform: *transform,
+                light_component: light_component.clone(),
+                enabled: light_component.enabled,
+                entity_id: Some(id),
+            };
+
+            scene.lights.push(light_config);
+            log::debug!("Pushed light into lights: {}", light.label());
+        }
+
+        for (_id, (camera, component, follow_target)) in self
+            .world
+            .query::<(&Camera, &CameraComponent, Option<&CameraFollowTarget>)>()
+            .iter() 
+        {
+            let camera_config = CameraConfig::from_ecs_camera(camera, component, follow_target);
+            scene.cameras.push(camera_config);
+            log::debug!("Pushed camera into cameras: {}", camera.label);
+        }
+
+
+        log::info!(
+            "Saved {} entities and camera configs to scene '{}'",
+            scene.entities.len(),
+            scene.scene_name
+        );
+
+        Ok(())
+    }
+
+    pub fn save_project_config(&mut self) -> anyhow::Result<()> {
+        self.save_current_scene()?;
+
+        {
+            let mut config = PROJECT.write();
+            config.dock_layout = Some(self.dock_state.clone());
+        }
+
+        {
+            let (scene_clone, project_path) = {
+                let scenes = SCENES.read();
+                let project = PROJECT.read();
+                (scenes[0].clone(), project.project_path.clone())
+            };
+
+            scene_clone.write_to(&project_path)?;
+
+            let mut config = PROJECT.write();
+            config.write_to_all()?;
+        }
+
+        Ok(())
+    }
+
+    pub fn load_project_config(&mut self, graphics: &Graphics) -> anyhow::Result<()> {
+        let config = PROJECT.read();
+
+        self.project_path = Some(config.project_path.clone());
+
+        if let Some(layout) = &config.dock_layout {
+            self.dock_state = layout.clone();
+        }
+
+        {
+            let scenes = SCENES.read();
+            if let Some(first_scene) = scenes.first() {
+                self.active_camera = Some(first_scene.load_into_world(&mut self.world, graphics)?);
+
+                log::info!(
+                    "Successfully loaded scene with {} entities and {} camera configs",
+                    first_scene.entities.len(),
+                    first_scene.cameras.len(),
+                );
+            } else {
+                let existing_debug_camera = self.world
+                    .query::<(&Camera, &CameraComponent)>()
+                    .iter()
+                    .find_map(|(entity, (_, component))| {
+                        if matches!(component.camera_type, CameraType::Debug) {
+                            Some(entity)
+                        } else {
+                            None
+                        }
+                    });
+
+                if let Some(camera_entity) = existing_debug_camera {
+                    log::info!("Using existing debug camera");
+                    self.active_camera = Some(camera_entity);
+                } else {
+                    log::info!("No scenes found, creating default debug camera");
+
+                    let debug_camera = Camera::predetermined(graphics, Some("Debug Camera"));
+                    let component = DebugCamera::new();
+
+                    let e = self.world.spawn((debug_camera, component));
+                    self.active_camera = Some(e);
+                }
+            }
+        }
+
+        Ok(())
+    }
+
+    pub fn show_ui(&mut self, ctx: &Context) {
+        egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
+            egui::MenuBar::new().ui(ui, |ui| {
+                ui.menu_button("File", |ui| {
+                    if ui
+                        .button("Main Menu (New + Open + Editor Settings)")
+                        .clicked()
+                    {
+                        self.scene_command = SceneCommand::SwitchScene("main_menu".into());
+                    }
+
+                    if ui.button("Save").clicked() {
+                        match self.save_project_config() {
+                            Ok(_) => {}
+                            Err(e) => {
+                                fatal!("Error saving project: {}", e);
+                            }
+                        }
+                        success!("Successfully saved project");
+                    }
+                    if ui.button("Project Settings").clicked() {};
+                    if matches!(self.editor_state, EditorState::Playing) {
+                        if ui.button("Stop").clicked() {
+                            self.signal = Signal::StopPlaying;
+                        }
+                    } else {
+                        if ui.button("Play").clicked() {
+                            self.signal = Signal::Play;
+                        }
+                    }
+                    ui.menu_button("Export", |ui| {
+                        // todo: create a window for better build menu
+                        if ui.button("Build").clicked() {
+                            {
+                                let proj = PROJECT.read();
+                                match build(proj.project_path.join(format!("{}.eucp", proj.project_name.clone())).clone()) {
+                                    Ok(thingy) => success!("Project output at {}", thingy.display()),
+                                    Err(e) => {
+                                        fatal!("Unable to build project [{}]: {}", proj.project_path.clone().display(), e);
+                                    },
+                                }
+                            }
+                        }
+                        ui.label("Package"); // todo: create a window for label
+                    });
+                    ui.separator();
+                    if ui.button("Quit").clicked() {
+                        match self.save_project_config() {
+                            Ok(_) => {}
+                            Err(e) => {
+                                fatal!("Error saving project: {}", e);
+                            }
+                        }
+                        success!("Successfully saved project");
+                    }
+                });
+                ui.menu_button("Edit", |ui| {
+                    if ui.button("Copy").clicked() {
+                        if let Some(entity) = &self.selected_entity {
+                            let query = self.world.query_one::<(&AdoptedEntity, &Transform, &ModelProperties)>(*entity);
+                            if let Ok(mut q) = query {
+                                if let Some((e, t, props)) = q.get() {
+                                    let s_entity = states::SceneEntity {
+                                        model_path: e.model().path.clone(),
+                                        label: e.model().label.clone(),
+                                        transform: *t,
+                                        properties: props.clone(),
+                                        script: None,
+                                        entity_id: None,
+                                    };
+                                    self.signal = Signal::Copy(s_entity);
+
+                                    info!("Copied selected entity!");
+                                } else {
+                                    warn!("Unable to copy entity: Unable to fetch world entity properties");
+                                }
+                            } else {
+                                warn!("Unable to copy entity: Unable to obtain lock");
+                            }
+                        } else {
+                            warn!("Unable to copy entity: None selected");
+                        }
+                    }
+
+                    if ui.button("Paste").clicked() {
+                        match &self.signal {
+                            Signal::Copy(entity) => {
+                                self.signal = Signal::Paste(entity.clone());
+                            }
+                            _ => {
+                                warn!("Unable to paste: You haven't selected anything!");
+                            }
+                        }
+                    }
+
+                    if ui.button("Undo").clicked() {
+                        self.signal = Signal::Undo;
+                    }
+                    ui.label("Redo");
+                });
+
+                ui.menu_button("Window", |ui_window| {
+                    if ui_window.button("Open Asset Viewer").clicked() {
+                        self.dock_state.push_to_focused_leaf(EditorTab::AssetViewer);
+                    }
+                    if ui_window.button("Open Resource Inspector").clicked() {
+                        self.dock_state
+                            .push_to_focused_leaf(EditorTab::ResourceInspector);
+                    }
+                    if ui_window.button("Open Entity List").clicked() {
+                        self.dock_state
+                            .push_to_focused_leaf(EditorTab::ModelEntityList);
+                    }
+                    if ui_window.button("Open Viewport").clicked() {
+                        self.dock_state.push_to_focused_leaf(EditorTab::Viewport);
+                    }
+                });
+                {
+                    let cfg = PROJECT.read();
+                    if cfg.editor_settings.is_debug_menu_shown {
+                        debug::show_menu_bar(ui, &mut self.signal);
+                    }
+                }
+            });
+        });
+
+        egui::CentralPanel::default().show(&ctx, |ui| {
+            DockArea::new(&mut self.dock_state)
+                .style(Style::from_egui(ui.style().as_ref()))
+                .show_inside(
+                    ui,
+                    &mut EditorTabViewer {
+                        view: self.texture_id.unwrap(),
+                        nodes: EntityNode::from_world(&self.world),
+                        gizmo: &mut self.gizmo,
+                        tex_size: self.size,
+                        world: &mut self.world,
+                        selected_entity: &mut self.selected_entity,
+                        viewport_mode: &mut self.viewport_mode,
+                        undo_stack: &mut self.undo_stack,
+                        signal: &mut self.signal,
+                        active_camera: &mut self.active_camera,
+                        gizmo_mode: &mut self.gizmo_mode,
+                        editor_mode: &mut self.editor_state,
+                    },
+                );
+        });
+
+        crate::utils::show_new_project_window(
+            ctx,
+            &mut self.show_new_project,
+            &mut self.project_name,
+            &mut self.project_path,
+            |name, path| {
+                crate::utils::start_project_creation(name.to_string(), Some(path.clone()));
+                self.pending_scene_switch = true;
+            },
+        );
+
+        if self.pending_scene_switch {
+            self.scene_command = SceneCommand::SwitchScene("editor".to_string());
+            self.pending_scene_switch = false;
+        }
+    }
+
+    pub fn switch_to_debug_camera(&mut self) {
+        let debug_camera = self.world.query::<(&Camera, &CameraComponent)>()
+            .iter()
+            .find_map(|(e, (_cam, comp))| {
+                if matches!(comp.camera_type, CameraType::Debug) {
+                    Some(e)
+                } else {
+                    None
+                }
+            });
+
+        if let Some(camera_entity) = debug_camera {
+            self.active_camera = Some(camera_entity);
+            info!("Switched to debug camera");
+        } else {
+            warn!("No debug camera found in the world");
+        }
+    }
+
+    pub fn switch_to_player_camera(&mut self) {
+        let player_camera = self.world.query::<(&Camera, &CameraComponent)>()
+            .iter()
+            .find_map(|(e, (_cam, comp))| {
+                if matches!(comp.camera_type, CameraType::Player) {
+                    Some(e)
+                } else {
+                    None
+                }
+            });
+
+        if let Some(camera_entity) = player_camera {
+            self.active_camera = Some(camera_entity);
+            info!("Switched to player camera");
+        } else {
+            warn!("No player camera found in the world");
+        }
+    }
+
+    pub fn is_using_debug_camera(&self) -> bool {
+        if let Some(active_camera_entity) = self.active_camera {
+            if let Ok(mut query) = self.world.query_one::<&CameraComponent>(active_camera_entity) {
+                if let Some(component) = query.get() {
+                    return matches!(component.camera_type, CameraType::Debug);
+                }
+            }
+        }
+        false
+    }
+}
+
+pub static LOGGED: LazyLock<Mutex<HashSet<String>>> = LazyLock::new(|| Mutex::new(HashSet::new()));
+
+fn show_entity_tree(
+    ui: &mut egui::Ui,
+    nodes: &mut Vec<EntityNode>,
+    selected: &mut Option<hecs::Entity>,
+    id_source: &str,
+) {
+    egui_dnd::Dnd::new(ui, id_source).show_vec(nodes, |ui, item, handle, _dragging| match item
+        .clone()
+    {
+        EntityNode::Entity { id, name } => {
+            ui.horizontal(|ui| {
+                handle.ui(ui, |ui| {
+                    ui.label("⏹️");
+                });
+                let resp = ui.selectable_label(selected.as_ref().eq(&Some(&id)), name);
+                if resp.clicked() {
+                    *selected = Some(id);
+                }
+            });
+        }
+        EntityNode::Script { name, path: _ } => {
+            ui.horizontal(|ui| {
+                handle.ui(ui, |ui| {
+                    ui.label("📜");
+                });
+                ui.label(format!("{name}"));
+            });
+        }
+        EntityNode::Group {
+            ref name,
+            ref mut children,
+            ref mut collapsed,
+        } => {
+            ui.horizontal(|ui| {
+                handle.ui(ui, |ui| {
+                    let header = egui::CollapsingHeader::new(name)
+                        .default_open(!*collapsed)
+                        .show(ui, |ui| {
+                            show_entity_tree(ui, children, selected, name);
+                        });
+                    *collapsed = !header.body_returned.is_some();
+                });
+            });
+        }
+        EntityNode::Light {
+            id,
+            name,
+        } => {
+            ui.horizontal(|ui| {
+                handle.ui(ui, |ui| {
+                    ui.label("💡");
+                });
+                let resp = ui.selectable_label(selected.as_ref().eq(&Some(&id)), name);
+                if resp.clicked() {
+                    *selected = Some(id);
+                }
+            });
+        }
+        EntityNode::Camera { id, name, camera_type } => {
+            ui.horizontal(|ui| {
+                handle.ui(ui, |ui| {
+                    let icon = match camera_type {
+                        CameraType::Debug => "🎥", // Debug camera
+                        CameraType::Player => "📹", // Player camera
+                        CameraType::Normal => "📷", // Normal camera
+                    };
+                    ui.label(icon);
+                });
+                let display_name = format!("{} ({})", name, match camera_type {
+                    CameraType::Debug => "Debug",
+                    CameraType::Player => "Player", 
+                    CameraType::Normal => "Normal",
+                });
+                let resp = ui.selectable_label(selected.as_ref().eq(&Some(&id)), display_name);
+                if resp.clicked() {
+                    *selected = Some(id);
+                }
+            });
+        }
+    });
+}
+
+/// Describes an action that is undoable
+#[derive(Debug)]
+pub enum UndoableAction {
+    Transform(hecs::Entity, Transform),
+    Spawn(hecs::Entity),
+    Label(hecs::Entity, String, EntityType),
+    RemoveComponent(hecs::Entity, ComponentType),
+    #[allow(dead_code)]
+    CameraAction(UndoableCameraAction),
+}
+#[derive(Debug)]
+pub enum EntityType {
+    Entity,
+    Light,
+    Camera,
+}
+
+impl UndoableAction {
+    pub fn push_to_undo(undo_stack: &mut Vec<UndoableAction>, action: Self) {
+        undo_stack.push(action);
+        // log::debug!("Undo Stack contents: {:#?}", undo_stack);
+    }
+
+    pub fn undo(&self, world: &mut hecs::World) -> anyhow::Result<()> {
+        match self {
+            UndoableAction::Transform(entity, transform) => {
+                        if let Ok(mut q) = world.query_one::<&mut Transform>(*entity) {
+                            if let Some(e_t) = q.get() {
+                                *e_t = *transform;
+                                log::debug!("Reverted transform");
+                                Ok(())
+                            } else {
+                                Err(anyhow::anyhow!("Unable to query the entity"))
+                            }
+                        } else {
+                            Err(anyhow::anyhow!("Could not find an entity to query"))
+                        }
+                    }
+            UndoableAction::Spawn(entity) => {
+                        if world.despawn(*entity).is_ok() {
+                            log::debug!("Undid spawn by despawning entity {:?}", entity);
+                            Ok(())
+                        } else {
+                            Err(anyhow::anyhow!("Failed to despawn entity {:?}", entity))
+                        }
+                    }
+            UndoableAction::Label(entity, original_label, entity_type) => {
+                match entity_type {
+                    EntityType::Entity => {
+                        if let Ok(mut q) = world.query_one::<&mut AdoptedEntity>(*entity) {
+                            if let Some(adopted) = q.get() {
+                                adopted.model_mut().label = original_label.clone();
+                                log::debug!("Reverted label for entity {:?} to '{}'", entity, original_label);
+                                Ok(())
+                            } else {
+                                Err(anyhow::anyhow!("Unable to query the entity for label revert"))
+                            }
+                        } else {
+                            Err(anyhow::anyhow!("Could not find an entity to query for label revert"))
+                        }
+                    },
+                    EntityType::Light => {
+                        if let Ok(mut q) = world.query_one::<&mut Light>(*entity) {
+                            if let Some(adopted) = q.get() {
+                                adopted.label = original_label.clone();
+                                log::debug!("Reverted label for light {:?} to '{}'", entity, original_label);
+                                Ok(())
+                            } else {
+                                Err(anyhow::anyhow!("Unable to query the light for label revert"))
+                            }
+                        } else {
+                            Err(anyhow::anyhow!("Could not find a light to query for label revert"))
+                        }
+                    },
+                    EntityType::Camera => {
+                        if let Ok(mut q) = world.query_one::<&mut Camera>(*entity) {
+                            if let Some(adopted) = q.get() {
+                                adopted.label = original_label.clone();
+                                log::debug!("Reverted label for camera {:?} to '{}'", entity, original_label);
+                                Ok(())
+                            } else {
+                                Err(anyhow::anyhow!("Unable to query the camera for label revert"))
+                            }
+                        } else {
+                            Err(anyhow::anyhow!("Could not find a camera to query for label revert"))
+                        }
+                    }
+                }
+            },
+            UndoableAction::RemoveComponent(entity, c_type) => {
+                match c_type {
+                    ComponentType::Script(component) => {
+                        world.insert_one(*entity, component.clone())?;
+                    },
+                    ComponentType::Camera(camera, component, follow) => {
+                        if let Some(f) = follow {
+                            world.insert(*entity, (camera.clone(), component.clone(), f.clone()))?;
+                        } else {
+                            world.insert(*entity, (camera.clone(), component.clone()))?;
+                        }
+                    }
+                }
+                Ok(())
+            },
+            UndoableAction::CameraAction(action) => {
+                match action {
+                    UndoableCameraAction::Speed(entity, speed) => {
+                        if let Ok((cam, comp)) = world.query_one_mut::<(&mut Camera, &mut CameraComponent)>(*entity) {
+                            comp.speed = *speed;
+                            comp.update(cam);
+                        }
+                    },
+                    UndoableCameraAction::Sensitivity(entity, sensitivity) => {
+                        if let Ok((cam, comp)) = world.query_one_mut::<(&mut Camera, &mut CameraComponent)>(*entity) {
+                            comp.sensitivity = *sensitivity;
+                            comp.update(cam);
+                        }
+                    },
+                    UndoableCameraAction::FOV(entity, fov) => {
+                        if let Ok((cam, comp)) = world.query_one_mut::<(&mut Camera, &mut CameraComponent)>(*entity) {
+                            comp.fov_y = *fov;
+                            comp.update(cam);
+                        }
+                    },
+                    UndoableCameraAction::Type(entity, camera_type) => {
+                        if let Ok((cam, comp)) = world.query_one_mut::<(&mut Camera, &mut CameraComponent)>(*entity) {
+                            comp.camera_type = *camera_type;
+                            comp.update(cam);
+                        }
+                    },
+                };
+                Ok(())
+            }
+        }
+    }
+}
+
+/// This enum will be used to describe the type of command/signal. This is only between
+/// the editor and unlike SceneCommand, this will ping a signal everywhere in that scene
+pub enum Signal {
+    None,
+    Copy(SceneEntity),
+    Paste(SceneEntity),
+    Delete,
+    Undo,
+    ScriptAction(ScriptAction),
+    CameraAction(CameraAction),
+    Play,
+    StopPlaying,
+    AddComponent(hecs::Entity, EntityType),
+    RemoveComponent(hecs::Entity, ComponentType),
+    CreateEntity,
+    LogEntities,
+}
+
+impl Default for Editor {
+    fn default() -> Self {
+        Editor::new()
+    }
+}
+
+#[derive(Debug)]
+pub enum ComponentType {
+    Script(ScriptComponent),
+    Camera(Camera, CameraComponent, Option<CameraFollowTarget>),
+}
+
+#[derive(Clone)]
+pub struct PlayModeBackup {
+    entities: Vec<(hecs::Entity, Transform, ModelProperties, Option<ScriptComponent>)>,
+    camera_data: Vec<(hecs::Entity, Camera, CameraComponent, Option<CameraFollowTarget>)>,
+}
+
+impl PlayModeBackup {
+    pub fn create_backup(editor: &mut Editor) -> anyhow::Result<()> {
+        let mut entities = Vec::new();
+        
+        for (entity_id, (_, transform, properties)) in editor
+            .world
+            .query::<(&AdoptedEntity, &Transform, &ModelProperties)>()
+            .iter()
+        {
+            let script = editor.world.query_one::<&ScriptComponent>(entity_id).ok().and_then(|mut s| {
+                s.get().map(|script| script.clone())
+            });
+            entities.push((entity_id, *transform, properties.clone(), script));
+        }
+
+        let mut camera_data = Vec::new();
+        
+        for (entity_id, (camera, component, follow_target)) in editor
+            .world
+            .query::<(&Camera, &CameraComponent, Option<&CameraFollowTarget>)>()
+            .iter()
+        {
+            camera_data.push((entity_id, camera.clone(), component.clone(), follow_target.cloned()));
+        }
+
+        editor.play_mode_backup = Some(PlayModeBackup {
+            entities,
+            camera_data,
+        });
+
+        log::info!("Created play mode backup with {} entities and {} cameras", 
+            editor.play_mode_backup.as_ref().unwrap().entities.len(),
+            editor.play_mode_backup.as_ref().unwrap().camera_data.len()
+        );
+        Ok(())
+    }
+
+    pub fn restore(editor: &mut Editor) -> anyhow::Result<()> {
+        if let Some(backup) = &editor.play_mode_backup {
+            // Restore entity states
+            for (entity_id, original_transform, original_properties, original_script) in &backup.entities {
+                if let Ok(mut transform) = editor.world.get::<&mut Transform>(*entity_id) {
+                    *transform = *original_transform;
+                }
+                
+                if let Ok(mut properties) = editor.world.get::<&mut ModelProperties>(*entity_id) {
+                    *properties = original_properties.clone();
+                }
+                
+                let has_script = editor.world.get::<&ScriptComponent>(*entity_id).is_ok();
+                match (has_script, original_script) {
+                    (true, Some(original)) => {
+                        if let Ok(mut script) = editor.world.get::<&mut ScriptComponent>(*entity_id) {
+                            *script = original.clone();
+                        }
+                    }
+                    (true, None) => {
+                        let _ = editor.world.remove_one::<ScriptComponent>(*entity_id);
+                    }
+                    (false, Some(original)) => {
+                        let _ = editor.world.insert_one(*entity_id, original.clone());
+                    }
+                    (false, None) => {
+                        // No change needed
+                    }
+                }
+            }
+
+            // Restore camera states
+            for (entity_id, original_camera, original_component, original_follow_target) in &backup.camera_data {
+                if let Ok(mut camera) = editor.world.get::<&mut Camera>(*entity_id) {
+                    *camera = original_camera.clone();
+                }
+                
+                if let Ok(mut component) = editor.world.get::<&mut CameraComponent>(*entity_id) {
+                    *component = original_component.clone();
+                }
+
+                let has_follow_target = editor.world.get::<&CameraFollowTarget>(*entity_id).is_ok();
+                match (has_follow_target, original_follow_target) {
+                    (true, Some(original)) => {
+                        if let Ok(mut follow_target) = editor.world.get::<&mut CameraFollowTarget>(*entity_id) {
+                            *follow_target = original.clone();
+                        }
+                    }
+                    (true, None) => {
+                        let _ = editor.world.remove_one::<CameraFollowTarget>(*entity_id);
+                    }
+                    (false, Some(original)) => {
+                        let _ = editor.world.insert_one(*entity_id, original.clone());
+                    }
+                    (false, None) => {
+                        // No change needed
+                    }
+                }
+            }
+
+            log::info!("Restored scene from play mode backup");
+            
+            editor.play_mode_backup = None;
+            Ok(())
+        } else {
+            Err(anyhow::anyhow!("No play mode backup found to restore"))
+        }
+    }
+}
+
+pub enum EditorState {
+    Editing,
+    Playing,
+}
\ No newline at end of file
diff --git a/eucalyptus-editor/src/editor/scene.rs b/eucalyptus-editor/src/editor/scene.rs
new file mode 100644
index 0000000..80f1b4c
--- /dev/null
+++ b/eucalyptus-editor/src/editor/scene.rs
@@ -0,0 +1,929 @@
+use egui::{Align2, Image};
+use dropbear_engine::{
+    entity::{AdoptedEntity, Transform}, graphics::{Graphics, Shader}, lighting::{Light, LightComponent}, model::{DrawLight, DrawModel}, scene::{Scene, SceneCommand}
+};
+use log;
+use parking_lot::Mutex;
+use wgpu::Color;
+use wgpu::util::DeviceExt;
+use winit::{event_loop::ActiveEventLoop, keyboard::KeyCode};
+use dropbear_engine::graphics::InstanceRaw;
+use dropbear_engine::model::Model;
+use dropbear_engine::starter::plane::PlaneBuilder;
+use eucalyptus_core::camera::PlayerCamera;
+use eucalyptus_core::{logging, scripting, success_without_console, warn_without_console};
+use eucalyptus_core::states::PropertyValue;
+use eucalyptus_core::utils::{PendingSpawn, PROTO_TEXTURE};
+use super::*;
+
+pub static PENDING_SPAWNS: LazyLock<Mutex<Vec<PendingSpawn>>> =
+    LazyLock::new(|| Mutex::new(Vec::new()));
+
+impl Scene for Editor {
+    fn load(&mut self, graphics: &mut Graphics) {
+        if self.active_camera.is_none() {
+            self.load_project_config(graphics).unwrap();
+        }
+
+        let shader = Shader::new(
+            graphics,
+            include_str!("../../../resources/shaders/shader.wgsl"),
+            Some("viewport_shader"),
+        );
+
+        self.light_manager.create_light_array_resources(graphics);
+
+        let texture_bind_group = &graphics.texture_bind_group().clone();
+        if let Some(active_camera) = self.active_camera {
+            if let Ok(mut q) = self.world.query_one::<(&Camera, &CameraComponent, Option<&CameraFollowTarget>)>(active_camera) {
+                if let Some((camera, _component, _follow_target)) = q.get() {
+                    let pipeline = graphics.create_render_pipline(
+                        &shader,
+                        vec![
+                            texture_bind_group,
+                            camera.layout(),
+                            self.light_manager.layout()
+                        ],
+                        None,
+                    );
+                    self.render_pipeline = Some(pipeline);
+
+                    self.light_manager.create_render_pipeline(
+                        graphics,
+                        include_str!("../../../resources/shaders/light.wgsl"),
+                        camera,
+                        Some("Light Pipeline")
+                    );
+                } else {
+                    log_once::warn_once!("Unable to fetch the query result of camera: {:?}", active_camera)
+                }
+            } else {
+                log_once::warn_once!("Unable to query camera, component and option<camerafollowtarget> for active camera: {:?}", active_camera);
+            }
+        } else {
+            log_once::warn_once!("No active camera found");
+        }
+
+        self.window = Some(graphics.state.window.clone());
+    }
+
+    fn update(&mut self, dt: f32, graphics: &mut Graphics) {
+        if let Some((_, tab)) = self.dock_state.find_active_focused() {
+            self.is_viewport_focused = matches!(tab, EditorTab::Viewport);
+        } else {
+            self.is_viewport_focused = false;
+        }
+
+        {
+            let mut pending_spawns = PENDING_SPAWNS.lock();
+            for spawn in pending_spawns.drain(..) {
+                match AdoptedEntity::new(graphics, &spawn.asset_path, Some(&spawn.asset_name)) {
+                    Ok(adopted) => {
+                        let entity_id =
+                            self.world
+                                .spawn((adopted, spawn.transform, spawn.properties));
+                        self.selected_entity = Some(entity_id);
+
+                        UndoableAction::push_to_undo(
+                            &mut self.undo_stack,
+                            UndoableAction::Spawn(entity_id),
+                        );
+
+                        log::info!(
+                            "Successfully spawned {} with ID {:?}",
+                            spawn.asset_name,
+                            entity_id
+                        );
+                    }
+                    Err(e) => {
+                        log::error!("Failed to spawn {}: {}", spawn.asset_name, e);
+                    }
+                }
+            }
+        }
+
+        if matches!(self.editor_state, EditorState::Playing) {
+            if self.input_state.pressed_keys.contains(&KeyCode::Escape) {
+                self.signal = Signal::StopPlaying;
+            }
+
+            let mut script_entities = Vec::new();
+            for (entity_id, script) in self.world.query::<&mut ScriptComponent>().iter() {
+                log_once::debug_once!("Script Entity -> id: {:?}, component: {:?}", entity_id, script);
+                script.name = script.path.file_name().unwrap().to_str().unwrap().to_string();
+                script_entities.push((entity_id, script.name.clone()));
+            }
+
+            if script_entities.is_empty() {
+                log_once::warn_once!("Script entities is empty");
+            }
+
+            for (entity_id, script_name) in script_entities {
+                if let Err(e) = self.script_manager.update_entity_script(entity_id, &script_name, &mut self.world, &self.input_state, dt) {
+                    log_once::warn_once!("Failed to update script '{}' for entity {:?}: {}", script_name, entity_id, e);
+                }
+            }
+        }
+
+        if self.is_viewport_focused
+            && matches!(self.viewport_mode, ViewportMode::CameraMove)
+        // && self.is_using_debug_camera()
+        {
+            let movement_keys: std::collections::HashSet<KeyCode> = self
+                .input_state
+                .pressed_keys
+                .iter()
+                .filter(|&&key| {
+                    matches!(
+                        key,
+                        KeyCode::KeyW
+                            | KeyCode::KeyA
+                            | KeyCode::KeyS
+                            | KeyCode::KeyD
+                            | KeyCode::Space
+                            | KeyCode::ShiftLeft
+                    )
+                })
+                .copied()
+                .collect();
+
+            // Handle camera input through ECS
+            if let Some(active_camera) = self.active_camera {
+                if let Ok(mut query) = self.world.query_one::<(&mut Camera, &CameraComponent)>(active_camera) {
+                    if let Some((camera, component)) = query.get() {
+                        // Handle keyboard input based on camera type
+                        match component.camera_type {
+                            CameraType::Debug => {
+                                DebugCamera::handle_keyboard_input(camera, &movement_keys);
+                                DebugCamera::handle_mouse_input(camera, component, self.input_state.mouse_delta);
+                            }
+                            CameraType::Player => {
+                                PlayerCamera::handle_keyboard_input(camera, &movement_keys);
+                                PlayerCamera::handle_mouse_input(camera, component, self.input_state.mouse_delta);
+                            }
+                            CameraType::Normal => {
+                                // Handle normal camera input if needed
+                                DebugCamera::handle_keyboard_input(camera, &movement_keys);
+                                DebugCamera::handle_mouse_input(camera, component, self.input_state.mouse_delta);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        match &self.signal {
+            Signal::Paste(scene_entity) => {
+                        match AdoptedEntity::new(
+                            graphics,
+                            &scene_entity.model_path.to_project_path(self.project_path.clone().unwrap()).unwrap(),
+                            Some(&scene_entity.label),
+                        ) {
+                            Ok(adopted) => {
+                                let entity_id = self.world.spawn((
+                                    adopted,
+                                    scene_entity.transform,
+                                    ModelProperties::default(),
+                                ));
+                                self.selected_entity = Some(entity_id);
+                                log::debug!(
+                                    "Successfully paste-spawned {} with ID {:?}",
+                                    scene_entity.label,
+                                    entity_id
+                                );
+
+                                success_without_console!("Paste!");
+                                self.signal = Signal::Copy(scene_entity.clone());
+                            }
+                            Err(e) => {
+                                warn!("Failed to paste-spawn {}: {}", scene_entity.label, e);
+                            }
+                        }
+                    }
+            Signal::Delete => {
+                        if let Some(sel_e) = &self.selected_entity {
+                            let is_viewport_cam = if let Ok(mut q) = self.world.query_one::<&CameraComponent>(*sel_e) { if let Some(c) = q.get() { if matches!(c.camera_type, CameraType::Debug) { true } else { false } } else { false } } else { false };
+                            if is_viewport_cam {
+                                warn!("You can't delete the viewport camera");
+                                self.signal = Signal::None;
+                            } else {
+                                match self.world.despawn(*sel_e) {
+                                    Ok(_) => {
+                                        info!("Decimated entity");
+                                        self.signal = Signal::None;
+                                    }
+                                    Err(e) => {
+                                        warn!("Failed to delete entity: {}", e);
+                                        self.signal = Signal::None;
+                                    }
+                                }
+                            }
+                        }
+                    }
+            Signal::Undo => {
+                        if let Some(action) = self.undo_stack.pop() {
+                            match action.undo(&mut self.world) {
+                                Ok(_) => {
+                                    info!("Undid action");
+                                }
+                                Err(e) => {
+                                    warn!("Failed to undo action: {}", e);
+                                }
+                            }
+                        } else {
+                            warn_without_console!("Nothing to undo");
+                            log::debug!("No undoable actions in stack");
+                        }
+                        self.signal = Signal::None;
+                    }
+            Signal::None => {}
+            Signal::Copy(_) => {}
+            Signal::ScriptAction(action) => match action {
+                        ScriptAction::AttachScript {
+                            script_path,
+                            script_name,
+                        } => {
+                            if let Some(selected_entity) = self.selected_entity {
+                                match scripting::move_script_to_src(script_path) {
+                                    Ok(moved_path) => {
+                                        let new_script = ScriptComponent {
+                                            name: script_name.clone(),
+                                            path: moved_path.clone(),
+                                        };
+
+                                        let replaced = if let Ok(mut sc) =
+                                            self.world.get::<&mut ScriptComponent>(selected_entity)
+                                        {
+                                            sc.name = new_script.name.clone();
+                                            sc.path = new_script.path.clone();
+                                            true
+                                        } else {
+                                            match scripting::attach_script_to_entity(
+                                                &mut self.world,
+                                                selected_entity,
+                                                new_script.clone(),
+                                            ) {
+                                                Ok(_) => false,
+                                                Err(e) => {
+                                                    fatal!(
+                                                        "Failed to attach script to entity {:?}: {}",
+                                                        selected_entity,
+                                                        e
+                                                    );
+                                                    self.signal = Signal::None;
+                                                    return;
+                                                }
+                                            }
+                                        };
+
+                                        if let Err(e) = scripting::convert_entity_to_group(
+                                            &self.world,
+                                            selected_entity,
+                                        ) {
+                                            log::warn!("convert_entity_to_group failed (non-fatal): {}", e);
+                                        }
+
+                                        success!(
+                                            "{} script '{}' at {} to entity {:?}",
+                                            if replaced { "Reattached" } else { "Attached" },
+                                            script_name,
+                                            moved_path.display(),
+                                            selected_entity
+                                        );
+                                    }
+                                    Err(e) => {
+                                        fatal!("Move failed: {}", e);
+                                    }
+                                }
+                            } else {
+                                fatal!("AttachScript requested but no entity is selected");
+                            }
+
+                            self.signal = Signal::None;
+                        }
+                        ScriptAction::CreateAndAttachScript {
+                            script_path,
+                            script_name,
+                        } => {
+                            if let Some(selected_entity) = self.selected_entity {
+                                let new_script = ScriptComponent {
+                                    name: script_name.clone(),
+                                    path: script_path.clone(),
+                                };
+
+                                let replaced = if let Ok(mut sc) =
+                                    self.world.get::<&mut ScriptComponent>(selected_entity)
+                                {
+                                    sc.name = new_script.name.clone();
+                                    sc.path = new_script.path.clone();
+                                    true
+                                } else {
+                                    match scripting::attach_script_to_entity(
+                                        &mut self.world,
+                                        selected_entity,
+                                        new_script.clone(),
+                                    ) {
+                                        Ok(_) => false,
+                                        Err(e) => {
+                                            fatal!("Failed to attach new script: {}", e);
+                                            self.signal = Signal::None;
+                                            return;
+                                        }
+                                    }
+                                };
+
+                                if let Err(e) =
+                                    scripting::convert_entity_to_group(&self.world, selected_entity)
+                                {
+                                    log::warn!("convert_entity_to_group failed (non-fatal): {}", e);
+                                }
+
+                                success!(
+                                    "{} new script '{}' at {} to entity {:?}",
+                                    if replaced { "Replaced" } else { "Attached" },
+                                    script_name,
+                                    script_path.display(),
+                                    selected_entity
+                                );
+                            } else {
+                                warn_without_console!("No selected entity to attach new script");
+                                log::warn!("CreateAndAttachScript requested but no entity is selected");
+                            }
+                            self.signal = Signal::None;
+                        }
+                        ScriptAction::RemoveScript => {
+                            if let Some(selected_entity) = self.selected_entity {
+                                if let Ok(script) = self.world.remove_one::<ScriptComponent>(selected_entity) {
+                                    success!("Removed script from entity {:?}", selected_entity);
+
+                                    if let Err(e) = scripting::convert_entity_to_group(
+                                        &self.world,
+                                        selected_entity,
+                                    ) {
+                                        log::warn!("convert_entity_to_group failed (non-fatal): {}", e);
+                                    }
+                                    log::debug!("Pushing remove component to undo stack");
+                                    UndoableAction::push_to_undo(&mut self.undo_stack, UndoableAction::RemoveComponent(selected_entity, ComponentType::Script(script)));
+                                } else {
+                                    warn!(
+                                        "No script component found on entity {:?}",
+                                        selected_entity
+                                    );
+                                }
+                            } else {
+                                warn!("No entity selected to remove script from");
+                            }
+
+                            self.signal = Signal::None;
+                        }
+                        ScriptAction::EditScript => {
+                            if let Some(selected_entity) = self.selected_entity {
+                                if let Ok(mut q) = self.world.query_one::<&ScriptComponent>(selected_entity)
+                                {
+                                    if let Some(script) = q.get() {
+                                        match open::that(script.path.clone()) {
+                                            Ok(()) => {
+                                                success!("Opened {}", script.name)
+                                            }
+                                            Err(e) => {
+                                                warn!("Error while opening {}: {}", script.name, e);
+                                            }
+                                        }
+                                    }
+                                } else {
+                                    warn!(
+                                        "No script component found on entity {:?}",
+                                        selected_entity
+                                    );
+                                }
+                            } else {
+                                warn!("No entity selected to edit script");
+                            }
+                            self.signal = Signal::None;
+                        }
+            },
+            Signal::Play => {
+                // Check if a player camera target exists
+                let has_player_camera_target = self.world
+                    .query::<(&Camera, &CameraComponent, &CameraFollowTarget)>()
+                    .iter()
+                    .any(|(_, (_, comp, _))| matches!(comp.camera_type, CameraType::Player));
+
+                if has_player_camera_target {
+                    if let Err(e) = PlayModeBackup::create_backup(self) {
+                        fatal!("Failed to create play mode backup: {}", e);
+                        self.signal = Signal::None;
+                        return;
+                    }
+
+                    self.editor_state = EditorState::Playing;
+
+                    self.switch_to_player_camera();
+
+                    let mut script_entities = Vec::new();
+                    for (entity_id, script) in self.world.query::<&ScriptComponent>().iter() {
+                        script_entities.push((entity_id, script.clone()));
+                    }
+
+                    for (entity_id, script) in script_entities {
+                        log::debug!("Initialising entity script [{}] from path: {}", script.name, script.path.display());
+                        match self.script_manager.load_script(&script.path) {
+                            Ok(script_name) => {
+                                if let Err(e) = self.script_manager.init_entity_script(entity_id, &script_name, &mut self.world, &self.input_state) {
+                                    log::warn!("Failed to initialise script '{}' for entity {:?}: {}", script.name, entity_id, e);
+                                    self.signal = Signal::StopPlaying;
+                                } else {
+                                    success_without_console!("You are in play mode now! Press Escape to exit");
+                                    log::info!("You are in play mode now! Press Escape to exit");
+                                }
+                            }
+                            Err(e) => {
+                                // todo: proper error menu
+                                fatal!("Failed to load script '{}': {}", script.name, e);
+                                self.signal = Signal::StopPlaying;
+                            }
+                        }
+                    }
+                } else {
+                    fatal!("Unable to build: Player camera not attached to an entity");
+                }
+
+                self.signal = Signal::None;
+            }
+            Signal::StopPlaying => {
+                if let Err(e) = PlayModeBackup::restore(self) {
+                    warn!("Failed to restore from play mode backup: {}", e);
+                    log::warn!("Failed to restore scene state: {}", e);
+                }
+
+                self.editor_state = EditorState::Editing;
+
+                self.switch_to_debug_camera();
+
+                for (entity_id, _) in self.world.query::<&ScriptComponent>().iter() {
+                    self.script_manager.remove_entity_script(entity_id);
+                }
+
+                success!("Exited play mode");
+                log::info!("Back to the editor you go...");
+
+                self.signal = Signal::None;
+            },
+            Signal::CameraAction(action) => match action {
+                CameraAction::SetPlayerTarget { entity, offset } => {
+                    // Find player camera and add/update CameraFollowTarget component
+                    let player_camera = self.world
+                        .query::<(&Camera, &CameraComponent)>()
+                        .iter()
+                        .find_map(|(e, (_, comp))| {
+                            if matches!(comp.camera_type, CameraType::Player) {
+                                Some(e)
+                            } else {
+                                None
+                            }
+                        });
+
+                    if let Some(camera_entity) = player_camera {
+                        let mut follow_target = (false, CameraFollowTarget::default());
+                        // Find the target entity label
+                        if let Ok(mut query) = self.world.query_one::<&AdoptedEntity>(*entity) {
+                            if let Some(adopted) = query.get() {
+                                follow_target = (true, CameraFollowTarget {
+                                    follow_target: adopted.label().to_string(),
+                                    offset: *offset,
+                                });
+                            }
+                        }
+
+                        if follow_target.0 {
+                            let _ = self.world.insert_one(camera_entity, follow_target);
+                            info!("Set player camera target to entity {:?}", entity);
+                        }
+                    }
+                    self.signal = Signal::None;
+                }
+                CameraAction::ClearPlayerTarget => {
+                    let player_camera = self.world
+                        .query::<(&Camera, &CameraComponent)>()
+                        .iter()
+                        .find_map(|(e, (_, comp))| {
+                            if matches!(comp.camera_type, CameraType::Player) {
+                                Some(e)
+                            } else {
+                                None
+                            }
+                        });
+
+                    if let Some(camera_entity) = player_camera {
+                        let _ = self.world.remove_one::<CameraFollowTarget>(camera_entity);
+                    }
+                    info!("Cleared player camera target");
+                    self.signal = Signal::None;
+                }
+            },
+            Signal::AddComponent(entity, e_type) => {
+                match e_type {
+                    EntityType::Entity => {
+                        if let Ok(e) = self.world.query_one_mut::<&AdoptedEntity>(*entity) {
+                            let mut local_signal: Option<Signal> = None;
+                            let label = e.label().clone();
+                            let mut show = true;
+                            egui::Window::new(format!("Add component for {}", label))
+                                .title_bar(true)
+                                .open(&mut show)
+                                .scroll([false, true])
+                                .anchor(Align2::CENTER_CENTER, [0.0, 0.0])
+                                .enabled(true)
+                                .show(&graphics.get_egui_context(), |ui| {
+                                if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Scripting")).clicked() {
+                                    log::debug!("Adding scripting component to entity [{}]", label);
+                                    if let Err(e) = self.world.insert_one(*entity, ScriptComponent::default()) {
+                                        warn!("Failed to add scripting component to entity: {}", e);
+                                    } else {
+                                        success!("Added the scripting component");
+                                    }
+                                    local_signal = Some(Signal::None);
+                                }
+                                if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Camera")).clicked() {
+                                    log::debug!("Adding camera component to entity [{}]", label);
+                                    
+                                    let has_camera = self.world.query_one::<(&Camera, &CameraComponent)>(*entity).is_ok();
+                                    
+                                    if has_camera {
+                                        warn!("Entity [{}] already has a camera component", label);
+                                    } else {
+                                        let camera = Camera::predetermined(graphics, Some(&format!("{} Camera", label)));
+                                        let component = CameraComponent::new();
+                                        
+                                        if let Err(e) = self.world.insert(*entity, (camera, component)) {
+                                            warn!("Failed to add camera component to entity: {}", e);
+                                        } else {
+                                            success!("Added the camera component");
+                                        }
+                                    }
+                                    local_signal = Some(Signal::None);
+                                }
+                            });
+                            if !show {
+                                self.signal = Signal::None;
+                            }
+                            if let Some(signal) = local_signal {
+                                self.signal = signal
+                            }
+                        } else {
+                            log_once::warn_once!("Failed to add component to entity: no entity component found");
+                        }
+                    }
+                    EntityType::Light => {
+                        if let Ok(light) = self.world.query_one_mut::<&Light>(*entity) {
+                            let mut show = true;
+                            egui::Window::new(format!("Add component for {}", light.label))
+                                .scroll([false, true])
+                                .anchor(Align2::CENTER_CENTER, [0.0, 0.0])
+                                .enabled(true)
+                                .open(&mut show)
+                                .title_bar(true)
+                                .show(&graphics.get_egui_context(), |ui| {
+                                    if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Scripting")).clicked() {
+                                        log::debug!("Adding scripting component to light [{}]", light.label);
+
+                                        success!("Added the scripting component to light [{}]", light.label);
+                                        self.signal = Signal::None;
+                                    }
+                                });
+                            if !show {
+                                self.signal = Signal::None;
+                            }
+                        } else {
+                            log_once::warn_once!("Failed to add component to light: no light component found");
+                        }
+                    },
+                    EntityType::Camera => {
+                        if let Ok((cam, _comp)) = self.world.query_one_mut::<(&Camera, &CameraComponent)>(*entity) {
+                            let mut show = true;
+                            egui::Window::new(format!("Add component for {}", cam.label))
+                                .scroll([false, true])
+                                .anchor(Align2::CENTER_CENTER, [0.0, 0.0])
+                                .enabled(true)
+                                .open(&mut show)
+                                .title_bar(true)
+                                .show(&graphics.get_egui_context(), |ui| {
+                                    egui_extras::install_image_loaders(ui.ctx());
+                                    ui.add(Image::from_bytes("bytes://theres_nothing", include_bytes!("../../../resources/theres_nothing.jpg")));
+                                    ui.label("Theres nothing...");
+                                    // // scripting
+                                    // if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Scripting")).clicked() {
+                                    //     log::debug!("Adding scripting component to camera [{}]", cam.label);
+
+                                    //     success!("Added the scripting component to camera [{}]", cam.label);
+                                    //     self.signal = Signal::None;
+                                    // }
+                                });
+                            if !show {
+                                self.signal = Signal::None;
+                            }
+                        } else {
+                            log_once::warn_once!("Failed to add component to light: no light component found");
+                        }
+                    }
+                }
+            },
+            Signal::RemoveComponent(entity, c_type) => {
+                match c_type {
+                    ComponentType::Script(_) => {
+                        match self.world.remove_one::<ScriptComponent>(*entity) {
+                            Ok(component) => {
+                                success!("Removed script component from entity {:?}", entity);
+                                UndoableAction::push_to_undo(&mut self.undo_stack, UndoableAction::RemoveComponent(*entity, ComponentType::Script(component)));
+                            }
+                            Err(e) => {
+                                warn!("Failed to remove script component from entity: {}", e);
+                            }
+                        };
+                        self.signal = Signal::None;
+                    },
+                    ComponentType::Camera(_, _, follow) => {
+                        if let Some(_) = follow {
+                            match self.world.remove::<(Camera, CameraComponent, CameraFollowTarget)>(*entity) {
+                                Ok(component) => {
+                                    success!("Removed camera component from entity {:?}", entity);
+                                    UndoableAction::push_to_undo(&mut self.undo_stack, UndoableAction::RemoveComponent(*entity, ComponentType::Camera(component.0, component.1, Some(component.2))));
+                                }
+                                Err(e) => {
+                                    warn!("Failed to remove camera component from entity: {}", e);
+                                }
+                            };
+                        } else {
+                            match self.world.remove::<(Camera, CameraComponent)>(*entity) {
+                                Ok(component) => {
+                                    success!("Removed camera component from entity {:?}", entity);
+                                    UndoableAction::push_to_undo(&mut self.undo_stack, UndoableAction::RemoveComponent(*entity, ComponentType::Camera(component.0, component.1, None)));
+                                }
+                                Err(e) => {
+                                    warn!("Failed to remove script component from entity: {}", e);
+                                }
+                            };
+                        }
+                    }
+                }
+            }
+            Signal::CreateEntity => {
+                // self.show_add_entity_window = true;
+                let mut show = true;
+                egui::Window::new("Add Entity")
+                    .scroll([false, true])
+                    .anchor(Align2::CENTER_CENTER, [0.0, 0.0])
+                    .enabled(true)
+                    .open(&mut show)
+                    .title_bar(true)
+                    .show(&graphics.get_egui_context(), |ui| {
+                        if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Model")).clicked() {
+                            log::debug!("Creating new model");
+                            warn!("Instead of using the `Add Entity` window, double click on the imported model in the asset \n\
+                            viewer to import a new model, then tweak the settings to how you wish after!");
+                            self.signal = Signal::None;
+                        }
+
+                        if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Light")).clicked() {
+                            log::debug!("Creating new lighting");
+                            let transform = Transform::new();
+                            let component = LightComponent::default();
+                            let light = Light::new(graphics, &component, &transform, Some("Light"));
+                            self.world.spawn((light, component, transform));
+                            success!("Created new light");
+
+                            // always ensure the signal is reset after action is dun
+                            self.signal = Signal::None;
+                        }
+
+                        if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Plane")).clicked() {
+                            log::debug!("Creating new plane");
+                            let plane = PlaneBuilder::new()
+                                .with_size(500.0, 200.0)
+                                .build(
+                                    graphics,
+                                    PROTO_TEXTURE,
+                                    Some("Plane")
+                                ).unwrap();
+                            let transform = Transform::new();
+                            let mut props = ModelProperties::new();
+                            props.custom_properties.insert("width".to_string(), PropertyValue::Float(500.0));
+                            props.custom_properties.insert("height".to_string(), PropertyValue::Float(200.0));
+                            props.custom_properties.insert("tiles_x".to_string(), PropertyValue::Int(500));
+                            props.custom_properties.insert("tiles_z".to_string(), PropertyValue::Int(200));
+                            self.world.spawn((plane, transform, props));
+                            success!("Created new plane");
+
+                            self.signal = Signal::None;
+                        }
+
+                        if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Cube")).clicked() {
+                            log::debug!("Creating new cube");
+                            let model = Model::load_from_memory(
+                                graphics,
+                                include_bytes!("../../../resources/cube.obj").to_vec(),
+                                Some("Cube")
+                            );
+                            match model {
+                                Ok(model) => {
+                                    let cube = AdoptedEntity::adopt(
+                                        graphics,
+                                        model,
+                                        Some("Cube")
+                                    );
+                                    self.world.spawn((cube, Transform::new(), ModelProperties::new()));
+                                }
+                                Err(e) => {
+                                    fatal!("Failed to load cube model: {}", e);
+                                }
+                            }
+                            success!("Created new cube");
+
+                            self.signal = Signal::None;
+                        }
+
+                        if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Cube")).clicked() {
+                            log::debug!("Creating new cube");
+                            let model = Model::load_from_memory(
+                                graphics,
+                                include_bytes!("../../../resources/cube.obj").to_vec(),
+                                Some("Cube")
+                            );
+                            match model {
+                                Ok(model) => {
+                                    let cube = AdoptedEntity::adopt(
+                                        graphics,
+                                        model,
+                                        Some("Cube")
+                                    );
+                                    self.world.spawn((cube, Transform::new(), ModelProperties::new()));
+                                }
+                                Err(e) => {
+                                    fatal!("Failed to load cube model: {}", e);
+                                }
+                            }
+                            success!("Created new cube");
+
+                            self.signal = Signal::None;
+                        }
+
+                        if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Camera")).clicked() {
+                            log::debug!("Creating new cube");
+                            let camera = Camera::predetermined(graphics, None);
+                            let component = CameraComponent::new();
+                            self.world.spawn((camera, component));
+                            success!("Created new camera");
+
+                            self.signal = Signal::None;
+                        }
+                    });
+                if !show {
+                    self.signal = Signal::None;
+                }
+            },
+            Signal::LogEntities => {
+                log::info!("====================");
+                for entity in self.world.iter() {
+                    if let Some(entity) = entity.get::<&AdoptedEntity>() {
+                        log::info!("Model: {:?}", entity.label());
+                    }
+
+                    if let Some(entity) = entity.get::<&Light>() {
+                        log::info!("Light: {:?}", entity.label());
+                    }
+                }
+                log::info!("====================");
+                self.signal = Signal::None;
+            }
+        }
+
+        let current_size = graphics.state.viewport_texture.size;
+        self.size = current_size;
+        
+        let new_aspect = current_size.width as f64 / current_size.height as f64;
+        if let Some(active_camera) = self.active_camera {
+            if let Ok(mut query) = self.world.query_one::<&mut Camera>(active_camera) {
+                if let Some(camera) = query.get() {
+                    camera.aspect = new_aspect;
+                }
+            }
+        }
+
+        for (_entity_id, (camera, _component, follow_target)) in self
+            .world
+            .query::<(&mut Camera, &CameraComponent, Option<&CameraFollowTarget>)>()
+            .iter()
+        {
+            if let Some(target) = follow_target {
+                for (_target_entity_id, (adopted, transform)) in self
+                    .world
+                    .query::<(&AdoptedEntity, &Transform)>()
+                    .iter()
+                {
+                    if adopted.label() == &target.follow_target {
+                        let target_pos = transform.position;
+                        camera.eye = target_pos + target.offset;
+                        camera.target = target_pos;
+                        break;
+                    }
+                }
+            }
+        }
+
+        for (_entity_id, (camera, component)) in self.world.query::<(&mut Camera, &mut CameraComponent)>().iter() {
+            component.update(camera);
+            camera.update(graphics);
+        }
+
+        let query = self.world.query_mut::<(&mut AdoptedEntity, &Transform)>();
+        for (_, (entity, transform)) in query {
+            entity.update(&graphics, transform);
+        }
+
+        let light_query = self.world.query_mut::<(&mut LightComponent, &Transform, &mut Light)>();
+        for (_, (light_component, transform, light)) in light_query {
+            light.update(light_component, transform);
+        }
+
+        self.light_manager.update(graphics, &self.world);
+    }
+
+    fn render(&mut self, graphics: &mut Graphics) {
+        // cornflower blue
+        let color = Color {
+            r: 100.0 / 255.0,
+            g: 149.0 / 255.0,
+            b: 237.0 / 255.0,
+            a: 1.0,
+        };
+
+        self.color = color.clone();
+        self.size = graphics.state.viewport_texture.size.clone();
+        self.texture_id = Some(graphics.state.texture_id.clone());
+        self.show_ui(&graphics.get_egui_context());
+
+        self.window = Some(graphics.state.window.clone());
+        logging::render(&graphics.get_egui_context());
+        if let Some(pipeline) = &self.render_pipeline {
+            if let Some(active_camera) = self.active_camera {
+                if let Ok(mut query) = self.world.query_one::<&Camera>(active_camera) {
+                    if let Some(camera) = query.get() {
+                        let mut light_query = self.world.query::<(&Light, &LightComponent)>();
+                        let mut entity_query = self.world.query::<(&AdoptedEntity, &Transform)>();
+                        {
+                            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 light_query.iter() {
+                                    if component.enabled {
+                                        render_pass.set_vertex_buffer(1, light.instance_buffer.as_ref().unwrap().slice(..));
+                                        render_pass.draw_light_model(
+                                            light.model(),
+                                            camera.bind_group(),
+                                            light.bind_group(),
+                                        );
+                                    }
+                                }
+                            }
+
+                            let mut model_batches: HashMap<*const Model, Vec<InstanceRaw>> = HashMap::new();
+
+                            for (_, (entity, _)) in entity_query.iter() {
+                                let model_ptr = entity.model() as *const Model;
+                                let instance_raw = entity.instance.to_raw();
+                                model_batches.entry(model_ptr).or_insert(Vec::new()).push(instance_raw);
+                            }
+
+                            render_pass.set_pipeline(pipeline);
+
+                            for (model_ptr, instances) in model_batches {
+                                let model = unsafe { &*model_ptr };
+
+                                let instance_buffer = graphics.state.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
+                                    label: Some("Batched Instance Buffer"),
+                                    contents: bytemuck::cast_slice(&instances),
+                                    usage: wgpu::BufferUsages::VERTEX,
+                                });
+
+                                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) {}
+
+    fn run_command(&mut self) -> SceneCommand {
+        std::mem::replace(&mut self.scene_command, SceneCommand::None)
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-editor/src/main.rs b/eucalyptus-editor/src/main.rs
new file mode 100644
index 0000000..7af76ce
--- /dev/null
+++ b/eucalyptus-editor/src/main.rs
@@ -0,0 +1,168 @@
+#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
+mod utils;
+mod camera;
+mod editor;
+mod build;
+mod menu;
+mod debug;
+
+use std::{cell::RefCell, rc::Rc, fs, path::PathBuf};
+use clap::{Arg, Command};
+
+use dropbear_engine::{WindowConfiguration, scene};
+
+pub const APP_INFO: app_dirs2::AppInfo = app_dirs2::AppInfo {
+    name: "Eucalyptus",
+    author: "4tkbytes",
+};
+
+fn main() -> anyhow::Result<()> {
+    #[cfg(target_os = "android")]
+    compile_error!("The `editor` feature is not supported on Android. If you are attempting\
+ to use the Eucalyptus editor on Android, please don't. Instead, use the `data-only` feature\
+ to use with dependencies or create your own game on Desktop. Sorry :(");
+    let matches = Command::new("eucalyptus-editor")
+        .about("A visual game editor")
+        .version(env!("CARGO_PKG_VERSION"))
+        .subcommand_required(false)
+        .arg_required_else_help(false)
+        .subcommand(
+            Command::new("build")
+                .about("Build a eucalyptus project, but only the .eupak file and its resources")
+                .arg(
+                    Arg::new("project")
+                        .help("Path to the .eucp project file")
+                        .value_name("PROJECT_FILE")
+                        .required(false),
+                ),
+        )
+        .subcommand(
+            Command::new("package")
+                .about("Package a eucalyptus project, which compiles the runtime and the resource .eupak file")
+                .arg(
+                    Arg::new("project")
+                        .help("Path to the .eucp project file")
+                        .value_name("PROJECT_FILE")
+                        .required(false),
+                ),
+        )
+        .subcommand(Command::new("read")
+            .about("Reads and displays the contents of a .eupak file for debugging")
+            .arg(
+                    Arg::new("eupak_file")
+                        .help("Path to the .eupak file")
+                        .value_name("RESOURCE_FILE")
+                        .required(false),
+                ),
+        )
+        .subcommand(Command::new("health").about("Check the health of the eucalyptus installation"))
+        .get_matches();
+
+    match matches.subcommand() {
+        Some(("build", sub_matches)) => {
+            let project_path = match sub_matches.get_one::<String>("project") {
+                Some(path) => PathBuf::from(path),
+                None => match find_eucp_file() {
+                    Ok(path) => path,
+                    Err(e) => {
+                        eprintln!("Error: {}", e);
+                        std::process::exit(1);
+                    }
+                },
+            };
+
+            crate::build::build(project_path)?;
+        }
+        Some(("package", sub_matches)) => {
+            let project_path = match sub_matches.get_one::<String>("project") {
+                Some(path) => PathBuf::from(path),
+                None => match find_eucp_file() {
+                    Ok(path) => path,
+                    Err(e) => {
+                        eprintln!("Error: {}", e);
+                        std::process::exit(1);
+                    }
+                },
+            };
+
+            crate::build::package(project_path, sub_matches)?;
+        }
+        Some(("health", _)) => {
+            crate::build::health()?;
+        }
+        Some(("read", sub_matches)) => {
+            let project_path = match sub_matches.get_one::<String>("eupak_file") {
+                Some(path) => PathBuf::from(path),
+                None => match find_eucp_file() {
+                    Ok(path) => path,
+                    Err(e) => {
+                        eprintln!("Error: {}", e);
+                        std::process::exit(1);
+                    }
+                },
+            };
+
+            crate::build::read_from_eupak(project_path)?;
+        }
+        None => {
+            let config = WindowConfiguration {
+                title: "Eucalyptus, built with dropbear".into(),
+                windowed_mode: dropbear_engine::WindowedModes::Maximised,
+                max_fps: dropbear_engine::App::NO_FPS_CAP,
+                app_info: APP_INFO,
+            };
+
+            let _app = dropbear_engine::run_app!(config, |mut scene_manager, mut input_manager| {
+                let main_menu = Rc::new(RefCell::new(crate::menu::MainMenu::new()));
+                let editor = Rc::new(RefCell::new(crate::editor::Editor::new()));
+
+                scene::add_scene_with_input(
+                    &mut scene_manager,
+                    &mut input_manager,
+                    main_menu,
+                    "main_menu",
+                );
+                scene::add_scene_with_input(
+                    &mut scene_manager,
+                    &mut input_manager,
+                    editor,
+                    "editor",
+                );
+
+                scene_manager.switch("main_menu");
+
+                (scene_manager, input_manager)
+            })
+            .unwrap();
+        }
+        _ => unreachable!(),
+    }
+    Ok(())
+}
+
+fn find_eucp_file() -> Result<PathBuf, String> {
+    let current_dir = std::env::current_dir().map_err(|_| "Failed to get current directory")?;
+
+    let entries = fs::read_dir(&current_dir).map_err(|_| "Failed to read current directory")?;
+
+    let mut eucp_files = Vec::new();
+
+    for entry in entries {
+        if let Ok(entry) = entry {
+            if let Some(file_name) = entry.file_name().to_str() {
+                if file_name.ends_with(".eucp") {
+                    eucp_files.push(entry.path());
+                }
+            }
+        }
+    }
+
+    match eucp_files.len() {
+        0 => Err("No .eucp files found in current directory".to_string()),
+        1 => Ok(eucp_files[0].clone()),
+        _ => Err(format!(
+            "Multiple .eucp files found: {:#?}. Please specify which one to use.",
+            eucp_files
+        )),
+    }
+}
diff --git a/eucalyptus-editor/src/menu.rs b/eucalyptus-editor/src/menu.rs
new file mode 100644
index 0000000..dc04748
--- /dev/null
+++ b/eucalyptus-editor/src/menu.rs
@@ -0,0 +1,384 @@
+use std::{
+    fs,
+    sync::mpsc::{self, Receiver},
+};
+
+use anyhow::anyhow;
+use dropbear_engine::{
+    input::{Controller, Keyboard, Mouse},
+    scene::{Scene, SceneCommand},
+};
+use egui::{self, FontId, Frame, RichText};
+use egui_toast_fork::{ToastOptions, Toasts};
+use gilrs;
+use git2::Repository;
+use log::{self, debug};
+use winit::{
+    dpi::PhysicalPosition, event::MouseButton, event_loop::ActiveEventLoop, keyboard::KeyCode,
+};
+
+use eucalyptus_core::states::{PROJECT, ProjectConfig};
+
+#[derive(Default)]
+pub struct MainMenu {
+    scene_command: SceneCommand,
+    show_new_project: bool,
+    project_name: String,
+    project_path: Option<std::path::PathBuf>,
+    project_error: Option<Vec<String>>,
+
+    progress_rx: Option<Receiver<ProjectProgress>>,
+
+    show_progress: bool,
+    progress: f32,
+    progress_message: String,
+    toast: Toasts,
+    is_in_file_dialogue: bool,
+}
+
+pub enum ProjectProgress {
+    Step {
+        progress: f32,
+        message: String,
+    },
+    #[allow(dead_code)]
+    Error(String),
+    Done,
+}
+
+impl MainMenu {
+    pub fn new() -> Self {
+        Self {
+            show_progress: false,
+            toast: Toasts::new()
+                .anchor(egui::Align2::RIGHT_BOTTOM, (-10.0, -10.0))
+                .direction(egui::Direction::BottomUp),
+            ..Default::default()
+        }
+    }
+
+    fn start_project_creation(&mut self) {
+        let (tx, rx) = mpsc::channel();
+        let project_name = self.project_name.clone();
+        let project_path = self.project_path.clone();
+
+        self.progress_rx = Some(rx);
+        self.show_progress = true;
+        self.progress = 0.0;
+        self.progress_message = "Starting project creation...".to_string();
+
+        std::thread::spawn(move || {
+            let mut errors = Vec::new();
+            let folders = [
+                ("git", 0.1, "Creating a git folder..."),
+                ("src", 0.2, "Creating src folder..."),
+                ("resources/models", 0.3, "Creating models folder..."),
+                ("resources/shaders", 0.4, "Creating shader folder..."),
+                ("resources/textures", 0.5, "Creating textures folder..."),
+                ("src2", 0.6, "Creating project config file..."),
+                ("scenes", 0.7, "Creating the scenes folder"),
+            ];
+
+            if let Some(path) = &project_path {
+                for (folder, progress, message) in folders {
+                    tx.send(ProjectProgress::Step {
+                        progress,
+                        message: message.to_string(),
+                    })
+                    .ok();
+
+                    let full_path = path.join(folder);
+                    let result: anyhow::Result<()> = if folder == "src" {
+                        if !full_path.exists() {
+                            fs::create_dir(&full_path)
+                                .map_err(|e| anyhow::anyhow!(e))
+                                .map(|_| ())
+                        } else {
+                            Ok(())
+                        }
+                    } else if folder == "git" {
+                        match Repository::init(path) {
+                            Ok(_) => Ok(()),
+                            Err(e) => {
+                                if matches!(e.code(), git2::ErrorCode::Exists) {
+                                    log::warn!("Git repository already exists");
+                                    Ok(())
+                                } else {
+                                    Err(anyhow!(e))
+                                }
+                            }
+                        }
+                    } else if folder == "src2" {
+                        if let Some(path) = &project_path {
+                            let mut config = ProjectConfig::new(project_name.clone(), &path);
+                            let _ = config.write_to_all();
+                            let mut global = PROJECT.write();
+                            *global = config;
+                            Ok(())
+                        } else {
+                            Err(anyhow!("Project path not found"))
+                        }
+                    } else {
+                        if !full_path.exists() {
+                            fs::create_dir_all(&full_path)
+                                .map_err(|e| anyhow!(e))
+                                .map(|_| ())
+                        } else {
+                            log::warn!("{:?} already exists", full_path);
+                            Ok(())
+                        }
+                    };
+                    if let Err(e) = result {
+                        tx.send(ProjectProgress::Error(e.to_string())).ok();
+                        errors.push(e);
+                    }
+                }
+                tx.send(ProjectProgress::Step {
+                    progress: 1.0,
+                    message: "Project creation complete!".to_string(),
+                })
+                .ok();
+
+                tx.send(ProjectProgress::Done).ok();
+            }
+        });
+
+        log::debug!("Starting project creation");
+    }
+}
+
+impl Scene for MainMenu {
+    fn load(&mut self, _graphics: &mut dropbear_engine::graphics::Graphics) {
+        log::info!("Loaded menu scene");
+    }
+
+    fn update(&mut self, _dt: f32, _graphics: &mut dropbear_engine::graphics::Graphics) {}
+
+    fn render(&mut self, graphics: &mut dropbear_engine::graphics::Graphics) {
+        let screen_size: (f32, f32) = (
+            graphics.state.window.inner_size().width as f32 - 100.0,
+            graphics.state.window.inner_size().height as f32 - 100.0,
+        );
+        let egui_ctx = graphics.get_egui_context();
+
+        egui::CentralPanel::default()
+                .frame(Frame::new())
+                .show(&egui_ctx, |ui| {
+                    ui.vertical_centered(|ui| {
+                        ui.add_space(64.0);
+                        ui.label(RichText::new("Eucalyptus").font(FontId::proportional(32.0)));
+                        ui.add_space(40.0);
+
+                        let button_size = egui::vec2(300.0, 60.0);
+
+                        if ui
+                            .add_sized(button_size, egui::Button::new("New Project"))
+                            .clicked()
+                        {
+                            log::debug!("Creating new project");
+                            self.show_new_project = true;
+                        }
+                        ui.add_space(20.0);
+
+                        if ui
+                            .add_sized(button_size, egui::Button::new("Open Project"))
+                            .clicked()
+                        {
+                            log::debug!("Opening project");
+                            self.is_in_file_dialogue = true;
+                            if let Some(path) = rfd::FileDialog::new()
+                                .add_filter("Eucalyptus Configuration Files", &["eucp"])
+                                .pick_file()
+                            {
+                                match ProjectConfig::read_from(&path) {
+                                    Ok(config) => {
+                                        log::info!("Loaded project!");
+                                        let mut global = PROJECT.write();
+                                        *global = config;
+                                        // println!("Loaded config info: {:#?}", global);
+                                        self.scene_command =
+                                            SceneCommand::SwitchScene(String::from("editor"));
+                                    }
+                                    Err(e) => if e.to_string().contains("missing field") {
+                                        self.toast.add(egui_toast_fork::Toast {
+                                            kind: egui_toast_fork::ToastKind::Error,
+                                            text: format!("Your project version is not up to date with the current project version").into(),
+                                            options: ToastOptions::default()
+                                                .duration_in_seconds(10.0)
+                                                .show_progress(true),
+                                            ..Default::default()
+                                        });
+                                        log::error!("Failed to load project: {}", e);
+                                    }
+                                };
+                            } else {
+                                log::warn!("File dialog returned \"None\"");
+                            }
+                            self.is_in_file_dialogue = false;
+                        }
+                        ui.add_space(20.0);
+
+                        if ui
+                            .add_sized(button_size, egui::Button::new("Settings"))
+                            .clicked()
+                        {
+                            log::debug!("Settings (not implemented)");
+                        }
+                        ui.add_space(20.0);
+
+                        if ui
+                            .add_sized(button_size, egui::Button::new("Quit"))
+                            .clicked()
+                        {
+                            self.scene_command = SceneCommand::Quit
+                        }
+                        ui.add_space(20.0);
+                    });
+                });
+
+        let mut show_new_project = self.show_new_project;
+        egui::Window::new("Create new project")
+            .open(&mut show_new_project)
+            .resizable(true)
+            .collapsible(false)
+            .fixed_size(screen_size)
+            .show(&egui_ctx, |ui| {
+                ui.vertical(|ui| {
+                    ui.heading("Project Name:");
+                    ui.add_space(5.0);
+
+                    ui.text_edit_singleline(&mut self.project_name);
+                    ui.add_space(10.0);
+
+                    ui.heading("Project Location: ");
+                    ui.add_space(5.0);
+
+                    if let Some(ref path) = self.project_path {
+                        ui.label(format!("Chosen location: {}", path.display()));
+                        ui.add_space(5.0);
+                    }
+
+                    ui.add_space(5.0);
+                    if ui.button("Choose Location").clicked() {
+                        self.is_in_file_dialogue = true;
+                        if let Some(path) = rfd::FileDialog::new()
+                            .set_title("Save Project")
+                            .set_file_name(&self.project_name)
+                            .pick_folder()
+                        {
+                            self.project_path = Some(path);
+                            log::debug!("Project will be saved at: {:?}", self.project_path);
+                        }
+                        self.is_in_file_dialogue = false;
+                    }
+
+                    let can_create = self.project_path.is_some() && !self.project_name.is_empty();
+                    if ui
+                        .add_enabled(can_create, egui::Button::new("Create Project"))
+                        .clicked()
+                    {
+                        log::info!("Creating new project at {:?}", self.project_path);
+                        self.start_project_creation();
+                        ui.ctx().request_repaint();
+                    }
+                });
+            });
+        self.show_new_project = show_new_project;
+
+        if let Some(rx) = self.progress_rx.as_mut() {
+            while let Ok(progress) = rx.try_recv() {
+                match progress {
+                    ProjectProgress::Step { progress, message } => {
+                        self.progress = progress;
+                        self.progress_message = message;
+                    }
+                    ProjectProgress::Error(err) => {
+                        self.project_error.get_or_insert_with(Vec::new).push(err);
+                    }
+                    ProjectProgress::Done if self.project_error.is_none() => {
+                        self.is_in_file_dialogue = false;
+                        self.show_new_project = false;
+                        self.show_progress = false;
+                        self.scene_command = SceneCommand::SwitchScene("editor".to_string());
+                    }
+                    ProjectProgress::Done => {}
+                }
+            }
+        }
+
+        if self.show_progress {
+            egui::Window::new("Creating Project...")
+                .collapsible(true)
+                .resizable(false)
+                .fixed_size([400.0, 120.0])
+                .show(&egui_ctx, |ui| {
+                    ui.label(&self.progress_message);
+                    ui.add_space(10.0);
+
+                    ui.add(egui::ProgressBar::new(self.progress).show_percentage());
+                    if let Some(errors) = &self.project_error {
+                        ui.colored_label(egui::Color32::RED, "Errors:");
+                        for err in errors {
+                            ui.label(err);
+                        }
+                    }
+                });
+        }
+
+        self.toast.show(&graphics.get_egui_context());
+    }
+
+    fn exit(&mut self, _event_loop: &ActiveEventLoop) {
+        log::info!("Exiting menu scene");
+    }
+
+    fn run_command(&mut self) -> SceneCommand {
+        std::mem::replace(&mut self.scene_command, SceneCommand::None)
+    }
+}
+
+impl Keyboard for MainMenu {
+    fn key_down(&mut self, key: KeyCode, event_loop: &ActiveEventLoop) {
+        if key == KeyCode::Escape {
+            if !self.show_new_project && !self.is_in_file_dialogue {
+                event_loop.exit();
+            }
+        }
+    }
+
+    fn key_up(&mut self, _key: KeyCode, _event_loop: &ActiveEventLoop) {}
+}
+
+impl Mouse for MainMenu {
+    fn mouse_move(&mut self, _position: PhysicalPosition<f64>) {}
+
+    fn mouse_down(&mut self, _button: MouseButton) {}
+
+    fn mouse_up(&mut self, _button: MouseButton) {}
+}
+
+impl Controller for MainMenu {
+    fn button_down(&mut self, button: gilrs::Button, id: gilrs::GamepadId) {
+        debug!("Controller button {:?} pressed! [{}]", button, id);
+    }
+
+    fn button_up(&mut self, button: gilrs::Button, id: gilrs::GamepadId) {
+        debug!("Controller button {:?} released! [{}]", button, id);
+    }
+
+    fn left_stick_changed(&mut self, x: f32, y: f32, id: gilrs::GamepadId) {
+        debug!("Left stick changed: x = {} | y = {} | id = {}", x, y, id);
+    }
+
+    fn right_stick_changed(&mut self, x: f32, y: f32, id: gilrs::GamepadId) {
+        debug!("Right stick changed: x = {} | y = {} | id = {}", x, y, id);
+    }
+
+    fn on_connect(&mut self, id: gilrs::GamepadId) {
+        debug!("Controller connected [{}]", id);
+    }
+
+    fn on_disconnect(&mut self, id: gilrs::GamepadId) {
+        debug!("Controller disconnected [{}]", id);
+    }
+}
diff --git a/eucalyptus-editor/src/utils.rs b/eucalyptus-editor/src/utils.rs
new file mode 100644
index 0000000..f02c6d9
--- /dev/null
+++ b/eucalyptus-editor/src/utils.rs
@@ -0,0 +1,219 @@
+use std::fs;
+use std::path::PathBuf;
+use std::sync::mpsc;
+use std::sync::mpsc::Receiver;
+use anyhow::anyhow;
+use egui::Context;
+use egui_toast_fork::{Toast, ToastOptions, Toasts};
+use git2::Repository;
+use dropbear_engine::camera::Camera;
+use dropbear_engine::scene::SceneCommand;
+use eucalyptus_core::states::{ProjectConfig, PROJECT};
+use eucalyptus_core::utils::ProjectProgress;
+
+pub fn show_new_project_window<F>(
+    ctx: &Context,
+    show_new_project: &mut bool,
+    project_name: &mut String,
+    project_path: &mut Option<PathBuf>,
+    on_create: F,
+) where
+    F: FnOnce(&str, &PathBuf),
+{
+    let screen_size = egui::vec2(400.0, 220.0);
+
+    let mut open = *show_new_project;
+    egui::Window::new("Create new project")
+        .open(&mut open)
+        .resizable(true)
+        .collapsible(false)
+        .fixed_size(screen_size)
+        .show(ctx, |ui| {
+            ui.vertical(|ui| {
+                ui.heading("Project Name:");
+                ui.add_space(5.0);
+
+                ui.text_edit_singleline(project_name);
+                ui.add_space(10.0);
+
+                ui.heading("Project Location: ");
+                ui.add_space(5.0);
+
+                if let Some(path) = project_path {
+                    ui.label(format!("Chosen location: {}", path.display()));
+                    ui.add_space(5.0);
+                }
+
+                ui.add_space(5.0);
+                if ui.button("Choose Location").clicked() {
+                    if let Some(path) = rfd::FileDialog::new()
+                        .set_title("Save Project")
+                        .set_file_name(project_name.clone())
+                        .pick_folder()
+                    {
+                        *project_path = Some(path);
+                    }
+                }
+
+                let can_create = project_path.is_some() && !project_name.is_empty();
+                if ui
+                    .add_enabled(can_create, egui::Button::new("Create Project"))
+                    .clicked()
+                {
+                    if let Some(path) = project_path {
+                        on_create(project_name, path);
+                    }
+                    ui.ctx().request_repaint();
+                }
+            });
+        });
+    *show_new_project = open;
+}
+
+/// Converts a click on a screen (like a viewport) coordinate relative to the world
+#[allow(dead_code)]
+pub fn screen_to_world_coords(
+    camera: &Camera,
+    screen_pos: egui::Pos2,
+    viewport_rect: egui::Rect,
+) -> (glam::DVec3, glam::DVec3) {
+    let viewport_width = viewport_rect.width() as f64;
+    let viewport_height = viewport_rect.height() as f64;
+
+    let ndc_x = 2.0 * (screen_pos.x as f64 - viewport_rect.min.x as f64) / viewport_width - 1.0;
+    let ndc_y = 1.0 - 2.0 * (screen_pos.y as f64 - viewport_rect.min.y as f64) / viewport_height;
+
+    let inv_view = camera.view_mat.inverse();
+    let inv_proj = camera.proj_mat.inverse();
+
+    let clip_near = glam::DVec4::new(ndc_x, ndc_y, 0.0, 1.0);
+    let clip_far = glam::DVec4::new(ndc_x, ndc_y, 1.0, 1.0);
+
+    let view_near = inv_proj * clip_near;
+    let view_far = inv_proj * clip_far;
+
+    let world_near = inv_view * glam::DVec4::new(view_near.x, view_near.y, view_near.z, 1.0);
+    let world_far = inv_view * glam::DVec4::new(view_far.x, view_far.y, view_far.z, 1.0);
+
+    let world_near = world_near.truncate() / world_near.w;
+    let world_far = world_far.truncate() / world_far.w;
+
+    (world_near, world_far)
+}
+
+/// Start creating a new project in a background thread.
+/// Returns a Receiver for progress updates.
+pub fn start_project_creation(
+    project_name: String,
+    project_path: Option<PathBuf>,
+) -> Option<Receiver<ProjectProgress>> {
+    let (tx, rx) = mpsc::channel();
+    let project_path = project_path.clone();
+
+    std::thread::spawn(move || {
+        let folders = [
+            ("git", 0.1, "Creating a git folder..."),
+            ("src", 0.2, "Creating src folder..."),
+            ("resources/models", 0.4, "Creating models folder..."),
+            ("resources/shaders", 0.6, "Creating shader folder..."),
+            ("resources/textures", 0.8, "Creating textures folder..."),
+            ("src2", 0.9, "Creating project config file..."),
+        ];
+
+        if let Some(path) = &project_path {
+            for (folder, progress, message) in folders {
+                tx.send(ProjectProgress::Step {
+                    _progress: progress,
+                    _message: message.to_string(),
+                })
+                .ok();
+
+                let full_path = path.join(folder);
+                let result: anyhow::Result<()> = if folder == "src" {
+                    if !full_path.exists() {
+                        fs::create_dir(&full_path)
+                            .map_err(|e| anyhow::anyhow!(e))
+                            .map(|_| ())
+                    } else {
+                        Ok(())
+                    }
+                } else if folder == "git" {
+                    match Repository::init(path) {
+                        Ok(_) => Ok(()),
+                        Err(e) => {
+                            if matches!(e.code(), git2::ErrorCode::Exists) {
+                                Ok(())
+                            } else {
+                                Err(anyhow!(e))
+                            }
+                        }
+                    }
+                } else if folder == "src2" {
+                    if let Some(path) = &project_path {
+                        let mut config = ProjectConfig::new(project_name.clone(), &path);
+                        let _ = config.write_to_all();
+                        let mut global = PROJECT.write();
+                        *global = config;
+                        Ok(())
+                    } else {
+                        Err(anyhow!("Project path not found"))
+                    }
+                } else {
+                    if !full_path.exists() {
+                        fs::create_dir_all(&full_path)
+                            .map_err(|e| anyhow!(e))
+                            .map(|_| ())
+                    } else {
+                        Ok(())
+                    }
+                };
+                if let Err(e) = result {
+                    tx.send(ProjectProgress::Error(e.to_string())).ok();
+                }
+            }
+            tx.send(ProjectProgress::Step {
+                _progress: 1.0,
+                _message: "Project creation complete!".to_string(),
+            })
+            .ok();
+            tx.send(ProjectProgress::Done).ok();
+        }
+    });
+
+    Some(rx)
+}
+
+#[allow(dead_code)]
+pub fn open_project(
+    scene_command: &mut SceneCommand,
+    toast: &mut Toasts,
+) -> Result<Option<SceneCommand>, String> {
+    if let Some(path) = rfd::FileDialog::new()
+        .add_filter("Eucalyptus Project Configuration Files", &["eucp"])
+        .pick_file()
+    {
+        match ProjectConfig::read_from(&path) {
+            Ok(config) => {
+                let mut global = PROJECT.write();
+                *global = config;
+                *scene_command = SceneCommand::SwitchScene("editor".to_string());
+                Ok(Some(SceneCommand::SwitchScene("editor".to_string())))
+            }
+            Err(e) => {
+                if e.to_string().contains("missing field") {
+                    toast.add(Toast {
+                        kind: egui_toast_fork::ToastKind::Error,
+                        text: "Project version is not up to date.".into(),
+                        options: ToastOptions::default()
+                            .duration_in_seconds(5.0)
+                            .show_progress(true),
+                        ..Default::default()
+                    });
+                }
+                Err(format!("Failed to load project: {e}"))
+            }
+        }
+    } else {
+        Err("File dialog returned None".to_string())
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus/Cargo.toml b/eucalyptus/Cargo.toml
deleted file mode 100644
index e50ed01..0000000
--- a/eucalyptus/Cargo.toml
+++ /dev/null
@@ -1,109 +0,0 @@
-[package]
-name = "eucalyptus"
-description = "A visual game editor for the dropbear game engine"
-default-run = "eucalyptus"
-exclude = ["resources"]
-
-version.workspace = true
-edition.workspace = true
-license-file.workspace = true
-repository.workspace = true
-readme.workspace = true
-
-[dependencies]
-anyhow = { workspace = true, optional = true }
-app_dirs2 = { workspace = true, optional = true }
-bincode = { workspace = true, optional = true }
-chrono = { workspace = true, optional = true }
-dropbear-engine = { workspace = true, optional = true }
-egui-toast-fork = { workspace = true, optional = true }
-egui = { workspace = true, optional = true }
-egui_dnd = { workspace = true, optional = true }
-egui_dock-fork = { workspace = true, optional = true }
-egui_extras = { workspace = true, optional = true }
-gilrs = { workspace = true, optional = true }
-git2 = { workspace = true, optional = true, features = ["vendored-openssl"]}
-glam = { workspace = true, optional = true }
-hecs = { workspace = true, optional = true }
-log = { workspace = true, optional = true }
-log-once = { workspace = true, optional = true }
-model_to_image = { workspace = true, optional = true }
-once_cell = { workspace = true, optional = true }
-open = { workspace = true, optional = true }
-parking_lot = { workspace = true, optional = true }
-ron = { workspace = true, optional = true }
-serde = { workspace = true, optional = true }
-tokio = { workspace = true, optional = true }
-transform-gizmo-egui = { workspace = true, optional = true }
-wgpu = { workspace = true, optional = true }
-winit = { workspace = true, optional = true }
-clap = { workspace = true, optional = true }
-walkdir = { workspace = true, optional = true }
-zip = { workspace = true, optional = true }
-bytemuck = "1.23.2"
-
-rustyscript.workspace = true
-
-[target.'cfg(not(target_os = "android"))'.dependencies]
-rfd = { workspace = true, optional = true }
-
-[features]
-editor = [
-    "anyhow",
-    "app_dirs2",
-    "bincode",
-    "chrono",
-    "dropbear-engine",
-    "egui-toast-fork",
-    "egui",
-    "egui_dnd",
-    "egui_dock-fork",
-    "egui_extras",
-    "gilrs",
-    "git2",
-    "glam",
-    "hecs",
-    "log",
-    "log-once",
-    "model_to_image",
-    "once_cell",
-    "open",
-    "parking_lot",
-    "rfd",
-    "ron",
-    "serde",
-    "tokio",
-    "transform-gizmo-egui",
-    "wgpu",
-    "winit",
-    "clap",
-    "walkdir",
-    "zip"
-]
-# default = ["editor"]
-
-data-only = [
-    "serde",
-    "bincode",
-    "anyhow",
-    "tokio",
-    "glam",
-    "log",
-    "dropbear-engine",
-    "winit",
-    "hecs",
-    "ron",
-    "log-once",
-    "once_cell",
-    "egui",
-    "chrono",
-    "parking_lot",
-    "walkdir",
-    "zip"
-]
-
-[build-dependencies]
-anyhow = "1.0"
-app_dirs2 = "2.5"
-reqwest = { version = "0.12", features = ["blocking"] }
-zip = "4.3"
diff --git a/eucalyptus/README.md b/eucalyptus/README.md
deleted file mode 100644
index 5fe767a..0000000
--- a/eucalyptus/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# eucalyptus
-
-The game editor for the dropbear game engine. The game engine is powered by [dropbear](https://github.com/4tkbytes/dropbear/tree/main/dropbear-engine) and uses the [redback](https://github.com/4tkbytes/dropbear/tree/main/redback) runtime to run and load your game in an easy way.
diff --git a/eucalyptus/build.rs b/eucalyptus/build.rs
deleted file mode 100644
index 8966f89..0000000
--- a/eucalyptus/build.rs
+++ /dev/null
@@ -1,67 +0,0 @@
-use std::fs::{self, File};
-use std::io::Cursor;
-
-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 response = reqwest::blocking::get(repo_zip_url)
-        .map_err(|e| anyhow::anyhow!("Failed to download repo zip: {}", e))?
-        .bytes()
-        .map_err(|e| anyhow::anyhow!("Failed to read zip bytes: {}", e))?;
-
-    let reader = Cursor::new(response);
-    let mut zip = zip::ZipArchive::new(reader)
-        .map_err(|e| anyhow::anyhow!("Failed to read zip archive: {}", e))?;
-
-    let app_info = app_dirs2::AppInfo {
-        name: "Eucalyptus",
-        author: "4tkbytes",
-    };
-    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))?;
-
-    fs::create_dir_all(&app_data_dir)
-        .map_err(|e| anyhow::anyhow!("Failed to create app data directory: {}", e))?;
-
-    let resource_prefix = "dropbear-main/resources/";
-    let mut found_resource = false;
-    for i in 0..zip.len() {
-        let mut file = zip.by_index(i).unwrap();
-        let name = file.name();
-
-        if name.starts_with(resource_prefix) && !name.ends_with('/') {
-            found_resource = true;
-            let rel_path = &name[resource_prefix.len()..];
-            let rel_path = rel_path.strip_prefix('/').unwrap_or(rel_path);
-            let dest_path = app_data_dir.join(rel_path);
-
-            if let Some(parent) = dest_path.parent() {
-                fs::create_dir_all(parent)
-                    .map_err(|e| anyhow::anyhow!("Failed to create parent directory: {}", e))?;
-            }
-
-            println!("Copying {} to {:?}", name, dest_path);
-
-            let mut outfile = File::create(&dest_path)
-                .map_err(|e| anyhow::anyhow!("Failed to create file: {}", e))?;
-            std::io::copy(&mut file, &mut outfile)
-                .map_err(|e| anyhow::anyhow!("Failed to copy file: {}", e))?;
-        }
-    }
-
-    if !found_resource {
-        return Err(anyhow::anyhow!(
-            "No resources folder found in the github repository [4tkbytes/dropbear] :("
-        ));
-    }
-
-    // fuck you windows :(
-    #[cfg(target_os = "windows")]
-    {
-        println!("cargo:rustc-link-arg=/FORCE:MULTIPLE");
-        println!("cargo:rustc-link-arg=/NODEFAULTLIB:libcmt.lib");
-    }
-
-    println!("cargo:rerun-if-changed=build.rs");
-    Ok(())
-}
diff --git a/eucalyptus/src/build/mod.rs b/eucalyptus/src/build/mod.rs
deleted file mode 100644
index 276aa38..0000000
--- a/eucalyptus/src/build/mod.rs
+++ /dev/null
@@ -1,469 +0,0 @@
-use std::{collections::HashMap, fs, path::PathBuf, process::Command};
-
-use clap::ArgMatches;
-use zip::write::SimpleFileOptions;
-
-use crate::states::{ProjectConfig, RuntimeData, SCENES, SOURCE};
-
-pub fn package(project_path: PathBuf, _sub_matches: &ArgMatches) -> anyhow::Result<()> {
-    if !project_path.exists() {
-        return Err(anyhow::anyhow!("Unable to locate project config file"));
-    }
-
-    let build_dir = project_path.parent().ok_or(anyhow::anyhow!("Unable to get parent"))?.join("build");
-
-    // check health
-    println!("Checking health (checking if commands exist)");
-    health()?;
-    println!("Health check completed!");
-
-    let clone_dir = build_dir.join("redback-runtime");
-    
-    if clone_dir.exists() {
-        println!("Repository directory exists, checking for updates...");
-        if should_update_repository(&clone_dir)? {
-            println!("Repository has changes or is outdated, removing and re-cloning...");
-            std::fs::remove_dir_all(&clone_dir)?;
-            clone_repository(&build_dir)?;
-        } else {
-            println!("Repository is up to date, skipping clone");
-        }
-    } else {
-        println!("Cloning repository");
-        clone_repository(&build_dir)?;
-    }
-
-    let project_config = ProjectConfig::read_from(&project_path)?;
-    let project_name = project_config.project_name.clone();
-
-    // cd into redback-runtime folder and compile redback-runtime using cargo
-    let runtime_dir = build_dir.join("redback-runtime");
-    if !runtime_dir.exists() {
-        return Err(anyhow::anyhow!("redback-runtime directory not found after cloning"));
-    }
-
-    let cargo_toml_path = runtime_dir.join("Cargo.toml");
-    if cargo_toml_path.exists() {
-        let cargo_toml_content = std::fs::read_to_string(&cargo_toml_path)?;
-        let modified_content = cargo_toml_content.replace(
-            r#"name = "redback-runtime""#, 
-            &format!(r#"name = "{}""#, project_name)
-        );
-        std::fs::write(&cargo_toml_path, modified_content)?;
-        println!("Updated Cargo.toml with project name: {}", project_name);
-    }
-
-    println!("Building {} for release", project_name);
-    let mut cargo_build = Command::new("cargo")
-        .args(&["build", "--release"])
-        .current_dir(&runtime_dir)
-        .stdout(std::process::Stdio::inherit())
-        .stderr(std::process::Stdio::inherit())
-        .spawn()?;
-
-    let exit_status = cargo_build.wait()?;
-
-    if !exit_status.success() {
-        return Err(anyhow::anyhow!("Failed to build {}", project_name));
-    }
-    println!("{} built successfully!", project_name);
-
-    let target_dir = runtime_dir.join("target").join("release");
-    let exe_name = if cfg!(target_os = "windows") {
-        format!("{}.exe", project_name)
-    } else {
-        project_name.clone()
-    };
-    
-    let built_exe = target_dir.join(&exe_name);
-    if !built_exe.exists() {
-        return Err(anyhow::anyhow!("Built executable not found at: {}", built_exe.display()));
-    }
-
-    println!("Building project data (.eupak file)");
-    build(project_path.clone())?;
-
-    let output_dir = project_path.parent().ok_or(anyhow::anyhow!("Unable to get parent"))?.join("build").join("package");
-    std::fs::create_dir_all(&output_dir)?;
-
-    let output_exe = output_dir.join(&exe_name);
-    
-    println!("Copying executable to: {}", output_exe.display());
-    std::fs::copy(&built_exe, &output_exe)?;
-
-    let eupak_source = project_path.parent().ok_or(anyhow::anyhow!("Unable to get parent"))?.join("build").join("output").join(format!("{}.eupak", project_name));
-    let eupak_dest = output_dir.join(format!("{}.eupak", project_name));
-    
-    if !eupak_source.exists() {
-        return Err(anyhow::anyhow!("Expected .eupak file not found at: {}", eupak_source.display()));
-    }
-    
-    println!("Copying .eupak file to: {}", eupak_dest.display());
-    std::fs::copy(&eupak_source, &eupak_dest)?;
-
-    let project_resources = project_path.parent().ok_or(anyhow::anyhow!("Unable to get parent"))?.join("resources");
-    if project_resources.exists() {
-        println!("Copying resources folder...");
-        let output_resources = output_dir.join("resources");
-        copy_resources_folder(&project_resources, &output_resources)?;
-    }
-
-    #[cfg(unix)]
-    {
-        use std::os::unix::fs::PermissionsExt;
-        let mut perms = std::fs::metadata(&output_exe)?.permissions();
-        perms.set_mode(0o755); // rwxr-xr-x
-        std::fs::set_permissions(&output_exe, perms)?;
-    }
-
-    copy_system_libraries(&output_dir)?;
-
-    println!("Creating zip package...");
-    let zip_path = project_path.parent().ok_or(anyhow::anyhow!("Unable to get parent"))?.join("build").join(format!("{}.zip", project_name));
-    create_zip_package(&output_dir, &zip_path)?;
-
-    println!("Cleaning up temporary files");
-    if build_dir.join("redback-runtime").exists() {
-        std::fs::remove_dir_all(build_dir.join("redback-runtime"))?;
-    }
-    if build_dir.join("output").exists() {
-        std::fs::remove_dir_all(build_dir.join("output"))?;
-    }
-
-    println!("\n✓ Package completed successfully!");
-    println!("Output directory: {}", output_dir.display());
-    println!("Zip package: {}", zip_path.display());
-    println!("Executable: {}", exe_name);
-    println!("Data file: {}.eupak", project_name);
-
-    Ok(())
-}
-
-fn clone_repository(build_dir: &PathBuf) -> anyhow::Result<()> {
-    git2::build::RepoBuilder::new()
-        .clone("https://github.com/4tkbytes/redback-runtime", &build_dir.join("redback-runtime"))?;
-    println!("Repository cloned successfully!");
-    Ok(())
-}
-
-fn should_update_repository(repo_dir: &PathBuf) -> anyhow::Result<bool> {
-    let repo = match git2::Repository::open(repo_dir) {
-        Ok(repo) => repo,
-        Err(_) => {
-            return Ok(true);
-        }
-    };
-
-    let statuses = repo.statuses(None)?;
-    if !statuses.is_empty() {
-        println!("Found local changes in repository");
-        return Ok(true);
-    }
-
-    let mut remote = repo.find_remote("origin")?;
-    remote.fetch(&["refs/heads/*:refs/remotes/origin/*"], None, None)?;
-
-    let head = repo.head()?.target().ok_or(anyhow::anyhow!("No HEAD commit"))?;
-    
-    let remote_ref = if let Ok(remote_main) = repo.find_reference("refs/remotes/origin/main") {
-        remote_main
-    } else if let Ok(remote_master) = repo.find_reference("refs/remotes/origin/master") {
-        remote_master
-    } else {
-        return Ok(true);
-    };
-
-    let remote_commit = remote_ref.target().ok_or(anyhow::anyhow!("No remote commit"))?;
-
-    Ok(head != remote_commit)
-}
-
-fn copy_resources_folder(src: &PathBuf, dest: &PathBuf) -> anyhow::Result<()> {
-    std::fs::create_dir_all(dest)?;
-
-    for entry in fs::read_dir(src)? {
-        let entry = entry?;
-        let src_path = entry.path();
-        let file_name = entry.file_name();
-
-        if file_name == "resources.eucc" {
-            continue;
-        }
-
-        let dest_path = dest.join(&file_name);
-
-        if src_path.is_dir() {
-            copy_resources_folder(&src_path, &dest_path)?;
-        } else {
-            std::fs::copy(&src_path, &dest_path)?;
-        }
-    }
-
-    Ok(())
-}
-
-fn copy_system_libraries(output_dir: &PathBuf) -> anyhow::Result<()> {
-    #[cfg(target_os = "windows")]
-    {
-        let dll_paths = vec![
-            "C:\\vcpkg\\installed\\x64-windows\\bin\\assimp-vc143-mt.dll",
-            "C:\\vcpkg\\installed\\x64-windows\\bin\\assimp.dll",
-            "C:\\Program Files\\Assimp\\bin\\assimp.dll",
-            "C:\\Program Files (x86)\\Assimp\\bin\\assimp.dll",
-        ];
-
-        for dll_path in dll_paths {
-            if std::path::Path::new(dll_path).exists() {
-                let dll_name = std::path::Path::new(dll_path).file_name().unwrap();
-                let dest = output_dir.join(dll_name);
-                std::fs::copy(dll_path, dest)?;
-                println!("Copied system library: {}", dll_name.to_string_lossy());
-                break;
-            }
-        }
-    }
-
-    #[cfg(any(target_os = "linux", target_os = "macos"))]
-    {
-        let lib_dir = output_dir.join("lib");
-        std::fs::create_dir_all(&lib_dir)?;
-
-        let lib_paths = vec![
-            "/usr/lib/libassimp.so",
-            "/usr/lib/x86_64-linux-gnu/libassimp.so",
-            "/usr/lib64/libassimp.so",
-            "/usr/local/lib/libassimp.so",
-            "/opt/homebrew/lib/libassimp.dylib",
-            "/usr/local/lib/libassimp.dylib",
-        ];
-
-        for lib_path in lib_paths {
-            if std::path::Path::new(lib_path).exists() {
-                let lib_name = std::path::Path::new(lib_path).file_name().unwrap();
-                let dest = lib_dir.join(lib_name);
-                std::fs::copy(lib_path, dest)?;
-                println!("Copied system library: {}", lib_name.to_string_lossy());
-                break;
-            }
-        }
-    }
-
-    Ok(())
-}
-
-fn create_zip_package(source_dir: &PathBuf, zip_path: &PathBuf) -> anyhow::Result<()> {
-    let file = std::fs::File::create(zip_path)?;
-    let mut zip = zip::ZipWriter::new(file);
-
-    let walkdir = walkdir::WalkDir::new(source_dir);
-    for entry in walkdir {
-        let entry = entry?;
-        let path = entry.path();
-        
-        if path.is_file() {
-            let relative_path = path.strip_prefix(source_dir)?;
-            let name = relative_path.to_string_lossy();
-            
-            let options: SimpleFileOptions = Default::default();
-            zip.start_file(name, options.into())?;
-            let mut file = std::fs::File::open(path)?;
-            std::io::copy(&mut file, &mut zip)?;
-        }
-    }
-
-    zip.finish()?;
-    println!("Created zip package: {}", zip_path.display());
-    Ok(())
-}
-
-pub fn read_from_eupak(eupak_path: PathBuf) -> anyhow::Result<()> {
-    let bytes = std::fs::read(&eupak_path)?;
-    let (content, _): (RuntimeData, usize) = bincode::decode_from_slice(&bytes, bincode::config::standard())?;
-    println!("{} contents: {:#?}", eupak_path.display(), content);
-    Ok(())
-}
-
-pub fn build(
-        project_path: PathBuf,
-        // _sub_matches: &ArgMatches
-    ) -> anyhow::Result<PathBuf> {
-    println!(" > Starting build");
-    if !project_path.exists() {
-        return Err(anyhow::anyhow!(format!("Unable to locate project config file: [{}]", project_path.display())));
-    }
-    // ProjectConfig::read_from(&project_path)?.load_config_to_memory()?;
-    
-    let mut project_config = ProjectConfig::read_from(&project_path)?;
-    log::info!(" > Reading from project config");
-    project_config.load_config_to_memory()?;
-    log::info!(" > Loading config to memory");
-
-    let source_config = {
-        let source_guard = SOURCE.read().map_err(|_| anyhow::anyhow!("Unable to lock SOURCE"))?;
-        source_guard.clone()
-    };
-    log::info!(" > Copied source config");
-
-    let scene_data = {
-        let scenes_guard = SCENES.read().map_err(|_| anyhow::anyhow!("Unable to lock SCENES"))?;
-        scenes_guard.clone()
-    };
-    log::info!(" > Copied scene data");
-
-    let build_dir = project_path.parent().unwrap().join("build").join("output");
-    fs::create_dir_all(&build_dir)?;
-    log::info!(" > Created build dir");
-
-    let project_name = project_config.project_name.clone();
-
-    let mut scripts = HashMap::new();
-    let script_dir = project_path.parent().unwrap().join("src");
-    if script_dir.exists() {
-        for entry in fs::read_dir(&script_dir)? {
-            let entry = entry?;
-            let path = entry.path();
-            if let Some(ext) = path.extension() {
-                if ext == "rhai" {
-                    let name = path.file_name().unwrap().to_string_lossy().to_string();
-                    let contents = fs::read_to_string(&path)?;
-                    println!(" > Copied script info from [{}]", name);
-                    scripts.insert(name, contents);
-                }
-            }
-        }
-    }
-
-    let runtime_data = RuntimeData {
-        project_config,
-        source_config,
-        scene_data,
-        scripts
-    };
-    log::info!(" > Created runtime data structures");
-
-    let runtime_file = build_dir.join(format!("{}.eupak", project_name));
-    let serialized = bincode::serde::encode_to_vec(runtime_data, bincode::config::standard())?;
-    std::fs::write(&runtime_file, serialized)?;
-    log::info!(" > Written the file to build location");
-
-    println!("Build completed successfully. Output at {:?}", runtime_file.display());
-    Ok(runtime_file)
-}
-
-pub fn health() -> anyhow::Result<()> {
-    let mut all_healthy = true;
-    
-    match Command::new("cargo").arg("--version").output() {
-        Ok(output) => {
-            if output.status.success() {
-                let version = String::from_utf8_lossy(&output.stdout).trim().to_string();
-                println!("Does cargo exist? ✓ YES - {}", version);
-                
-                match Command::new("rustc").arg("--version").output() {
-                    Ok(rustc_output) => {
-                        if rustc_output.status.success() {
-                            let rustc_version = String::from_utf8_lossy(&rustc_output.stdout).trim().to_string();
-                            println!("Does rustc compiler exist? ✓ YES - {}", rustc_version);
-                        } else {
-                            println!("Does rustc compiler exist? ✗ NO - rustc command failed");
-                            all_healthy = false;
-                        }
-                    }
-                    Err(_) => {
-                        println!("Does rustc compiler exist? ✗ NO - rustc not found in PATH");
-                        all_healthy = false;
-                    }
-                }
-            } else {
-                println!("Does cargo exist? ✗ NO - cargo command failed");
-                println!("Does rustc compiler exist? ✗ NO - cargo failed, cannot check rustc");
-                all_healthy = false;
-            }
-        }
-        Err(_) => {
-            println!("Does cargo exist? ✗ NO - cargo not found in PATH");
-            println!("Does rustc compiler exist? ✗ NO - cargo not found, cannot check rustc");
-            all_healthy = false;
-        }
-    }
-    
-    let assimp_found = check_assimp_availability();
-    if assimp_found {
-        println!("Does assimp lib exist? ✓ YES - Found assimp library");
-    } else {
-        println!("Does assimp lib exist? ⚠ MAYBE - Could not definitively locate assimp, but it may be available through system package manager or vcpkg");
-    }
-    
-    if all_healthy {
-        println!("\n✓ All core tools are available!");
-    } else {
-        println!("\n✗ Some required tools are missing. Please install Rust and Cargo.");
-        return Err(anyhow::anyhow!("Health check failed - missing required tools"));
-    }
-    
-    Ok(())
-}
-
-fn check_assimp_availability() -> bool {
-    #[cfg(target_os = "windows")]
-    {
-        let common_paths = vec![
-            "C:\\vcpkg\\installed\\x64-windows\\lib\\assimp-vc143-mt.lib",
-            "C:\\vcpkg\\installed\\x64-windows\\lib\\assimp.lib",
-            "C:\\vcpkg\\installed\\x86-windows\\lib\\assimp-vc143-mt.lib",
-            "C:\\vcpkg\\installed\\x86-windows\\lib\\assimp.lib",
-            "C:\\Program Files\\Assimp\\lib\\assimp.lib",
-            "C:\\Program Files (x86)\\Assimp\\lib\\assimp.lib",
-        ];
-        
-        for path in common_paths {
-            if std::path::Path::new(path).exists() {
-                return true;
-            }
-        }
-        
-        if let Ok(output) = Command::new("where").arg("assimp.dll").output() {
-            if output.status.success() {
-                return true;
-            }
-        }
-    }
-    
-    #[cfg(any(target_os = "linux", target_os = "macos"))]
-    {
-        let common_paths = vec![
-            "/usr/lib/libassimp.so",
-            "/usr/lib/x86_64-linux-gnu/libassimp.so",
-            "/usr/lib64/libassimp.so",
-            "/usr/local/lib/libassimp.so",
-            "/opt/homebrew/lib/libassimp.dylib",
-            "/usr/local/lib/libassimp.dylib",
-        ];
-        
-        for path in common_paths {
-            if std::path::Path::new(path).exists() {
-                return true;
-            }
-        }
-        
-        if let Ok(output) = Command::new("pkg-config").args(&["--exists", "assimp"]).output() {
-            if output.status.success() {
-                return true;
-            }
-        }
-        
-        #[cfg(target_os = "linux")]
-        {
-            if let Ok(output) = Command::new("ldconfig").args(&["-p"]).output() {
-                if output.status.success() {
-                    let output_str = String::from_utf8_lossy(&output.stdout);
-                    if output_str.contains("libassimp") {
-                        return true;
-                    }
-                }
-            }
-        }
-    }
-    
-    false
-}
diff --git a/eucalyptus/src/camera.rs b/eucalyptus/src/camera.rs
deleted file mode 100644
index 9c44955..0000000
--- a/eucalyptus/src/camera.rs
+++ /dev/null
@@ -1,276 +0,0 @@
-use std::{collections::HashSet, time::Instant};
-
-use dropbear_engine::camera::Camera;
-use egui::{CollapsingHeader, Ui};
-use glam::DVec3;
-use hecs::Entity;
-use serde::{Deserialize, Serialize};
-use winit::keyboard::KeyCode;
-
-use crate::editor::{component::Component, EntityType, Signal, StaticallyKept, UndoableAction};
-
-#[derive(Debug, Clone)]
-pub struct CameraComponent {
-    pub speed: f64,
-    pub sensitivity: f64,
-    pub fov_y: f64,
-    pub camera_type: CameraType
-}
-
-impl CameraComponent {
-    pub fn new() -> Self {
-        Self {
-            speed: 5.0,
-            sensitivity: 0.002,
-            fov_y: 60.0,
-            camera_type: CameraType::Normal,
-        }
-    }
-
-    pub fn update(&mut self, camera: &mut Camera) {
-        camera.speed = self.speed;
-        camera.sensitivity = self.sensitivity;
-        camera.fov_y = self.fov_y;
-    }
-
-    // setting camera offset is just adding the CameraFollowTarget struct
-    // to the ecs system
-}
-
-pub struct PlayerCamera;
-
-impl PlayerCamera {
-    pub fn new() -> CameraComponent {
-        CameraComponent {
-            camera_type: CameraType::Player,
-            ..CameraComponent::new()
-        }
-    }
-
-    pub fn handle_keyboard_input(
-        camera: &mut Camera,
-        pressed_keys: &HashSet<KeyCode>
-    ) {
-        for key in pressed_keys {
-            match key {
-                KeyCode::KeyW => camera.move_forwards(),
-                KeyCode::KeyA => camera.move_left(),
-                KeyCode::KeyD => camera.move_right(),
-                KeyCode::KeyS => camera.move_back(),
-                KeyCode::ShiftLeft => camera.move_down(),
-                KeyCode::Space => camera.move_up(),
-                _ => {}
-            }
-        }
-    }
-
-    pub fn handle_mouse_input(camera: &mut Camera, component: &CameraComponent, mouse_delta: Option<(f64, f64)>) {
-        if let Some((dx, dy)) = mouse_delta {
-            camera.track_mouse_delta(dx * component.sensitivity, dy * component.sensitivity);
-        }
-    }
-}
-
-pub struct DebugCamera;
-
-impl DebugCamera {
-    pub fn new() -> CameraComponent {
-        CameraComponent {
-            camera_type: CameraType::Debug,
-            ..CameraComponent::new()
-        }
-    }
-
-    pub fn handle_keyboard_input(
-        camera: &mut Camera,
-        pressed_keys: &HashSet<KeyCode>
-    ) {
-        for key in pressed_keys {
-            match key {
-                KeyCode::KeyW => camera.move_forwards(),
-                KeyCode::KeyA => camera.move_left(),
-                KeyCode::KeyD => camera.move_right(),
-                KeyCode::KeyS => camera.move_back(),
-                KeyCode::ShiftLeft => camera.move_down(),
-                KeyCode::Space => camera.move_up(),
-                _ => {}
-            }
-        }
-    }
-
-    pub fn handle_mouse_input(camera: &mut Camera, component: &CameraComponent, mouse_delta: Option<(f64, f64)>) {
-        if let Some((dx, dy)) = mouse_delta {
-            camera.track_mouse_delta(dx * component.sensitivity, dy * component.sensitivity);
-        }
-    }
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct CameraFollowTarget {
-    pub follow_target: String,
-    pub offset: DVec3,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
-pub enum CameraType {
-    Normal,
-    Debug,
-    Player,
-}
-
-impl Default for CameraType {
-    fn default() -> Self {
-        Self::Normal
-    }
-}
-
-#[derive(Debug, Clone)]
-pub enum CameraAction {
-    SetPlayerTarget { entity: hecs::Entity, offset: DVec3 },
-    ClearPlayerTarget,
-}
-
-#[cfg(feature = "editor")]
-impl Component for Camera {
-    fn inspect(&mut self, entity: &mut Entity, cfg: &mut StaticallyKept, ui: &mut Ui, undo_stack: &mut Vec<UndoableAction>, _signal: &mut Signal, _label: &mut String) {
-        let _ = _signal;
-        ui.group(|ui| {
-            CollapsingHeader::new("Camera").default_open(true).show(ui, |ui| {
-                ui.horizontal(|ui| {
-                    ui.label("Name: ");
-                    let resp = ui.text_edit_singleline(&mut self.label);
-
-                    if resp.changed() {
-                        if cfg.old_label_entity.is_none() {
-                            cfg.old_label_entity = Some(entity.clone());
-                            cfg.label_original = Some(self.label.clone());
-                        }
-                        cfg.label_last_edit = Some(Instant::now());
-                    }
-
-                    if resp.lost_focus() {
-                        if let Some(ent) = cfg.old_label_entity.take() {
-                            if ent == *entity {
-                                if let Some(orig) = cfg.label_original.take() {
-                                    UndoableAction::push_to_undo(
-                                        undo_stack,
-                                        UndoableAction::Label(ent, orig, EntityType::Entity),
-                                    );
-                                    log::debug!("Pushed camera label change to undo stack");
-                                }
-                            } else {
-                                cfg.label_original = None;
-                            }
-                        }
-                        cfg.label_last_edit = None;
-                    }
-                });
-
-                ui.horizontal(|ui| {
-                    ui.label("Position:");
-                    ui.label(format!("{:.2}, {:.2}, {:.2}", self.eye.x, self.eye.y, self.eye.z));
-                });
-
-                ui.horizontal(|ui| {
-                    ui.label("Target:");
-                    ui.label(format!("{:.2}, {:.2}, {:.2}", self.target.x, self.target.y, self.target.z));
-                });
-            });
-        });
-    }
-}
-
-#[derive(Debug)]
-#[cfg(feature = "editor")]
-pub enum UndoableCameraAction {
-    Speed(hecs::Entity, f64),
-    Sensitivity(hecs::Entity, f64),
-    FOV(hecs::Entity, f64),
-    Type(hecs::Entity, CameraType),
-}
-
-#[cfg(feature = "editor")]
-impl Component for CameraComponent {
-    fn inspect(&mut self, _entity: &mut Entity, _cfg: &mut StaticallyKept, ui: &mut Ui, _undo_stack: &mut Vec<UndoableAction>, _signal: &mut Signal, _label: &mut String) {
-        ui.group(|ui| {
-            CollapsingHeader::new("Camera Component").default_open(true).show(ui, |ui| {
-                ui.horizontal(|ui| {
-                    ui.label("Type:");
-                    egui::ComboBox::from_label("")
-                        .selected_text(format!("{:?}", self.camera_type))
-                        .show_ui(ui, |ui| {
-                            ui.selectable_value(&mut self.camera_type, CameraType::Normal, "Normal");
-                            if !matches!(self.camera_type, CameraType::Player) {
-                                ui.selectable_value(&mut self.camera_type, CameraType::Debug, "Debug");
-                            } else {
-                                ui.add_enabled(false, egui::Button::new("Debug"));
-                                ui.label("Debug not available for player cameras");
-                            }
-                            ui.selectable_value(&mut self.camera_type, CameraType::Player, "Player");
-                        });
-                });
-
-                ui.horizontal(|ui| {
-                    ui.label("Speed:");
-                    ui.add(egui::DragValue::new(&mut self.speed).speed(0.1).range(0.1..=20.0));
-                });
-
-                ui.horizontal(|ui| {
-                    ui.label("Sensitivity:");
-                    ui.add(egui::DragValue::new(&mut self.sensitivity).speed(0.0001).range(0.0001..=1.0));
-                });
-
-                ui.horizontal(|ui| {
-                    ui.label("FOV:");
-                    ui.add(egui::Slider::new(&mut self.fov_y, 10.0..=120.0).suffix("°"));
-                });
-
-                if matches!(self.camera_type, CameraType::Player) {
-                    ui.separator();
-                    ui.label("Player Camera Controls:");
-                    if ui.button("Set as Active Camera").clicked() {
-                        // This would need to be implemented via signal
-                        log::info!("Set player camera as active (not implemented)");
-                    }
-                }
-            });
-        });
-    }
-}
-
-#[cfg(feature = "editor")]
-impl Component for CameraFollowTarget {
-    fn inspect(&mut self, _entity: &mut Entity, _cfg: &mut StaticallyKept, ui: &mut Ui, _undo_stack: &mut Vec<UndoableAction>, signal: &mut Signal, _label: &mut String) {
-        ui.group(|ui| {
-            CollapsingHeader::new("Camera Follow Target").default_open(true).show(ui, |ui| {
-                ui.horizontal(|ui| {
-                    ui.label("Target Entity:");
-                    ui.text_edit_singleline(&mut self.follow_target);
-                });
-
-                ui.horizontal(|ui| {
-                    ui.label("Offset:");
-                });
-
-                ui.horizontal(|ui| {
-                    ui.label("X:");
-                    ui.add(egui::DragValue::new(&mut self.offset.x).speed(0.1));
-                });
-
-                ui.horizontal(|ui| {
-                    ui.label("Y:");
-                    ui.add(egui::DragValue::new(&mut self.offset.y).speed(0.1));
-                });
-
-                ui.horizontal(|ui| {
-                    ui.label("Z:");
-                    ui.add(egui::DragValue::new(&mut self.offset.z).speed(0.1));
-                });
-
-                if ui.button("Clear Target").clicked() {
-                    *signal = Signal::CameraAction(CameraAction::ClearPlayerTarget);
-                }
-            });
-        });
-    }
-}
\ No newline at end of file
diff --git a/eucalyptus/src/camera1.rs b/eucalyptus/src/camera1.rs
deleted file mode 100644
index ccd6c65..0000000
--- a/eucalyptus/src/camera1.rs
+++ /dev/null
@@ -1,348 +0,0 @@
-//! Old code for managing cameras. Currently in a deprecated state, and used for reference
-//! in the case something doesn't match up with my camera controllers
-//! or I need advice. 
-
-use std::{any::Any, collections::HashSet};
-
-use dropbear_engine::{camera::Camera, entity::Transform};
-use glam::DVec3;
-use serde::{Deserialize, Serialize};
-use winit::keyboard::KeyCode;
-
-pub trait CameraController: std::fmt::Debug {
-    fn update(&mut self, camera: &mut Camera, dt: f32);
-    fn handle_keyboard_input(&mut self, camera: &mut Camera, pressed_keys: &HashSet<KeyCode>);
-    fn handle_mouse_input(&mut self, camera: &mut Camera, mouse_delta: Option<(f64, f64)>);
-
-    fn as_any(&self) -> &dyn Any;
-    fn as_any_mut(&mut self) -> &mut dyn Any;
-}
-
-#[derive(Debug)]
-pub struct DebugCameraController {
-    #[allow(dead_code)]
-    pub speed: f64,
-    pub sensitivity: f64,
-}
-
-impl DebugCameraController {
-    pub fn new() -> Self {
-        Self {
-            speed: 0.125,
-            sensitivity: 0.002,
-        }
-    }
-}
-
-impl CameraController for DebugCameraController {
-    fn update(&mut self, _camera: &mut Camera, _dt: f32) {
-        // Debug camera doesn't need frame-based updates
-    }
-
-    fn handle_keyboard_input(
-        &mut self,
-        camera: &mut Camera,
-        pressed_keys: &std::collections::HashSet<KeyCode>,
-    ) {
-        for key in pressed_keys {
-            match key {
-                KeyCode::KeyW => camera.move_forwards(),
-                KeyCode::KeyA => camera.move_left(),
-                KeyCode::KeyD => camera.move_right(),
-                KeyCode::KeyS => camera.move_back(),
-                KeyCode::ShiftLeft => camera.move_down(),
-                KeyCode::Space => camera.move_up(),
-                _ => {}
-            }
-        }
-    }
-
-    fn handle_mouse_input(&mut self, camera: &mut Camera, mouse_delta: Option<(f64, f64)>) {
-        if let Some((dx, dy)) = mouse_delta {
-            camera.track_mouse_delta(dx * self.sensitivity, dy * self.sensitivity);
-        }
-    }
-
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
-    fn as_any_mut(&mut self) -> &mut dyn Any {
-        self
-    }
-}
-
-#[allow(dead_code)]
-#[derive(Debug)]
-pub struct PlayerCameraController {
-    pub follow_target: Option<hecs::Entity>,
-    pub offset: DVec3,
-    pub follow_speed: f64,
-    pub look_sensitivity: f64,
-}
-
-#[allow(dead_code)]
-impl PlayerCameraController {
-    pub fn new() -> Self {
-        Self {
-            follow_target: None,
-            offset: DVec3::new(0.0, 2.0, -5.0),
-            follow_speed: 5.0,
-            look_sensitivity: 0.002,
-        }
-    }
-
-    pub fn set_follow_target(&mut self, entity: hecs::Entity) {
-        self.follow_target = Some(entity);
-    }
-
-    pub fn clear_follow_target(&mut self) {
-        self.follow_target = None;
-    }
-
-    pub fn set_offset(&mut self, offset: DVec3) {
-        self.offset = offset;
-    }
-
-    pub fn get_follow_target(&self) -> Option<hecs::Entity> {
-        self.follow_target
-    }
-}
-
-impl CameraController for PlayerCameraController {
-    fn update(&mut self, _camera: &mut Camera, _dt: f32) {
-        // todo: implement following the entity
-    }
-
-    fn handle_keyboard_input(
-        &mut self,
-        camera: &mut Camera,
-        pressed_keys: &std::collections::HashSet<KeyCode>,
-    ) {
-        // todo: handle keyboard input, make it custom according to user
-        for key in pressed_keys {
-            match key {
-                KeyCode::KeyW => camera.move_forwards(),
-                KeyCode::KeyA => camera.move_left(),
-                KeyCode::KeyD => camera.move_right(),
-                KeyCode::KeyS => camera.move_back(),
-                KeyCode::ShiftLeft => camera.move_down(),
-                KeyCode::Space => camera.move_up(),
-                _ => {}
-            }
-        }
-    }
-
-    fn handle_mouse_input(&mut self, camera: &mut Camera, mouse_delta: Option<(f64, f64)>) {
-        if let Some((dx, dy)) = mouse_delta {
-            camera.track_mouse_delta(dx * self.look_sensitivity, dy * self.look_sensitivity);
-        }
-    }
-
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
-    fn as_any_mut(&mut self) -> &mut dyn Any {
-        self
-    }
-}
-
-use std::collections::HashMap;
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
-pub enum CameraType {
-    Debug,
-    Player,
-}
-
-#[derive(Debug)]
-pub struct CameraManager {
-    cameras: HashMap<CameraType, Camera>,
-    controllers: HashMap<CameraType, Box<dyn CameraController>>,
-    active_camera: CameraType,
-}
-
-impl CameraManager {
-    pub fn new() -> Self {
-        Self {
-            cameras: HashMap::new(),
-            controllers: HashMap::new(),
-            active_camera: CameraType::Debug,
-        }
-    }
-
-    pub fn add_camera(
-        &mut self,
-        camera_type: CameraType,
-        camera: Camera,
-        controller: Box<dyn CameraController>,
-    ) {
-        self.cameras.insert(camera_type, camera);
-        self.controllers.insert(camera_type, controller);
-    }
-
-    pub fn set_active(&mut self, camera_type: CameraType) {
-        if self.cameras.contains_key(&camera_type) {
-            self.active_camera = camera_type;
-        }
-    }
-
-    pub fn get_active(&self) -> Option<&Camera> {
-        self.cameras.get(&self.active_camera)
-    }
-
-    pub fn get_active_mut(&mut self) -> Option<&mut Camera> {
-        self.cameras.get_mut(&self.active_camera)
-    }
-
-    pub fn get_active_type(&self) -> CameraType {
-        self.active_camera
-    }
-
-    pub fn update_all(&mut self, dt: f32, graphics: &dropbear_engine::graphics::Graphics) {
-        for (camera_type, camera) in self.cameras.iter_mut() {
-            if let Some(controller) = self.controllers.get_mut(camera_type) {
-                controller.update(camera, dt);
-            }
-            camera.update(graphics);
-        }
-    }
-
-    pub fn handle_input(
-        &mut self,
-        pressed_keys: &std::collections::HashSet<KeyCode>,
-        mouse_delta: Option<(f64, f64)>,
-    ) {
-        if let Some(camera) = self.cameras.get_mut(&self.active_camera) {
-            if let Some(controller) = self.controllers.get_mut(&self.active_camera) {
-                controller.handle_keyboard_input(camera, pressed_keys);
-                controller.handle_mouse_input(camera, mouse_delta);
-            }
-        }
-    }
-
-    pub fn update_camera_following(&mut self, world: &hecs::World, _dt: f32) {
-        if let Some(player_camera) = self.cameras.get_mut(&CameraType::Player) {
-            if let Some(player_controller) = self.controllers.get_mut(&CameraType::Player) {
-                if let Some(controller) = player_controller
-                    .as_any_mut()
-                    .downcast_mut::<PlayerCameraController>()
-                {
-                    if let Some(target_entity) = controller.follow_target {
-                        match world.get::<&Transform>(target_entity) {
-                            Ok(transform) => {
-                                let target_pos = transform.position + controller.offset;
-
-                                let raw_dir = player_camera.target - player_camera.eye;
-                                let look_direction = if raw_dir.length() > 1e-6 {
-                                    raw_dir.normalize()
-                                } else {
-                                    let fwd = player_camera.forward();
-                                    if fwd.length().is_finite() && fwd.length() > 1e-6 {
-                                        fwd
-                                    } else {
-                                        glam::DVec3::new(0.0, 0.0, 1.0)
-                                    }
-                                };
-
-                                player_camera.eye = target_pos;
-                                player_camera.target = target_pos + look_direction;
-                            }
-                            Err(_) => {
-                                log_once::error_once!("Follow target entity {:?} not present in world", target_entity);
-                            }
-                        }
-                    } else {
-                        log_once::error_once!("Unable to follow camera: No follow target exists in controller");
-                    }
-                } else {
-                    log_once::error_once!("Unable to follow camera: Failed to downcast mut");
-                }
-            } else {
-                log_once::error_once!("Unable to follow camera: No player controller exists");
-            }
-        } else {
-            log_once::error_once!("Unable to follow camera: No camera exists for player");
-        }
-    }
-
-    #[allow(dead_code)]
-    pub fn set_player_camera_target(&mut self, entity: hecs::Entity, offset: DVec3) {
-        if let Some(controller) = self.controllers.get_mut(&CameraType::Player) {
-            if let Some(player_controller) = controller
-                .as_any_mut()
-                .downcast_mut::<PlayerCameraController>()
-            {
-                player_controller.set_follow_target(entity);
-                player_controller.set_offset(offset);
-            }
-        }
-    }
-
-    #[allow(dead_code)]
-    pub fn clear_player_camera_target(&mut self) {
-        if let Some(controller) = self.controllers.get_mut(&CameraType::Player) {
-            if let Some(player_controller) = controller
-                .as_any_mut()
-                .downcast_mut::<PlayerCameraController>()
-            {
-                player_controller.clear_follow_target();
-            }
-        }
-    }
-
-    #[allow(dead_code)]
-    pub fn get_player_camera_target(&self) -> Option<hecs::Entity> {
-        if let Some(controller) = self.controllers.get(&CameraType::Player) {
-            if let Some(player_controller) =
-                controller.as_any().downcast_ref::<PlayerCameraController>()
-            {
-                return player_controller.get_follow_target();
-            }
-        }
-        None
-    }
-
-    pub fn get_camera(&self, camera_type: &CameraType) -> Option<&Camera> {
-        self.cameras.get(camera_type)
-    }
-
-    #[allow(dead_code)]
-    pub fn get_camera_mut(&mut self, camera_type: &CameraType) -> Option<&mut Camera> {
-        self.cameras.get_mut(camera_type)
-    }
-
-    #[allow(dead_code)]
-    pub fn has_camera(&self, camera_type: &CameraType) -> bool {
-        self.cameras.contains_key(camera_type)
-    }
-
-    #[allow(dead_code)]
-    pub fn remove_camera(&mut self, camera_type: &CameraType) -> Option<Camera> {
-        self.controllers.remove(camera_type);
-        self.cameras.remove(camera_type)
-    }
-
-    pub fn clear_cameras(&mut self) {
-        self.cameras.clear();
-        self.controllers.clear();
-    }
-
-    pub fn get_player_camera_offset(&self) -> Option<DVec3> {
-        if let Some(controller) = self.controllers.get(&CameraType::Player) {
-            if let Some(player_controller) =
-                controller.as_any().downcast_ref::<PlayerCameraController>()
-            {
-                return Some(player_controller.offset);
-            }
-        }
-        None
-    }
-}
-
-#[derive(Debug)]
-pub enum CameraAction {
-    SetPlayerTarget { entity: hecs::Entity, offset: DVec3 },
-    ClearPlayerTarget,
-}
diff --git a/eucalyptus/src/debug.rs b/eucalyptus/src/debug.rs
deleted file mode 100644
index 690a47f..0000000
--- a/eucalyptus/src/debug.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-//! Used to aid with debugging any issues with the editor.
-use egui::Ui;
-use crate::editor::Signal;
-
-/// Show a menu bar for debug. A new "Debug" menu button will show up on the editors menu bar.
-pub(crate) fn show_menu_bar(ui: &mut Ui, signal: &mut Signal) {
-    ui.menu_button("Debug", |ui_debug| {
-        if ui_debug.button("Panic").clicked() {
-            log::warn!("Panic caused on purpose from Menu Button Click");
-            panic!("Testing out panicking with new panic module, this is a test")
-        }
-
-        if ui_debug.button("Show Entities Loaded").clicked() {
-            log::info!("Show Entities Loaded under Debug Menu is clicked");
-            *signal = Signal::LogEntities;
-        }
-    });
-}
\ No newline at end of file
diff --git a/eucalyptus/src/dropbear.ts b/eucalyptus/src/dropbear.ts
deleted file mode 100644
index 1ffce98..0000000
--- a/eucalyptus/src/dropbear.ts
+++ /dev/null
@@ -1,1115 +0,0 @@
-// Modules for the dropbear-engine scripting component
-// Made by 4tkbytes
-// EDIT THIS IF YOU WISH, I RECOMMEND YOU NOT TOUCH IT
-
-/**
- * A class describing the position, scale and rotation to be able to manipulate
- * the entity's location. 
- */
-export class Transform {
-    /**
-     * A {@link Vector3} describing the position of the entity
-     */
-    position: Vector3;
-    /**
-     * A {@link Quaternion} describing the rotation of the entity
-     */
-    rotation: Quaternion;
-    /**
-     * A {@link Vector3} describing the scale of the entity
-     */
-    scale: Vector3;
-
-    public constructor(position?: Vector3, rotation?: Quaternion, scale?: Vector3) {
-        this.position = position || Vector3.zero();
-        this.rotation = rotation || Quaternion.identity();
-        this.scale = scale || Vector3.one();
-    }
-
-    /**
-     * Translates/Offsets the position of the entity by a {@link Vector3}
-     * 
-     * # Example
-     * @example
-     * ```ts
-     * let transform = new Transform();
-     * transform.translate(new Vector3(1.0, 1.0, 1.0));
-     * ```
-     * 
-     * @param movement - A {@link Vector3} for position
-     */
-    public translate(movement: Vector3) {
-        this.position.x += movement.x;
-        this.position.y += movement.y;
-        this.position.z += movement.z;
-    }
-
-    /**
-     * Rotates the transformables rotation on the X axis
-     * 
-     * # Example
-     * @example
-     * ```ts
-     * let transform = new Transform();
-     * transform.rotateX(dbMath.degreesToRadians(180));
-     * ```
-     * 
-     * @param angle - The angle in radians
-     */
-    public rotateX(angle: number) {
-        const rotQuat = Quaternion.fromAxisAngle(new Vector3(1, 0, 0), angle);
-        this.rotation = Quaternion.multiply(this.rotation, rotQuat);
-        
-    }
-
-    /**
-     * Rotates the transformables rotation on the Y axis
-     * 
-     * # Example
-     * ```ts
-     * let transform = new Transform();
-     * transform.rotateY(dbMath.degreesToRadians(180))
-     * ```
-     * 
-     * @param angle - The angle in radians
-     */
-    public rotateY(angle: number) {
-        const rotQuat = Quaternion.fromAxisAngle(new Vector3(0, 1, 0), angle);
-        this.rotation = Quaternion.multiply(this.rotation, rotQuat);        
-    }
-
-    /**
-     * Rotates the transformables rotation on the Z axis
-     * 
-     * # Example
-     * ```ts
-     * let transform = new Transform();
-     * transform.rotateZ(dbMath.degreesToRadians(180))
-     * ```
-     * 
-     * @param angle - The angle in radians
-     */
-    public rotateZ(angle: number) {
-        const rotQuat = Quaternion.fromAxisAngle(new Vector3(0, 0, 1), angle);
-        this.rotation = Quaternion.multiply(this.rotation, rotQuat);
-    }
-
-    /**
-     * Uniformly scales the entity by a multiplier. 
-     * 
-     * # Example
-     * ```ts
-     * let transform = new Transform();
-     * transform.scaleUniform(2.0)
-     * ```
-     * 
-     * @param scale - A number that the scale multiplies by
-     */
-    public scaleUniform(scale: number) {
-        this.scale.x *= scale;
-        this.scale.y *= scale;
-        this.scale.z *= scale;
-    }
-
-    /**
-     * Individually scales the entity by a multiplier by using a {@link Vector3}
-     * 
-     * # Example
-     * ```ts
-     * let transform = new Transform();
-     * transform.scaleIndividual(new Vector3(1.0, 2.0, 1.5))
-     * ```
-     * 
-     * @param scale - A Vector3 representing the scale.x, scale.y and scale.z values
-     */
-    public scaleIndividual(scale: Vector3) {
-        this.scale.x *= scale.x;
-        this.scale.y *= scale.y;
-        this.scale.z *= scale.z;
-    }
-}
-
-/**
- * Utilities for math functions that do not exist in the TypeScript Math module. 
- */
-export const dbMath = {
-    /**
-     * Convert from degrees to radians
-     * 
-     * # Example
-     * @example
-     * ```ts
-     * console.log(dbMath.degreesToRadians(180)) // expect Math.PI
-     * ```
-     * 
-     * @param deg - The angle in degrees
-     * @returns The angle in radians
-     */
-    degreesToRadians:(deg: number):number => {
-        return deg * (Math.PI / 180.0);
-    },
-
-    /**
-     * Convert from radians to degrees
-     * 
-     * # Example
-     * @example
-     * ```ts
-     * console.log(dbMath.radiansToDegrees(Math.PI)) // expect 180
-     * ```
-     * 
-     * @param rad - The angle in radians
-     * @returns The angle in degrees
-     */
-    radiansToDegrees:(rad: number):number => {
-        return (180 * rad) / Math.PI;
-    },
-
-    /**
-     * Constrains a number to lie within a specified range. If value is less than min, returns min. 
-     * If value is greater than max, returns max. Otherwise, returns value.
-     * 
-     * @example
-     * ```ts
-     * dropbear.dbMath.clamp(5, 0, 10)     // → 5
-     * dropbear.dbMath.clamp(-3, 0, 10)    // → 0
-     * dropbear.dbMath.clamp(15, 0, 10)    // → 10
-     * ```
-     * 
-     * @param value - The input value to clamp
-     * @param min - The lower bound of the range
-     * @param max - The upper bound of the range
-     * @returns - The clamped value
-     */
-    clamp: (value: number, min: number, max: number): number => {
-        return Math.min(Math.max(value, min), max);
-    }
-}
-
-/**
- * A Vector of 3 components: an X, Y and Z. 
- */
-export class Vector3 {
-    /**
-     * The X value
-     */
-    public x: number;
-
-    /**
-     * The Y value
-     */
-    public y: number;
-    /**
-     * The Z value
-     */
-    public z: number;
-
-    public constructor(x: number, y: number, z: number) {
-        this.x = x;
-        this.y = y;
-        this.z = z;
-    }
-
-    /**
-     * Converts the {@link Vector3} class to a primitive number array
-     * 
-     * # Example
-     * @example
-     * ```ts
-     * let vec = new Vector3(1.0, 1.5, 2.0);
-     * console.log(vec.as_array()) // expect [1.0, 1.5, 2.0]
-     * ```
-     * 
-     * @returns A number array representing the x, y, z values
-     */
-    public as_array(): [number, number, number] {
-        return [this.x, this.y, this.z];
-    }
-
-    /**
-     * An alternative static constructor to create a {@link Vector3} with all values 
-     * set to 0.0
-     * 
-     * # Example
-     * ```ts
-     * let vec = Vector3.zero();
-     * console.log(vec); // expect [0.0, 0.0, 0.0]
-     * ```
-     * 
-     * @returns A new Vector3 instance with all values set to 0.0
-     */
-    public static zero(): Vector3 {
-        return new Vector3(0.0, 0.0, 0.0);
-    }
-
-    /**
-     * An alternative static constructor to create a {@link Vector3} with all values 
-     * set to 1.0
-     * 
-     * # Example
-     * ```ts
-     * let vec = Vector3.one();
-     * console.log(vec); // expect [1.0, 1.0, 1.0]
-     * ```
-     * 
-     * @returns A new Vector3 instance with all values set to 1.0
-     */
-    public static one(): Vector3 {
-        return new Vector3(1.0, 1.0, 1.0);
-    }
-
-    /**
-     * An alternative static constructor that creates a Vector3 from a number array.
-     * 
-     * # Example
-     * @example
-     * ```ts
-     * let arr = [1.0, 1.5, 2.0];
-     * let vec = Vector3.fromArray(arr);
-     * console.log(vec.to_array() === arr) // expect to print 'true'
-     * ```
-     *  
-     * @param arr - The number array to convert
-     * @returns - A new Vector3 instance
-     */
-    public static fromArray(arr: [number, number, number]): Vector3 {
-        return new Vector3(arr[0], arr[1], arr[2]);
-    }
-
-    /**
-     * Add another Vector3 to this vector and return the result as a new Vector3.
-     *
-     * @param rhs - Right-hand side vector to add.
-     * @returns A new Vector3 equal to (this + rhs).
-     *
-     * @example
-     * const a = new Vector3(1, 2, 3);
-     * const b = new Vector3(4, 5, 6);
-     * const c = a.add(b); // Vector3(5,7,9)
-     */
-    public add(rhs: Vector3): Vector3 {
-        return new Vector3(this.x + rhs.x, this.y + rhs.y, this.z + rhs.z);
-    }
-
-    /**
-     * Subtract another Vector3 from this vector and return the result as a new Vector3.
-     *
-     * @param rhs - Right-hand side vector to subtract.
-     * @returns A new Vector3 equal to (this - rhs).
-     *
-     * @example
-     * const a = new Vector3(5, 7, 9);
-     * const b = new Vector3(1, 2, 3);
-     * const c = a.subtract(b); // Vector3(4,5,6)
-     */
-    public subtract(rhs: Vector3): Vector3 {
-        return new Vector3(this.x - rhs.x, this.y - rhs.y, this.z - rhs.z);
-    }
-
-    /**
-     * Multiply this vector by a scalar and return the result as a new Vector3.
-     *
-     * @param rhs - Scalar multiplier.
-     * @returns A new Vector3 scaled by rhs.
-     *
-     * @example
-     * const v = new Vector3(1, 2, 3);
-     * const s = v.multiply(2); // Vector3(2,4,6)
-     */
-    public multiply(rhs: number): Vector3 {
-        return new Vector3(this.x * rhs, this.y * rhs, this.z * rhs);
-    }
-
-    /**
-     * Compute the Euclidean length (magnitude) of this vector.
-     *
-     * @returns The length sqrt(x*x + y*y + z*z).
-     *
-     * @example
-     * const v = new Vector3(1, 2, 2);
-     * console.log(v.length()); // 3
-     */
-    public length(): number {
-        return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
-    }
-
-    /**
-     * Return a new Vector3 representing the normalized (unit) direction of this vector.
-     * If the vector has zero length, returns Vector3.zero().
-     *
-     * @returns A normalized Vector3 or Vector3.zero() when length is 0.
-     *
-     * @example
-     * const v = new Vector3(0, 3, 4);
-     * const n = v.normalize(); // Vector3(0, 0.6, 0.8)
-     */
-    public normalize(): Vector3 {
-        const len = this.length();
-        if (len === 0) return Vector3.zero();
-        return new Vector3(this.x / len, this.y / len, this.z / len);
-    }
-}
-
-/**
- * Quaternion representing a rotation in 3D space.
- *
- * Components are stored as (x, y, z, w) where w is the scalar part.
- * Useful helpers are provided to construct quaternions from Euler angles,
- * axis/angle, multiply them, and convert to/from arrays.
- */
-export class Quaternion {
-    public x: number;
-    public y: number;
-    public z: number;
-    public w: number;
-
-    /**
-     * Create a new quaternion.
-     *
-     * @param x - X component (default 0)
-     * @param y - Y component (default 0)
-     * @param z - Z component (default 0)
-     * @param w - W (scalar) component (default 1)
-     *
-     * @example
-     * const q = new Quaternion(); // identity
-     * const q2 = new Quaternion(0.1, 0.2, 0.3, 0.9);
-     */
-    public constructor(x: number = 0, y: number = 0, z: number = 0, w: number = 1) {
-        this.x = x;
-        this.y = y;
-        this.z = z;
-        this.w = w;
-    }
-
-    /**
-     * Construct a quaternion from Euler angles (radians).
-     *
-     * The parameters represent rotations about the X, Y and Z axes respectively.
-     * Angles must be provided in radians.
-     *
-     * @param x - rotation about the X axis in radians
-     * @param y - rotation about the Y axis in radians
-     * @param z - rotation about the Z axis in radians
-     * @returns A new Quaternion representing the composite rotation
-     *
-     * @example
-     * const q = Quaternion.fromEuler(Math.PI/2, 0, 0); // 90° around X
-     */
-    public static fromEuler(x: number, y: number, z: number): Quaternion {
-        // Convert euler angles to quaternion
-        const cx = Math.cos(x * 0.5);
-        const sx = Math.sin(x * 0.5);
-        const cy = Math.cos(y * 0.5);
-        const sy = Math.sin(y * 0.5);
-        const cz = Math.cos(z * 0.5);
-        const sz = Math.sin(z * 0.5);
-
-        return new Quaternion(
-            sx * cy * cz - cx * sy * sz,
-            cx * sy * cz + sx * cy * sz,
-            cx * cy * sz - sx * sy * cz,
-            cx * cy * cz + sx * sy * sz
-        );
-    }
-
-    /**
-     * Construct a quaternion representing a rotation around an axis.
-     *
-     * The axis will be normalized internally. Angle is in radians.
-     *
-     * @param axis - Rotation axis as a Vector3
-     * @param angle - Rotation angle in radians
-     * @returns A new Quaternion representing the axis-angle rotation
-     *
-     * @example
-     * const q = Quaternion.fromAxisAngle(new Vector3(0,1,0), Math.PI); // 180° around Y
-     */
-    public static fromAxisAngle(axis: Vector3, angle: number): Quaternion {
-        const halfAngle = angle * 0.5;
-        const sin = Math.sin(halfAngle);
-        const normalizedAxis = axis.normalize();
-        
-        return new Quaternion(
-            normalizedAxis.x * sin,
-            normalizedAxis.y * sin,
-            normalizedAxis.z * sin,
-            Math.cos(halfAngle)
-        );
-    }
-
-    /**
-     * Multiply two quaternions.
-     *
-     * The result corresponds to the composition a * b (apply b, then a) using
-     * the multiplication implemented here.
-     *
-     * @param a - Left quaternion
-     * @param b - Right quaternion
-     * @returns The product quaternion
-     *
-     * @example
-     * const r = Quaternion.multiply(q1, q2);
-     */
-    public static multiply(a: Quaternion, b: Quaternion): Quaternion {
-        return new Quaternion(
-            a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
-            a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
-            a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,
-            a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z
-        );
-    }
-
-    /**
-     * Create a quaternion from a numeric array [x, y, z, w].
-     *
-     * @param arr - Array containing quaternion components in order [x, y, z, w]
-     * @returns A new Quaternion with components taken from the array
-     *
-     * @example
-     * const q = Quaternion.fromArray([0, 0, 0, 1]);
-     */
-    public static fromArray(arr: [number, number, number, number]): Quaternion {
-        return new Quaternion(arr[0], arr[1], arr[2], arr[3]);
-    }
-
-    /**
-     * Return the identity quaternion (no rotation).
-     *
-     * @returns Quaternion equal to (0, 0, 0, 1)
-     *
-     * @example
-     * const id = Quaternion.identity();
-     */
-    public static identity(): Quaternion {
-        return new Quaternion(0.0, 0.0, 0.0, 1.0);
-    }
-
-    /**
-     * Convert this quaternion to a numeric array [x, y, z, w].
-     *
-     * @returns An array containing the quaternion components
-     *
-     * @example
-     * const arr = q.as_array(); // [x, y, z, w]
-     */
-    public as_array(): [number, number, number, number] {
-        return [this.x, this.y, this.z, this.w];
-    }
-}
-
-/**
- * Values of the Key codes. 
- */
-export const Keys = {
-    KeyA: "KeyA", KeyB: "KeyB", KeyC: "KeyC", KeyD: "KeyD", KeyE: "KeyE", KeyF: "KeyF", 
-    KeyG: "KeyG", KeyH: "KeyH", KeyI: "KeyI", KeyJ: "KeyJ", KeyK: "KeyK", KeyL: "KeyL", 
-    KeyM: "KeyM", KeyN: "KeyN", KeyO: "KeyO", KeyP: "KeyP", KeyQ: "KeyQ", KeyR: "KeyR", 
-    KeyS: "KeyS", KeyT: "KeyT", KeyU: "KeyU", KeyV: "KeyV", KeyW: "KeyW", KeyX: "KeyX", 
-    KeyY: "KeyY", KeyZ: "KeyZ",
-    Digit0: "Digit0", Digit1: "Digit1", Digit2: "Digit2", Digit3: "Digit3", Digit4: "Digit4",
-    Digit5: "Digit5", Digit6: "Digit6", Digit7: "Digit7", Digit8: "Digit8", Digit9: "Digit9",
-    Space: "Space",
-    ShiftLeft: "ShiftLeft", ShiftRight: "ShiftRight",
-    ControlLeft: "ControlLeft", ControlRight: "ControlRight",
-    AltLeft: "AltLeft", AltRight: "AltRight",
-    Escape: "Escape", Enter: "Enter", Tab: "Tab",
-    ArrowUp: "ArrowUp", ArrowDown: "ArrowDown", ArrowLeft: "ArrowLeft", ArrowRight: "ArrowRight",
-    F1: "F1", F2: "F2", F3: "F3", F4: "F4", F5: "F5", F6: "F6",
-    F7: "F7", F8: "F8", F9: "F9", F10: "F10", F11: "F11", F12: "F12",
-} as const;
-
-export type KeyCode = typeof Keys[keyof typeof Keys];
-
-/**
- * The properties of the entity. 
- * From the eucalyptus editor, you are able to make custom 
- * properties such as 'speed' or 'health'. This class allows 
- * you to create and edit new value
- * during the runtime of the script. 
- * 
- * Usage:
- * ```ts
- * props.setNumber("hp", 100);
- * const hp = props.getNumber("hp"); // 100
- * ```
- */
-export class EntityProperties {
-    private data: Record<string, any>;
-
-    /**
-     * Create an EntityProperties wrapper.
-     *
-     * @param data - Optional initial properties object. A shallow reference is kept.
-     */
-    public constructor(data?: Record<string, any>) {
-        this.data = data || {};
-    }
-
-    /**
-     * Get the raw underlying properties object.
-     *
-     * Use this to serialize properties back to the host or perform bulk operations.
-     *
-     * @returns The raw Record<string, any> used to store properties.
-     *
-     * @example
-     * const raw = props.getRawProperties();
-     */
-    public getRawProperties(): Record<string, any> {
-        return this.data;
-    }
-
-    /**
-     * Set a string property.
-     *
-     * @param key - Property key.
-     * @param value - String value to set.
-     *
-     * @example
-     * props.setString("tag", "friendly");
-     */
-    public setString(key: string, value: string): void {
-        this.data[key] = value;
-    }
-
-    /**
-     * Set a numeric property.
-     *
-     * @param key - Property key.
-     * @param value - Number value to set.
-     *
-     * @example
-     * props.setNumber("speed", 4.2);
-     */
-    public setNumber(key: string, value: number): void {
-        this.data[key] = value;
-    }
-
-    /**
-     * Set a boolean property.
-     *
-     * @param key - Property key.
-     * @param value - Boolean value to set.
-     *
-     * @example
-     * props.setBool("isActive", true);
-     */
-    public setBool(key: string, value: boolean): void {
-        this.data[key] = value;
-    }
-
-    /**
-     * Get a string property.
-     *
-     * If the property is missing or falsy, an empty string ("") is returned.
-     *
-     * @param key - Property key.
-     * @returns The string value or "" when missing.
-     *
-     * @example
-     * const name = props.getString("name");
-     */
-    public getString(key: string): string {
-        return this.data[key] || "";
-    }
-
-    /**
-     * Get a numeric property.
-     *
-     * If the property is missing or falsy, 0 is returned.
-     *
-     * @param key - Property key.
-     * @returns The number value or 0 when missing.
-     *
-     * @example
-     * const speed = props.getNumber("speed");
-     */
-    public getNumber(key: string): number {
-        return this.data[key] || 0;
-    }
-
-    /**
-     * Get a boolean property.
-     *
-     * If the property is missing or falsy, false is returned.
-     *
-     * @param key - Property key.
-     * @returns The boolean value or false when missing.
-     *
-     * @example
-     * const active = props.getBool("isActive");
-     */
-    public getBool(key: string): boolean {
-        return this.data[key] || false;
-    }
-
-    /**
-     * Checks if the entity has a specific property as per a key. 
-     * 
-     * If the property exists, it will return true. If not, it will return false
-     * @param key - Property key.
-     * @returns - True is the value exists, false if not
-     * 
-     * @example
-     * ```ts
-     * if props.hasProperty("speed") {
-     *      let speed = props.getNumber("speed");
-     *      speed = speed+10;
-     * } else {
-     *      console.log("No value as speed exists");
-     * }
-     * ```
-     */
-    public hasProperty(key: string): boolean {
-        return key in this.data;
-    }
-}
-
-export class Entity {
-    public label: string;
-    public transform: Transform;
-    public properties: EntityProperties;
-
-    /**
-     * Creates a new instance of entity
-     * @param entityData - The entty data, typically parsed as an argument in the load or update functions
-     */
-    constructor(label: string, properties: EntityData, transform: TransformData) {
-        this.label = label;
-        this.transform = createTransformFromData(transform);
-        this.properties = new EntityProperties(properties);
-    }
-
-    /**
-     * Moves the player forward on the Z axis
-     * @param distance - The distance (as a number) it moves forward by
-     */
-    moveForward(distance: number): void {
-        const movement = new Vector3(0, 0, -distance);
-        this.transform.translate(movement);
-    }
-
-    /**
-     * Moves the player back on the Z axis
-     * @param distance - The distance (as a number) it moves back by
-     */
-    moveBack(distance: number): void {
-        const movement = new Vector3(0, 0, distance);
-        this.transform.translate(movement);
-    }
-
-    /**
-     * Moves the player left on the X axis
-     * @param distance - The distance (as a number) it moves left by
-     */
-    moveLeft(distance: number): void {
-        const movement = new Vector3(-distance, 0, 0);
-        this.transform.translate(movement);
-    }
-
-    /**
-     * Moves the player right on the X axis
-     * @param distance - The distance (as a number) it moves right by
-     */
-    moveRight(distance: number): void {
-        const movement = new Vector3(distance, 0, 0);
-        this.transform.translate(movement);
-    }
-
-    /**
-     * Moves the player up on the Y axis
-     * @param distance - The distance (as a number) it moves up by
-     */
-    moveUp(distance: number): void {
-        const movement = new Vector3(0, distance, 0);
-        this.transform.translate(movement);
-    }
-
-    /**
-     * Moves the player down on the Y axis
-     * @param distance - The distance (as a number) it moves down by
-     */
-    moveDown(distance: number): void {
-        const movement = new Vector3(0, -distance, 0);
-        this.transform.translate(movement);
-    }
-}
-
-
-/**
- * Helper function that creates a new {@link Transform} from 
- * a {@link TransformData}
- * @param data - The raw transformable ({@link TransformData})data
- * @returns - An instance of a {@link Transform}
- */
-function createTransformFromData(data: TransformData): Transform {
-    const position = Vector3.fromArray(data.position);
-    const rotation = Quaternion.fromArray(data.rotation);
-    const scale = Vector3.fromArray(data.scale);
-    return new Transform(position, rotation, scale);
-}
-
-/**
- * Camera class for controlling and manipulating cameras in the scene.
- * Provides functionality for camera movement, switching, and property manipulation.
- */
-export class Camera {
-    public label: string;
-
-    public eye: Vector3;
-    public target: Vector3;
-    public up: Vector3;
-    public aspect: number;
-    public fov: number;
-    public near: number;
-    public far: number;
-    public yaw: number;
-    public pitch: number;
-    public speed: number;
-    public sensitivity: number;
-    public camera_type: string;
-
-
-    /**
-     * Create a new Camera instance.
-     * 
-     * @param data - Optional camera data to initialize from
-     */
-    constructor(label: string, data: CameraData) {
-        this.eye = Vector3.fromArray(data.eye);
-        this.target = Vector3.fromArray(data.target);
-        this.up = Vector3.fromArray(data.up);
-        this.aspect = data.aspect;
-        this.fov = data.fov;
-        this.near = data.near;
-        this.far = data.far;
-        this.yaw = data.yaw;
-        this.pitch = data.pitch;
-        this.speed = data.speed;
-        this.sensitivity = data.sensitivity;
-        this.camera_type = data.camera_type;
-        this.label = label;
-    }
-
-    /**
-     * Track mouse movement for camera look controls.
-     * 
-     * @param delta - Mouse delta X and Y
-     * 
-     * @example
-     * ```ts
-     * let delta = entity.getMouseDelta();
-     * camera.track
-     * ```
-     */
-    trackMouseDelta(delta: [number, number]): void {
-        this.yaw += delta[0] * this.sensitivity;
-        this.pitch += delta[1] * this.sensitivity;
-        
-        this.pitch = dbMath.clamp(this.pitch, dbMath.degreesToRadians(-89.0), dbMath.degreesToRadians(89.0));
-
-        
-        let direction = new Vector3(
-            Math.cos(this.yaw) * Math.cos(this.pitch),
-            Math.sin(this.pitch),
-            Math.sin(this.yaw) * Math.cos(this.pitch),
-        );
-        this.target = this.eye.add(direction);
-    }
-}
-
-export class Light {
-
-}
-
-/**
- * A raw format for storing the transform data, typically used as a
- * FFI by the engine
- */
-export interface TransformData {
-    position: [number, number, number];
-    rotation: [number, number, number, number]; // quaternion [x, y, z, w]
-    scale: [number, number, number];
-}
-
-/**
- * A raw format for storing the custom entity properties from the engines
- * raw FFI
- */
-export interface EntityData {
-    custom_properties: Record<string, any>;
-}
-
-/**
- * A raw format for storing the input data
- */
-export interface InputData {
-    mouse_pos: [number, number];
-    pressed_keys: string[];
-    mouse_delta: [number, number] | null;
-    is_cursor_locked: boolean;
-}
-
-/**
- * A raw format for storing the camera data
- */
-export interface CameraData {
-    eye: [number, number, number];
-    target: [number, number, number];
-    up: [number, number, number];
-    aspect: number;
-    fov: number;
-    near: number;
-    far: number;
-    yaw: number;
-    pitch: number;
-    speed: number;
-    sensitivity: number;
-    camera_type: string;
-}
-
-/**
- * A raw format for storing the light data
- */
-export interface LightData {
-
-}
-
-/**
- * A raw format for storing the scene data
- */
-export interface RawSceneData {
-    entities: [{
-        label: string,
-        properties: EntityData, 
-        transform: TransformData
-    }];
-    cameras: [{
-        label: string,
-        data: CameraData
-    }];
-    lights: [{
-        label: string,
-        data: LightData
-    }];
-    input: InputData;
-}
-
-/**
- * A wrapper class aimed to aid with input data
- */
-export class Input {
-    public inputData?: InputData
-
-    // constructor(data: InputData) {
-    //     this.inputData = data;
-    // }
-
-    // empty because global variable
-    constructor() {
-
-    }
-
-    /**
-     * Checks if a key is pressed
-     * @param key - The specific keycode
-     * @returns - True is pressed, false if not
-     * 
-     * @example
-     * ```ts
-     * if entity.isKeyPressed(Keys::KeyW) {
-     *      console.log("The W key is pressed");
-     * }
-     * ```
-     */
-    isKeyPressed(key: KeyCode): boolean {
-        if (!this.inputData) return false;
-        return this.inputData.pressed_keys.indexOf(key) !== -1
-    }
-
-    /**
-     * Fetches the mouse position
-     * @returns - The x,y position of the mouse
-     */
-    getMousePosition(): [number, number] {
-        return this.inputData?.mouse_pos || [0, 0];
-    }
-
-    /**
-     * Fetches the change in the mouse position from the center (as it gets reset each frame)
-     * @returns - The dx,dy position of the mouse
-     */
-    getMouseDelta(): [number, number] {
-        return this.inputData?.mouse_delta || [0.0, 0.0];
-    }
-}
-
-/**
- * The class containing all the different entities in this scene. 
- */
-export class Scene {
-    // ensure none of these are public
-    current_entity?: string;
-    entities?: Entity[];
-    cameras?: Camera[];
-    lights?: Light[];
-
-    // Sets everything to default
-    constructor() {
-        this.cameras = [];
-        this.entities = [];
-        this.lights = [];
-    }
-
-    /**
-     * Returns a **reference** to the camera in the scene
-     * @param label - The label of the camera as set by you from the editor
-     */
-    public getCamera(label: string): Camera | undefined {
-        return this.cameras?.find(c => c.label === label);
-    }
-
-    /**
-     * Returns a **reference** to the light in the scene
-     * @param label - The label of the light as set by you from the editor
-     */
-    public getLight(label: string): Light | undefined {
-        return this.lights?.find(l => (l as any).label === label);
-    }
-
-    /**
-     * Fetches an entity as per its label. Returns the actual Entity instance (mutable reference).
-     * @param label - The label of the entity
-     */
-    public getEntity(label: string): Entity | undefined {
-        return this.entities?.find(e => e.label === label);
-    }
-
-    /**
-     * Fetches the current entity this script is attached to (returns mutable reference).
-     */
-    public getCurrentEntity(): Entity | undefined {
-        if (!this.current_entity) {
-            console.error("Unable to get entity: Have you added dropbear.start(s) yet?");
-            return undefined;
-        }
-        const ent = this.getEntity(this.current_entity);
-        if (!ent) {
-            console.error(`Unable to get entity: no entity with label "${this.current_entity}" found`);
-        }
-        return ent;
-    }
-}
-
-/**
- * Starts the specific function by filling the scene data. 
- * @param data 
- */
-export function start(data: RawSceneData) {
-    data.cameras.forEach(camera => {
-        scene.cameras?.push(new Camera(camera.label, camera.data))
-    });
-    data.entities.forEach(entity => {
-        scene.entities?.push(new Entity(entity.label, entity.properties, entity.transform))
-    });
-    data.lights.forEach(light => {
-        scene.lights?.push(light)
-    });
-    input.inputData = data.input;
-}
-
-/**
- * Ends the scripting function by returning a Partial RawSceneData for the
- * rust client to take. 
- */
-export function end(): Partial<RawSceneData> {
-    const out: Partial<RawSceneData> = {};
-
-    if (scene.entities && scene.entities.length) {
-        out.entities = scene.entities.map(e => {
-            return {
-                label: e.label,
-                properties: {
-                    custom_properties: e.properties.getRawProperties()
-                } as EntityData,
-                transform: {
-                    position: e.transform.position.as_array(),
-                    rotation: e.transform.rotation.as_array(),
-                    scale: e.transform.scale.as_array()
-                } as TransformData
-            };
-        }) as any;
-    }
-
-    if (scene.cameras && scene.cameras.length) {
-        out.cameras = scene.cameras.map(c => {
-            return {
-                label: c.label,
-                data: {
-                    eye: c.eye.as_array(),
-                    target: c.target.as_array(),
-                    up: c.up.as_array(),
-                    aspect: c.aspect,
-                    fov: c.fov,
-                    near: c.near,
-                    far: c.far,
-                    yaw: c.yaw,
-                    pitch: c.pitch,
-                    speed: c.speed,
-                    sensitivity: c.sensitivity,
-                    camera_type: c.camera_type
-                } as CameraData
-            };
-        }) as any;
-    }
-
-    if (scene.lights && scene.lights.length) {
-        out.lights = scene.lights.map(l => {
-            return {
-                label: (l as any).label || "",
-                data: {} as LightData
-            };
-        }) as any;
-    }
-
-    if (input.inputData) {
-        out.input = input.inputData;
-    }
-
-    return out;
-}
-
-// global variables
-/**
- * A global variable that contains all the information about the scene. 
- * 
- * To use the variable, you need to run {@link start()} at the start of 
- * the function and return {@link end()} at the end to send to the engine.  
- * 
- * @example
- * ```ts
- * export function onUpdate(s, dt: number) {
- *    dropbear.start(s);
- *    console.log("I'm being updated!");
- *    return dropbear.end();
- * }
- * ```
- */
-export const scene = new Scene();
-/**
- * A global variable that contains all the information about the inputs
- * such as the keyboard and the mouse.
- * 
- * To use the variable, you need to run {@link start()} at the start of 
- * the function and return {@link end()} at the end to send to the engine.  
- * 
- * @example
- * ```ts
- * export function onUpdate(s, dt: number) {
- *    dropbear.start(s);
- *    console.log("I'm being updated!");
- *    return dropbear.end();
- * }
- */
-export const input = new Input();
\ No newline at end of file
diff --git a/eucalyptus/src/editor/component.rs b/eucalyptus/src/editor/component.rs
deleted file mode 100644
index 5dcb1b4..0000000
--- a/eucalyptus/src/editor/component.rs
+++ /dev/null
@@ -1,665 +0,0 @@
-//! This module should describe the different components that are editable in the resource inspector.
-
-use std::time::Instant;
-use egui::{CollapsingHeader, ComboBox, Ui};
-use glam::Vec3;
-use hecs::Entity;
-use dropbear_engine::attenuation::ATTENUATION_PRESETS;
-use dropbear_engine::entity::{AdoptedEntity, Transform};
-use dropbear_engine::lighting::{Light, LightComponent, LightType};
-use crate::editor::{EntityType, Signal, StaticallyKept, UndoableAction};
-use crate::scripting::{ScriptAction, TEMPLATE_SCRIPT};
-use crate::states::ScriptComponent;
-
-pub trait Component {
-    fn inspect(&mut self, entity: &mut hecs::Entity, cfg: &mut StaticallyKept, ui: &mut Ui, undo_stack: &mut Vec<UndoableAction>, signal: &mut Signal, label: &mut String);
-}
-
-impl Component for Transform {
-    fn inspect(&mut self, entity: &mut Entity, cfg: &mut StaticallyKept, ui: &mut Ui, undo_stack: &mut Vec<UndoableAction>, _signal: &mut Signal, _label: &mut String) {
-        ui.group(|ui| {
-            CollapsingHeader::new("Transform").default_open(true).show(
-                ui,
-                |ui| {
-                    ui.horizontal(|ui| {
-                        ui.label("Position:");
-                    });
-
-                    ui.horizontal(|ui| {
-                        ui.label("X:");
-                        let response = ui.add(
-                            egui::DragValue::new(&mut self.position.x)
-                                .speed(0.1)
-                                .fixed_decimals(3),
-                        );
-
-                        if response.drag_started() {
-                            cfg.transform_old_entity = Some(entity.clone());
-                            cfg.transform_original_transform = Some((*self).clone());
-                            cfg.transform_in_progress = true;
-                        }
-
-                        if response.drag_stopped() && cfg.transform_in_progress {
-                            if let Some(ent) = cfg.transform_old_entity.take() {
-                                if let Some(orig) = cfg.transform_original_transform.take() {
-                                    UndoableAction::push_to_undo(
-                                        undo_stack,
-                                        UndoableAction::Transform(ent, orig),
-                                    );
-                                    log::debug!("Pushed X transform change to undo stack");
-                                }
-                            }
-                            cfg.transform_in_progress = false;
-                        }
-                    });
-                    ui.horizontal(|ui| {
-                        ui.label("Y:");
-                        let response = ui.add(
-                            egui::DragValue::new(&mut self.position.y)
-                                .speed(0.1)
-                                .fixed_decimals(3),
-                        );
-
-                        if response.drag_started() {
-                            cfg.transform_old_entity = Some(entity.clone());
-                            cfg.transform_original_transform = Some((*self).clone());
-                            cfg.transform_in_progress = true;
-                        }
-
-                        if response.drag_stopped() && cfg.transform_in_progress {
-                            if let Some(ent) = cfg.transform_old_entity.take() {
-                                if let Some(orig) = cfg.transform_original_transform.take() {
-                                    UndoableAction::push_to_undo(
-                                        undo_stack,
-                                        UndoableAction::Transform(ent, orig),
-                                    );
-                                    log::debug!("Pushed Y transform change to undo stack");
-                                }
-                            }
-                            cfg.transform_in_progress = false;
-                        }
-                    });
-
-                    ui.horizontal(|ui| {
-                        ui.label("Z:");
-                        let response = ui.add(
-                            egui::DragValue::new(&mut self.position.z)
-                                .speed(0.1)
-                                .fixed_decimals(3),
-                        );
-
-                        if response.drag_started() {
-                            cfg.transform_old_entity = Some(entity.clone());
-                            cfg.transform_original_transform = Some((*self).clone());
-                            cfg.transform_in_progress = true;
-                        }
-
-                        if response.drag_stopped() && cfg.transform_in_progress {
-                            if let Some(ent) = cfg.transform_old_entity.take() {
-                                if let Some(orig) = cfg.transform_original_transform.take() {
-                                    UndoableAction::push_to_undo(
-                                        undo_stack,
-                                        UndoableAction::Transform(ent, orig),
-                                    );
-                                    log::debug!("Pushed Z transform change to undo stack");
-                                }
-                            }
-                            cfg.transform_in_progress = false;
-                        }
-                    });
-                    if ui.button("Reset Position").clicked() {
-                        self.position = glam::DVec3::ZERO;
-                    }
-
-                    ui.add_space(5.0);
-
-                    ui.horizontal(|ui| {
-                        ui.label("Rotation:");
-                    });
-
-                    let mut rotation_changed = false;
-                    let (mut x_rot, mut y_rot, mut z_rot) =
-                        self.rotation.to_euler(glam::EulerRot::XYZ);
-
-                    ui.horizontal(|ui| {
-                        ui.label("X:");
-                        let response = ui.add(
-                            egui::Slider::new(
-                                &mut x_rot,
-                                -std::f64::consts::PI
-                                    ..=std::f64::consts::PI,
-                            )
-                                .step_by(0.01)
-                                .custom_formatter(|n, _| {
-                                    format!("{:.1}°", n.to_degrees())
-                                })
-                                .custom_parser(|s| {
-                                    s.trim_end_matches('°')
-                                        .parse::<f64>()
-                                        .ok()
-                                        .map(|v| v.to_radians())
-                                }),
-                        );
-
-                        if response.drag_started() {
-                            cfg.transform_old_entity = Some(entity.clone());
-                            cfg.transform_original_transform = Some((*self).clone());
-                            cfg.transform_in_progress = true;
-                        }
-
-                        if response.changed() {
-                            rotation_changed = true;
-                        }
-
-                        if response.drag_stopped() && cfg.transform_in_progress {
-                            if let Some(ent) = cfg.transform_old_entity.take() {
-                                if let Some(orig) = cfg.transform_original_transform.take() {
-                                    UndoableAction::push_to_undo(
-                                        undo_stack,
-                                        UndoableAction::Transform(ent, orig),
-                                    );
-                                    log::debug!("Pushed X rotation change to undo stack");
-                                }
-                            }
-                            cfg.transform_in_progress = false;
-                        }
-                    });
-
-                    ui.horizontal(|ui| {
-                        ui.label("Y:");
-                        let response = ui.add(
-                            egui::Slider::new(
-                                &mut y_rot,
-                                -std::f64::consts::PI
-                                    ..=std::f64::consts::PI,
-                            )
-                                .step_by(0.01)
-                                .custom_formatter(|n, _| {
-                                    format!("{:.1}°", n.to_degrees())
-                                })
-                                .custom_parser(|s| {
-                                    s.trim_end_matches('°')
-                                        .parse::<f64>()
-                                        .ok()
-                                        .map(|v| v.to_radians())
-                                }),
-                        );
-
-                        if response.drag_started() {
-                            cfg.transform_old_entity = Some(entity.clone());
-                            cfg.transform_original_transform = Some((*self).clone());
-                            cfg.transform_in_progress = true;
-                        }
-
-                        if response.changed() {
-                            rotation_changed = true;
-                        }
-
-                        if response.drag_stopped() && cfg.transform_in_progress {
-                            if let Some(ent) = cfg.transform_old_entity.take() {
-                                if let Some(orig) = cfg.transform_original_transform.take() {
-                                    UndoableAction::push_to_undo(
-                                        undo_stack,
-                                        UndoableAction::Transform(ent, orig),
-                                    );
-                                    log::debug!("Pushed Y rotation change to undo stack");
-                                }
-                            }
-                            cfg.transform_in_progress = false;
-                        }
-                    });
-
-                    ui.horizontal(|ui| {
-                        ui.label("Z:");
-                        let response = ui.add(
-                            egui::Slider::new(
-                                &mut z_rot,
-                                -std::f64::consts::PI
-                                    ..=std::f64::consts::PI,
-                            )
-                                .step_by(0.01)
-                                .custom_formatter(|n, _| {
-                                    format!("{:.1}°", n.to_degrees())
-                                })
-                                .custom_parser(|s| {
-                                    s.trim_end_matches('°')
-                                        .parse::<f64>()
-                                        .ok()
-                                        .map(|v| v.to_radians())
-                                }),
-                        );
-
-                        if response.drag_started() {
-                            cfg.transform_old_entity = Some(entity.clone());
-                            cfg.transform_original_transform = Some((*self).clone());
-                            cfg.transform_in_progress = true;
-                        }
-
-                        if response.changed() {
-                            rotation_changed = true;
-                        }
-
-                        if response.drag_stopped() && cfg.transform_in_progress {
-                            if let Some(ent) = cfg.transform_old_entity.take() {
-                                if let Some(orig) = cfg.transform_original_transform.take() {
-                                    UndoableAction::push_to_undo(
-                                        undo_stack,
-                                        UndoableAction::Transform(ent, orig),
-                                    );
-                                    log::debug!("Pushed Z rotation change to undo stack");
-                                }
-                            }
-                            cfg.transform_in_progress = false;
-                        }
-                    });
-
-                    if rotation_changed {
-                        self.rotation = glam::DQuat::from_euler(
-                            glam::EulerRot::XYZ,
-                            x_rot,
-                            y_rot,
-                            z_rot,
-                        );
-                    }
-                    if ui.button("Reset Rotation").clicked() {
-                        self.rotation = glam::DQuat::IDENTITY;
-                    }
-                    ui.add_space(5.0);
-
-                    ui.horizontal(|ui| {
-                        ui.label("Scale:");
-                        ui.with_layout(
-                            egui::Layout::right_to_left(egui::Align::Center),
-                            |ui| {
-                                let lock_icon = if cfg.scale_locked {
-                                    "🔒"
-                                } else {
-                                    "🔓"
-                                };
-                                if ui
-                                    .button(lock_icon)
-                                    .on_hover_text("Lock uniform scaling")
-                                    .clicked()
-                                {
-                                    cfg.scale_locked = !cfg.scale_locked;
-                                }
-                            },
-                        );
-                    });
-
-                    let mut scale_changed = false;
-                    let mut new_scale = self.scale;
-
-                    ui.horizontal(|ui| {
-                        ui.label("X:");
-                        let response = ui.add(
-                            egui::DragValue::new(&mut new_scale.x)
-                                .speed(0.01)
-                                .fixed_decimals(3),
-                        );
-
-                        if response.drag_started() {
-                            cfg.transform_old_entity = Some(entity.clone());
-                            cfg.transform_original_transform = Some((*self).clone());
-                            cfg.transform_in_progress = true;
-                        }
-
-                        if response.changed() {
-                            scale_changed = true;
-                            if cfg.scale_locked {
-                                let scale_factor = new_scale.x / self.scale.x;
-                                new_scale.y = self.scale.y * scale_factor;
-                                new_scale.z = self.scale.z * scale_factor;
-                            }
-                        }
-
-                        if response.drag_stopped() && cfg.transform_in_progress {
-                            if let Some(ent) = cfg.transform_old_entity.take() {
-                                if let Some(orig) = cfg.transform_original_transform.take() {
-                                    UndoableAction::push_to_undo(
-                                        undo_stack,
-                                        UndoableAction::Transform(ent, orig),
-                                    );
-                                    log::debug!("Pushed X scale change to undo stack");
-                                }
-                            }
-                            cfg.transform_in_progress = false;
-                        }
-                    });
-
-                    ui.horizontal(|ui| {
-                        ui.label("Y:");
-                        let y_slider = egui::DragValue::new(&mut new_scale.y)
-                            .speed(0.01)
-                            .fixed_decimals(3);
-
-                        let response = ui.add_enabled(!cfg.scale_locked, y_slider);
-
-                        if response.drag_started() && !cfg.scale_locked {
-                            cfg.transform_old_entity = Some(entity.clone());
-                            cfg.transform_original_transform = Some((*self).clone());
-                            cfg.transform_in_progress = true;
-                        }
-
-                        if response.changed() {
-                            scale_changed = true;
-                        }
-
-                        if response.drag_stopped() && cfg.transform_in_progress {
-                            if let Some(ent) = cfg.transform_old_entity.take() {
-                                if let Some(orig) = cfg.transform_original_transform.take() {
-                                    UndoableAction::push_to_undo(
-                                        undo_stack,
-                                        UndoableAction::Transform(ent, orig),
-                                    );
-                                    log::debug!("Pushed Y scale change to undo stack");
-                                }
-                            }
-                            cfg.transform_in_progress = false;
-                        }
-                    });
-
-                    ui.horizontal(|ui| {
-                        ui.label("Z:");
-                        let z_slider = egui::DragValue::new(&mut new_scale.z)
-                            .speed(0.01)
-                            .fixed_decimals(3);
-
-                        let response = ui.add_enabled(!cfg.scale_locked, z_slider);
-
-                        if response.drag_started() && !cfg.scale_locked {
-                            cfg.transform_old_entity = Some(entity.clone());
-                            cfg.transform_original_transform = Some((*self).clone());
-                            cfg.transform_in_progress = true;
-                        }
-
-                        if response.changed() {
-                            scale_changed = true;
-                        }
-
-                        if response.drag_stopped() && cfg.transform_in_progress {
-                            if let Some(ent) = cfg.transform_old_entity.take() {
-                                if let Some(orig) = cfg.transform_original_transform.take() {
-                                    UndoableAction::push_to_undo(
-                                        undo_stack,
-                                        UndoableAction::Transform(ent, orig),
-                                    );
-                                    log::debug!("Pushed Z scale change to undo stack");
-                                }
-                            }
-                            cfg.transform_in_progress = false;
-                        }
-                    });
-
-                    if scale_changed {
-                        self.scale = new_scale;
-                    }
-                    if ui.button("Reset Scale").clicked() {
-                        self.scale = glam::DVec3::ONE;
-                    }
-                    ui.add_space(5.0);
-
-                    // maybe use? probs not :/
-                    // if pos_changed || rotation_changed || scale_changed {
-                    //     ui.colored_label(egui::Color32::YELLOW, "Transform modified");
-                    // }
-                },
-            );
-        });
-    }
-}
-
-impl Component for ScriptComponent {
-    fn inspect(&mut self, _entity: &mut Entity, _cfg: &mut StaticallyKept, ui: &mut Ui, _undo_stack: &mut Vec<UndoableAction>, signal: &mut Signal, label: &mut String) {
-        let script_loc = self.path.to_str().unwrap_or("").to_string();
-
-        ui.group(|ui| {
-            CollapsingHeader::new("Scripting")
-                .default_open(true)
-                .show(ui, |ui| {
-                    ui.horizontal(|ui| {
-                        if ui.button("Browse").clicked() {
-                            if let Some(script_file) = rfd::FileDialog::new()
-                                .add_filter("Typescript", &["ts"])
-                                .pick_file()
-                            {
-                                let script_name = script_file
-                                    .file_stem()
-                                    .unwrap_or_default()
-                                    .to_string_lossy()
-                                    .to_string();
-                                let lib_path = &script_file.clone().parent().unwrap().join("dropbear.ts");
-                                if let Err(_) = std::fs::read(lib_path) {
-                                    log::warn!("dropbear.ts library does not exist in project source directory, copying...");
-                                    if let Err(e) = std::fs::write(lib_path, include_str!("../dropbear.ts")) {
-                                        log::error!("Non-fatal error: Creating library file failed: {}", e);
-                                    } else {
-                                        log::info!("Wrote dropbear.ts library file!");
-                                    }
-                                };
-                                *signal = Signal::ScriptAction(ScriptAction::AttachScript {
-                                    script_path: script_file,
-                                    script_name,
-                                });
-                            }
-                        }
-
-                        if ui.button("New").clicked() {
-                            if let Some(script_path) = rfd::FileDialog::new()
-                                .add_filter("TypeScript", &["ts"])
-                                .set_file_name(format!("{}_script.ts", label))
-                                .save_file()
-                            {
-                                // check if dropbear module exists
-                                // todo: change this to an %APPDATA% file instead of to memory. 
-                                let lib_path = &script_path.clone().parent().unwrap().join("dropbear.ts");
-                                if let Err(_) = std::fs::read(lib_path) {
-                                    log::warn!("dropbear.ts library does not exist in project source directory, copying...");
-                                    if let Err(e) = std::fs::write(lib_path, include_str!("../dropbear.ts")) {
-                                        log::error!("Non-fatal error: Creating library file failed: {}", e);
-                                    } else {
-                                        log::info!("Wrote dropbear.ts library file!");
-                                    }
-                                };
-                                match std::fs::write(&script_path, TEMPLATE_SCRIPT) {
-                                    Ok(_) => {
-                                        let script_name = script_path
-                                            .file_stem()
-                                            .unwrap_or_default()
-                                            .to_string_lossy()
-                                            .to_string();
-                                        *signal = Signal::ScriptAction(ScriptAction::CreateAndAttachScript {
-                                            script_path,
-                                            script_name,
-                                        });
-                                    },
-                                    Err(e) => {
-                                        crate::warn!("Failed to create script file: {}", e);
-                                    },
-                                }
-                            }
-                        }
-                    });
-
-                    ui.separator();
-
-                    ui.horizontal_wrapped(|ui| {
-                        ui.label("Script Location:");
-                        ui.label(script_loc);
-                    });
-
-                    if ui.button("Remove").clicked() {
-                        *signal = Signal::ScriptAction(ScriptAction::RemoveScript);
-                    }
-                    ui.separator();
-                    ui.horizontal(|ui| {
-                        if ui.button("Edit Script").clicked() {
-                            *signal = Signal::ScriptAction(ScriptAction::EditScript);
-                        }
-                    });
-                });
-        });
-    }
-}
-
-impl Component for AdoptedEntity {
-    fn inspect(&mut self, entity: &mut Entity, cfg: &mut StaticallyKept, ui: &mut Ui, undo_stack: &mut Vec<UndoableAction>, _signal: &mut Signal, _label: &mut String) {
-        // label
-        ui.group(|ui| {
-            ui.horizontal(|ui| {
-                ui.label("Name: ");
-
-                let resp = ui.text_edit_singleline(self.label_mut());
-
-                if resp.changed() {
-                    if cfg.old_label_entity.is_none() {
-                        cfg.old_label_entity = Some(entity.clone());
-                        cfg.label_original = Some(self.label().clone());
-                    }
-                    cfg.label_last_edit = Some(Instant::now());
-                }
-
-                if resp.lost_focus() {
-                    if let Some(ent) = cfg.old_label_entity.take() {
-                        if ent == *entity {
-                            if let Some(orig) = cfg.label_original.take() {
-                                UndoableAction::push_to_undo(
-                                    undo_stack,
-                                    UndoableAction::Label(ent, orig, EntityType::Entity),
-                                );
-                                log::debug!("Pushed label change to undo stack (immediate)");
-                            }
-                        } else {
-                            cfg.label_original = None;
-                        }
-                    }
-                    cfg.label_last_edit = None;
-                }
-            })
-        });
-    }
-}
-
-impl Component for Light {
-    fn inspect(&mut self, entity: &mut Entity, cfg: &mut StaticallyKept, ui: &mut Ui, undo_stack: &mut Vec<UndoableAction>, _signal: &mut Signal, _label: &mut String) {
-        ui.group(|ui| {
-            ui.horizontal(|ui| {
-                ui.label("Name: ");
-
-                let resp = ui.text_edit_singleline(&mut self.label);
-
-                if resp.changed() {
-                    if cfg.old_label_entity.is_none() {
-                        cfg.old_label_entity = Some(entity.clone());
-                        cfg.label_original = Some(self.label.clone().to_string());
-                    }
-                    cfg.label_last_edit = Some(Instant::now());
-                }
-
-                if resp.lost_focus() {
-                    if let Some(ent) = cfg.old_label_entity.take() {
-                        if ent == *entity {
-                            if let Some(orig) = cfg.label_original.take() {
-                                UndoableAction::push_to_undo(
-                                    undo_stack,
-                                    UndoableAction::Label(ent, orig, EntityType::Light),
-                                );
-                                log::debug!("Pushed label change to undo stack (immediate)");
-                            }
-                        } else {
-                            cfg.label_original = None;
-                        }
-                    }
-                    cfg.label_last_edit = None;
-                }
-            })
-        });
-    }
-}
-
-impl Component for LightComponent {
-    fn inspect(&mut self, _entity: &mut Entity, _cfg: &mut StaticallyKept, ui: &mut Ui, _undo_stack: &mut Vec<UndoableAction>, _signal: &mut Signal, _label: &mut String) {
-        ui.group(|ui| {
-            ui.horizontal(|ui| {
-                ComboBox::new("light_type", "Light Type")
-                    // .width(ui.available_width())
-                    .selected_text(self.light_type.to_string())
-                    .show_ui(ui, |ui| {
-                        ui.selectable_value(&mut self.light_type, LightType::Directional, "Directional");
-                        ui.selectable_value(&mut self.light_type, LightType::Point, "Point");
-                        ui.selectable_value(&mut self.light_type, LightType::Spot, "Spot");
-                    });
-            });
-
-            // let is_dir = matches!(self.light_type, LightType::Directional);
-            let is_point = matches!(self.light_type, LightType::Point);
-            let is_spot = matches!(self.light_type, LightType::Spot);
-
-            // colour
-            ui.separator();
-            let mut colour = self.colour.clone().as_vec3().to_array();
-            ui.horizontal(|ui| {
-                ui.label("Colour");
-                egui::color_picker::color_edit_button_rgb(ui, &mut colour)
-            });
-            self.colour = Vec3::from_array(colour).as_dvec3();
-
-            // intensity
-            ui.separator();
-            ui.horizontal(|ui| {
-                ui.label("Intensity");
-                ui.add(egui::Slider::new(&mut self.intensity, 0.0..=1.0));
-            });
-
-            // enabled and visible
-            ui.separator();
-            ui.horizontal(|ui| {
-                ui.checkbox(&mut self.enabled, "Enabled");
-                ui.checkbox(&mut self.visible, "Visible");
-            });
-
-            if is_spot || is_point {
-                // attenuation
-                ui.separator();
-                ui.horizontal(|ui| {
-                    ComboBox::new("Range", "Range")
-                        // .width(ui.available_width())
-                        .selected_text(format!("Range {}", self.attenuation.range.to_string()))
-                        .show_ui(ui, |ui| {
-                            for (preset, label) in ATTENUATION_PRESETS {
-                                ui.selectable_value(&mut self.attenuation, preset.clone(), *label);
-                            }
-                        });
-                });
-            }
-
-            if is_spot {
-                // cutoff angles
-                ui.horizontal(|ui| {
-                    ui.add(
-                        egui::Slider::new(&mut self.cutoff_angle, 1.0..=89.0)
-                            .text("Inner")
-                            .suffix("°")
-                            .step_by(0.1)
-                    );
-                });
-
-                ui.horizontal(|ui| {
-                    ui.add(
-                        egui::Slider::new(&mut self.outer_cutoff_angle, 1.0..=90.0)
-                            .text("Outer")
-                            .suffix("°")
-                            .step_by(0.1)
-                    );
-                });
-
-                if self.outer_cutoff_angle <= self.cutoff_angle {
-                    self.outer_cutoff_angle = self.cutoff_angle + 1.0;
-                }
-
-                let cone_softness = self.outer_cutoff_angle - self.cutoff_angle;
-                ui.label(format!("Soft edge: {:.1}°", cone_softness));
-            }
-        });
-    }
-}
\ No newline at end of file
diff --git a/eucalyptus/src/editor/dock.rs b/eucalyptus/src/editor/dock.rs
deleted file mode 100644
index 81adcce..0000000
--- a/eucalyptus/src/editor/dock.rs
+++ /dev/null
@@ -1,1031 +0,0 @@
-use super::*;
-use std::{collections::HashSet, sync::LazyLock};
-
-use dropbear_engine::{entity::Transform, lighting::{Light, LightComponent}};
-use egui;
-use egui_dock_fork::TabViewer;
-use egui_extras;
-use log;
-use parking_lot::Mutex;
-use serde::{Deserialize, Serialize};
-use transform_gizmo_egui::{
-    math::{DMat4, DVec3}, EnumSet, Gizmo, GizmoConfig, GizmoExt, GizmoMode
-};
-
-use crate::{
-    APP_INFO,
-    editor::scene::PENDING_SPAWNS,
-    states::{EntityNode, Node, RESOURCES, ResourceType},
-    utils::PendingSpawn,
-};
-use crate::editor::component::Component;
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-pub enum EditorTab {
-    AssetViewer,       // bottom side,
-    ResourceInspector, // left side,
-    ModelEntityList,   // right side,
-    Viewport,          // middle,
-}
-
-pub struct EditorTabViewer<'a> {
-    pub view: egui::TextureId,
-    pub nodes: Vec<EntityNode>,
-    pub tex_size: Extent3d,
-    pub gizmo: &'a mut Gizmo,
-    pub world: &'a mut hecs::World,
-    pub selected_entity: &'a mut Option<hecs::Entity>,
-    pub viewport_mode: &'a mut ViewportMode,
-    pub undo_stack: &'a mut Vec<UndoableAction>,
-    pub signal: &'a mut Signal,
-    pub gizmo_mode: &'a mut EnumSet<GizmoMode>,
-    pub editor_mode: &'a mut EditorState,
-    pub active_camera: &'a mut Option<hecs::Entity>,
-}
-
-impl<'a> EditorTabViewer<'a> {
-    fn spawn_entity_at_pos(
-        &mut self,
-        asset: &DraggedAsset,
-        position: glam::DVec3,
-        properties: Option<ModelProperties>,
-    ) -> anyhow::Result<()> {
-        let mut transform = Transform::default();
-        transform.position = position;
-        {
-            let mut pending_spawns = PENDING_SPAWNS.lock();
-            if let Some(props) = properties {
-                pending_spawns.push(PendingSpawn {
-                    asset_path: asset.path.clone(),
-                    asset_name: asset.name.clone(),
-                    transform,
-                    properties: props,
-                });
-            } else {
-                pending_spawns.push(PendingSpawn {
-                    asset_path: asset.path.clone(),
-                    asset_name: asset.name.clone(),
-                    transform,
-                    properties: ModelProperties::default(),
-                });
-            }
-            Ok(())
-        }
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct DraggedAsset {
-    pub name: String,
-    pub path: PathBuf,
-}
-
-pub static TABS_GLOBAL: LazyLock<Mutex<StaticallyKept>> =
-    LazyLock::new(|| Mutex::new(StaticallyKept::default()));
-
-
-/// Variables kept statically. 
-/// 
-/// The entire module (including the tab viewer) due to it
-/// being part of an update/render function, therefore this is used to ensure
-/// progress is not lost. 
-#[derive(Default)]
-pub struct StaticallyKept {
-    show_context_menu: bool,
-    context_menu_pos: egui::Pos2,
-    context_menu_tab: Option<EditorTab>,
-    is_focused: bool,
-    old_pos: Transform,
-    pub(crate) scale_locked: bool,
-
-    pub(crate) old_label_entity: Option<hecs::Entity>,
-    pub(crate) label_original: Option<String>,
-    pub(crate) label_last_edit: Option<Instant>,
-
-    pub(crate) transform_old_entity: Option<hecs::Entity>,
-    pub(crate) transform_original_transform: Option<Transform>,
-
-    pub(crate) transform_in_progress: bool,
-}
-
-impl StaticallyKept {}
-
-impl<'a> TabViewer for EditorTabViewer<'a> {
-    type Tab = EditorTab;
-
-    fn title(&mut self, tab: &mut Self::Tab) -> egui::WidgetText {
-        match tab {
-            EditorTab::Viewport => "Viewport".into(),
-            EditorTab::ModelEntityList => "Model/Entity List".into(),
-            EditorTab::AssetViewer => "Asset Viewer".into(),
-            EditorTab::ResourceInspector => "Resource Inspector".into(),
-        }
-    }
-
-    fn ui(&mut self, ui: &mut egui::Ui, tab: &mut Self::Tab) {
-        let mut cfg = TABS_GLOBAL.lock();
-        
-        ui.ctx().input(|i| {
-            if i.pointer.button_pressed(egui::PointerButton::Secondary) {
-                if let Some(pos) = i.pointer.hover_pos() {
-                    if ui.available_rect_before_wrap().contains(pos) {
-                        cfg.show_context_menu = true;
-                        cfg.context_menu_pos = pos;
-                        cfg.context_menu_tab = Some(tab.clone());
-                    }
-                }
-            }
-        });
-
-        match tab {
-            EditorTab::Viewport => {
-                // ------------------- Template for querying active camera -----------------
-                // if let Some(active_camera) = self.active_camera {
-                //     if let Ok(mut q) = self.world.query_one::<(&Camera, &CameraComponent, Option<&CameraFollowTarget>)>(*active_camera) {
-                //         if let Some((camera, component, follow_target)) = q.get() {
-
-                //         } else {
-                //             log_once::warn_once!("Unable to fetch the query result of camera: {:?}", active_camera)
-                //         }
-                //     } else {
-                //         log_once::warn_once!("Unable to query camera, component and option<camerafollowtarget> for active camera: {:?}", active_camera);
-                //     }
-                // } else {
-                //     log_once::warn_once!("No active camera found");
-                // }
-                // -------------------------------------------------------------------------
-
-                let available_rect = ui.available_rect_before_wrap();
-                let available_size = available_rect.size();
-                
-                let tex_aspect = self.tex_size.width as f32 / self.tex_size.height as f32;
-                let available_aspect = available_size.x / available_size.y;
-                
-                let (display_width, display_height) = if available_aspect > tex_aspect {
-                    let height = available_size.y * 0.95;
-                    let width = height * tex_aspect;
-                    (width, height)
-                } else {
-                    let width = available_size.x * 0.95;
-                    let height = width / tex_aspect;
-                    (width, height)
-                };
-
-                let center_x = available_rect.center().x;
-                let center_y = available_rect.center().y;
-                
-                let image_rect = egui::Rect::from_center_size(
-                    egui::pos2(center_x, center_y),
-                    egui::vec2(display_width, display_height)
-                );
-
-                let (_rect, _response) = ui.allocate_exact_size(
-                    available_size,
-                    egui::Sense::click_and_drag()
-                );
-
-                let _image_response = ui.allocate_rect(image_rect, egui::Sense::click_and_drag());
-
-                ui.scope_builder(egui::UiBuilder::new().max_rect(image_rect), |ui| {
-                    ui.add_sized(
-                        [display_width, display_height],
-                        egui::Image::new((
-                            self.view,
-                            [display_width, display_height].into(),
-                        ))
-                        .fit_to_exact_size([display_width, display_height].into())
-                    )
-                });
-
-                let image_response = ui.interact(image_rect, ui.id().with("viewport_image"), egui::Sense::click_and_drag());
-
-                if image_response.clicked() {
-                    if let Some(active_camera) = self.active_camera {
-                        if let Ok(mut q) = self.world.query_one::<(&Camera, &CameraComponent, Option<&CameraFollowTarget>)>(*active_camera) {
-                            if let Some((camera, _, _)) = q.get() {
-                                if let Some(click_pos) = ui.ctx().input(|i| i.pointer.interact_pos()) {
-                                let viewport_rect = image_response.rect;
-                                let local_pos = click_pos - viewport_rect.min;
-
-                                let ndc_x = (2.0 * local_pos.x / viewport_rect.width()) - 1.0;
-                                let ndc_y = 1.0 - (2.0 * local_pos.y / viewport_rect.height());
-
-                                let view_matrix =
-                                    glam::DMat4::look_at_lh(camera.eye, camera.target, camera.up);
-
-                                let proj_matrix = glam::DMat4::perspective_lh(
-                                    camera.fov_y.to_radians(),
-                                    camera.aspect,
-                                    camera.znear,
-                                    camera.zfar,
-                                );
-
-                                if !view_matrix.is_finite() {
-                                    log::error!("Invalid view matrix");
-                                    return;
-                                }
-                                if !proj_matrix.is_finite() {
-                                    log::error!("Invalid projection matrix");
-                                    return;
-                                }
-
-                                let view_proj = proj_matrix * view_matrix;
-                                let inv_view_proj = view_proj.inverse();
-
-                                if !inv_view_proj.is_finite() {
-                                    log::error!("Cannot invert view-projection matrix");
-                                    return;
-                                }
-
-                                let ray_start_ndc = glam::DVec4::new(ndc_x as f64, ndc_y as f64, 0.0, 1.0);
-                                let ray_end_ndc = glam::DVec4::new(ndc_x as f64, ndc_y as f64, 1.0, 1.0);
-
-                                let ray_start_world = inv_view_proj * ray_start_ndc;
-                                let ray_end_world = inv_view_proj * ray_end_ndc;
-
-                                if ray_start_world.w == 0.0 || ray_end_world.w == 0.0 {
-                                    log::error!("Invalid homogeneous coordinates");
-                                    return;
-                                }
-
-                                let ray_start = ray_start_world.truncate() / ray_start_world.w;
-                                let ray_end = ray_end_world.truncate() / ray_end_world.w;
-
-                                if !ray_start.is_finite() || !ray_end.is_finite() {
-                                    log::error!(
-                                        "Invalid ray points - start: {:?}, end: {:?}",
-                                        ray_start,
-                                        ray_end
-                                    );
-                                    return;
-                                }
-
-                                let ray_direction = (ray_end - ray_start).normalize();
-
-                                if !ray_direction.is_finite() {
-                                    log::error!("Invalid ray direction: {:?}", ray_direction);
-                                    return;
-                                }
-
-                                let mut closest_distance = f64::INFINITY;
-                                let mut selected_entity_id: Option<hecs::Entity> = None;
-                                let mut entity_count = 0;
-
-                                for (entity_id, (transform, _)) in
-                                    self.world.query::<(&Transform, &AdoptedEntity)>().iter()
-                                {
-                                    entity_count += 1;
-                                    let entity_pos = transform.position;
-                                    let sphere_radius = transform.scale.max_element() * 1.5;
-
-                                    let to_sphere = entity_pos - ray_start;
-                                    let projection = to_sphere.dot(ray_direction);
-
-                                    if projection > 0.0 {
-                                        let closest_point = ray_start + ray_direction * projection;
-                                        let distance_to_sphere = (closest_point - entity_pos).length();
-
-                                        if distance_to_sphere <= sphere_radius {
-                                            let discriminant = sphere_radius * sphere_radius
-                                                - distance_to_sphere * distance_to_sphere;
-                                            if discriminant >= 0.0 {
-                                                let intersection_distance =
-                                                    projection - discriminant.sqrt();
-
-                                                if intersection_distance < closest_distance {
-                                                    closest_distance = intersection_distance;
-                                                    selected_entity_id = Some(entity_id);
-                                                }
-                                            }
-                                        }
-                                    }
-                                }
-
-                                log::debug!("Total entities checked: {}", entity_count);
-
-                                if !matches!(self.editor_mode, EditorState::Playing) {
-                                    if let Some(entity_id) = selected_entity_id {
-                                        *self.selected_entity = Some(entity_id);
-                                        log::debug!("Selected entity: {:?}", entity_id);
-                                    } else {
-                                        // *self.selected_entity = None;
-                                        if entity_count == 0 {
-                                            log::debug!("No entities in world to select");
-                                        } else {
-                                            log::debug!(
-                                                "No entity hit by ray (checked {} entities)",
-                                                entity_count
-                                            );
-                                        }
-                                    }
-                                }
-                            }
-                            } else {
-                                log_once::warn_once!("Unable to fetch the query result of camera: {:?}", active_camera)
-                            }
-                        } else {
-                            log_once::warn_once!("Unable to query camera, component and option<camerafollowtarget> for active camera: {:?}", active_camera);
-                        }
-                    } else {
-                        log_once::warn_once!("No active camera found");
-                    }
-                    
-                }
-
-                let snapping = ui.input(|input| input.modifiers.shift);
-
-                // Note to self: fuck you >:(
-                // Note to self: ok wow thats pretty rude im trying my best >﹏<
-                // Note to self: finally holy shit i got it working
-
-                if let Some(active_camera) = self.active_camera {
-                    if let Ok(mut q) = self.world.query_one::<(&Camera, &CameraComponent, Option<&CameraFollowTarget>)>(*active_camera) {
-                        if let Some((camera, _, _)) = q.get() {
-                            self.gizmo.update_config(GizmoConfig {
-                            view_matrix: DMat4::look_at_lh(
-                                DVec3::new(
-                                    camera.eye.x as f64,
-                                    camera.eye.y as f64,
-                                    camera.eye.z as f64,
-                                ),
-                                DVec3::new(
-                                    camera.target.x as f64,
-                                    camera.target.y as f64,
-                                    camera.target.z as f64,
-                                ),
-                                DVec3::new(camera.up.x as f64, camera.up.y as f64, camera.up.z as f64),
-                            )
-                            .into(),
-                            projection_matrix: DMat4::perspective_infinite_reverse_lh(
-                                camera.fov_y as f64,
-                                display_width as f64 / display_height as f64,
-                                camera.znear as f64,
-                            )
-                            .into(),
-                            viewport: image_rect,
-                            modes: *self.gizmo_mode,
-                            orientation: transform_gizmo_egui::GizmoOrientation::Global,
-                            snapping,
-                            snap_distance: 1.0,
-                            ..Default::default()
-                        });
-                        } else {
-                            log_once::warn_once!("Unable to fetch the query result of camera: {:?}", active_camera)
-                        }
-                    } else {
-                        log_once::warn_once!("Unable to query camera, component and option<camerafollowtarget> for active camera: {:?}", active_camera);
-                    }
-                } else {
-                    log_once::warn_once!("No active camera found");
-                }
-
-                if !matches!(self.viewport_mode, crate::utils::ViewportMode::None) {
-                    if let Some(entity_id) = self.selected_entity {
-                        if let Ok(transform) =
-                            self.world.query_one_mut::<&mut Transform>(*entity_id)
-                        {
-                            let was_focused = cfg.is_focused;
-                            cfg.is_focused = self.gizmo.is_focused();
-
-                            if cfg.is_focused && !was_focused {
-                                cfg.old_pos = *transform;
-                            }
-
-                            let gizmo_transform =
-                                transform_gizmo_egui::math::Transform::from_scale_rotation_translation(
-                                    transform.scale,
-                                    transform.rotation,
-                                    transform.position,
-                                );
-
-                            if let Some((_result, new_transforms)) =
-                                self.gizmo.interact(ui, &[gizmo_transform])
-                            {
-                                if let Some(new_transform) = new_transforms.first() {
-                                    transform.position = new_transform.translation.into();
-                                    transform.rotation = new_transform.rotation.into();
-                                    transform.scale = new_transform.scale.into();
-                                }
-                            }
-
-                            if was_focused && !cfg.is_focused {
-                                let transform_changed = cfg.old_pos.position != transform.position
-                                    || cfg.old_pos.rotation != transform.rotation
-                                    || cfg.old_pos.scale != transform.scale;
-
-                                if transform_changed {
-                                    UndoableAction::push_to_undo(
-                                        &mut self.undo_stack,
-                                        UndoableAction::Transform(
-                                            entity_id.clone(),
-                                            cfg.old_pos.clone(),
-                                        ),
-                                    );
-                                    log::debug!("Pushed transform action to stack");
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-            EditorTab::ModelEntityList => {
-                ui.label("Model/Entity List");
-                show_entity_tree(
-                    ui,
-                    &mut self.nodes,
-                    &mut self.selected_entity,
-                    "Model Entity Asset List",
-                );
-            }
-            EditorTab::AssetViewer => {
-                egui_extras::install_image_loaders(ui.ctx());
-
-                let mut assets: Vec<(String, String, PathBuf, ResourceType)> = Vec::new();
-                {
-                    let res = RESOURCES.read().unwrap();
-                    egui_extras::install_image_loaders(ui.ctx());
-
-                    fn recursive_search_nodes_and_attach_thumbnail(
-                        res: &Vec<Node>,
-                        assets: &mut Vec<(String, String, PathBuf, ResourceType)>,
-                        logged: &mut HashSet<String>,
-                    ) {
-                        for node in res {
-                            match node {
-                                Node::File(file) => {
-                                    if !logged.contains(&file.name) {
-                                        logged.insert(file.name.clone());
-                                        log::debug!(
-                                            "Adding image for {} of type {}",
-                                            file.name,
-                                            file.resource_type.as_ref().unwrap()
-                                        );
-                                    }
-                                    if let Some(ref res_type) = file.resource_type {
-                                        match res_type {
-                                            crate::states::ResourceType::Model => {
-                                                let ad_dir = app_dirs2::get_app_root(
-                                                    app_dirs2::AppDataType::UserData,
-                                                    &APP_INFO,
-                                                )
-                                                .unwrap();
-
-                                                let model_thumbnail =
-                                                    ad_dir.join(format!("{}.png", file.name));
-
-                                                if !model_thumbnail.exists() {
-                                                    // gen image
-                                                    log::debug!(
-                                                        "Model thumbnail [{}] does not exist, generating one now",
-                                                        file.name
-                                                    );
-                                                    let mut model = match model_to_image::ModelToImageBuilder::new(&file.path)
-                                                    .with_size((600, 600))
-                                                    .build() {
-                                                        Ok(v) => v,
-                                                        Err(e) => panic!("Error occurred while loading file from path: {}", e),
-                                                    };
-                                                    if let Err(e) =
-                                                        model.render().unwrap().write_to(Some(
-                                                            &ad_dir
-                                                                .join(format!("{}.png", file.name)),
-                                                        ))
-                                                    {
-                                                        log::error!(
-                                                            "Failed to write model thumbnail for {}: {}",
-                                                            file.name,
-                                                            e
-                                                        );
-                                                    }
-                                                }
-
-                                                let image_uri =
-                                                    model_thumbnail.to_string_lossy().to_string();
-
-                                                assets.push((
-                                                    format!("file://{}", image_uri),
-                                                    file.name.clone(),
-                                                    file.path.clone(),
-                                                    res_type.clone(),
-                                                ))
-                                            }
-                                            ResourceType::Texture => assets.push((
-                                                format!(
-                                                    "file://{}",
-                                                    file.path.to_string_lossy().to_string()
-                                                ),
-                                                file.name.clone(),
-                                                file.path.clone(),
-                                                res_type.clone(),
-                                            )),
-                                            _ => {
-                                                if file
-                                                    .path
-                                                    .clone()
-                                                    .extension()
-                                                    .unwrap()
-                                                    .to_str()
-                                                    .unwrap()
-                                                    .contains("euc")
-                                                {
-                                                    continue;
-                                                }
-                                                assets.push((
-                                                    "NO_TEXTURE".into(),
-                                                    file.name.clone(),
-                                                    file.path.clone(),
-                                                    res_type.clone(),
-                                                ))
-                                            }
-                                        }
-                                    }
-                                }
-                                Node::Folder(folder) => {
-                                    recursive_search_nodes_and_attach_thumbnail(
-                                        &folder.nodes,
-                                        assets,
-                                        logged,
-                                    )
-                                }
-                            }
-                        }
-                    }
-
-                    let mut logged = LOGGED.lock();
-                    recursive_search_nodes_and_attach_thumbnail(
-                        &res.nodes,
-                        &mut assets,
-                        &mut logged,
-                    );
-                }
-
-                egui::ScrollArea::vertical().show(ui, |ui| {
-                    let max_columns = 6;
-                    let available_width = ui.clip_rect().width() - ui.spacing().indent;
-                    let margin = 16.0;
-                    let usable_width = available_width - margin;
-                    let label_height = 20.0;
-                    let padding = 8.0;
-                    let min_card_width = 60.0;
-
-                    let mut columns = max_columns;
-                    for test_columns in (1..=max_columns).rev() {
-                        let card_width = usable_width / test_columns as f32;
-                        if card_width >= min_card_width {
-                            columns = test_columns;
-                            break;
-                        }
-                    }
-
-                    let card_width = usable_width / columns as f32;
-                    let image_size = (card_width - label_height - padding).max(32.0);
-                    let card_height = image_size + label_height + padding;
-
-                    for row_start in (0..assets.len()).step_by(columns) {
-                        let row_end = (row_start + columns).min(assets.len());
-                        let row_items = &mut assets[row_start..row_end];
-
-                        ui.horizontal(|ui| {
-                            ui.set_max_width(usable_width);
-
-                            egui_dnd::dnd(ui, format!("asset_row_{}", row_start / columns))
-                                .show_vec(
-                                    row_items,
-                                    |ui, (image, asset_name, asset_path, asset_type), handle, state| {
-                                        let card_size = egui::vec2(card_width, card_height);
-                                        handle.ui(ui, |ui| {
-                                            let (rect, card_response) = ui.allocate_exact_size(
-                                                card_size,
-                                                egui::Sense::click(),
-                                            );
-
-                                            let mut card_ui = ui.new_child(
-                                                egui::UiBuilder::new().max_rect(rect).layout(
-                                                    egui::Layout::top_down(egui::Align::Center),
-                                                ),
-                                            );
-
-                                            let image_response = card_ui.add_sized(
-                                                [image_size, image_size],
-                                                egui::ImageButton::new(image.clone()).frame(false),
-                                            );
-
-                                            let is_hovered = card_response.hovered() || image_response.hovered() || state.dragged;
-                                            let is_d_clicked = card_response.double_clicked() || image_response.double_clicked();
-
-                                            if is_d_clicked {
-                                                if matches!(asset_type, ResourceType::Model) {
-                                                    let mut spawn_position = DVec3::default();
-                                                    if let Some(active_camera) = self.active_camera {
-                                                        if let Ok(mut q) = self.world.query_one::<(&Camera, &CameraComponent, Option<&CameraFollowTarget>)>(*active_camera) {
-                                                            if let Some((camera, _, _)) = q.get() {
-                                                                spawn_position = camera.eye;
-                                                            } else {
-                                                                log_once::warn_once!("Unable to fetch the query result of camera: {:?}", active_camera)
-                                                            }
-                                                        } else {
-                                                            log_once::warn_once!("Unable to query camera, component and option<camerafollowtarget> for active camera: {:?}", active_camera);
-                                                        }
-                                                    } else {
-                                                        log_once::warn_once!("No active camera found");
-                                                    }
-
-                                                    let asset = DraggedAsset {
-                                                        name: asset_name.clone(),
-                                                        path: asset_path.clone(),
-                                                        // asset_type: asset_type.clone(),
-                                                    };
-
-                                                    match self.spawn_entity_at_pos(&asset, spawn_position, None) {
-                                                        Ok(()) => {
-                                                            log::debug!("double click spawned {} at camera pos {:?}",
-                                                                asset.name, spawn_position
-                                                            );
-
-                                                            crate::success!("Spawned {} at camera", asset.name);
-                                                        }
-                                                        Err(e) => {
-                                                            log::error!(
-                                                            "Failed to spawn {} at camera: {}",
-                                                            asset.name,
-                                                            e);
-
-                                                            crate::fatal!("Failed to spawn {}: {}",
-                                                                        asset.name, e);
-                                                        }
-                                                    }
-                                                }
-                                            }
-
-                                            if is_hovered || state.dragged {
-                                                ui.painter().rect_filled(
-                                                    rect,
-                                                    6.0,
-                                                    if state.dragged {
-                                                        egui::Color32::from_rgb(80, 80, 100)
-                                                    } else {
-                                                        egui::Color32::from_rgb(60, 60, 80)
-                                                    },
-                                                );
-                                            }
-
-                                            card_ui.vertical_centered(|ui| {
-                                                ui.label(
-                                                    egui::RichText::new(asset_name.clone())
-                                                        .strong()
-                                                        .color(egui::Color32::WHITE),
-                                                );
-                                            });
-                                        });
-                                    },
-                                );
-                        });
-                        ui.add_space(8.0);
-                    }
-                });
-            }
-            EditorTab::ResourceInspector => {
-                if let Some(entity) = self.selected_entity {
-                    if let Ok((e, transform, _props, script, camera, camera_component, follow_target)) = self
-                    .world
-                    .query_one_mut::<(&mut AdoptedEntity, Option<&mut Transform>, Option<&ModelProperties>, Option<&mut ScriptComponent>, Option<&mut Camera>, Option<&mut CameraComponent>, Option<&mut CameraFollowTarget>)>(*entity) {
-                        e.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, &mut String::new());
-                        if let Some(t) = transform {
-                            t.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, e.label_mut());
-                        }
-
-                        if let (Some(camera), Some(camera_component)) = (camera, camera_component) {
-                            ui.separator();
-                            ui.label("Camera Components:");
-                            camera.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, &mut String::new());
-                            camera_component.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, &mut camera.label.clone());
-                            
-                            if let Some(target) = follow_target {
-                                target.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, &mut camera.label.clone());
-                            }
-                        }
-
-                        // if let Some(props) = _props {
-                        //     props.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, e.label_mut());
-                        // }
-
-                        if let Some(script) = script {
-                            script.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, e.label_mut());
-                        }
-
-                        if let Some(t) = cfg.label_last_edit {
-                            if t.elapsed() >= Duration::from_millis(500) {
-                                if let Some(ent) = cfg.old_label_entity.take() {
-                                    if let Some(orig) = cfg.label_original.take() {
-                                        UndoableAction::push_to_undo(
-                                            &mut self.undo_stack,
-                                            UndoableAction::Label(ent, orig, EntityType::Entity),
-                                        );
-                                        log::debug!("Pushed label change to undo stack after 500ms debounce period");
-                                    }
-                                }
-                                cfg.label_last_edit = None;
-                            }
-                        }
-                    } else {
-                        log_once::debug_once!("Unable to query entity inside resource inspector");
-                    }
-                    
-                    if let Ok((light, transform, props)) = self.world.query_one_mut::<(&mut Light, &mut Transform, &mut LightComponent)>(*entity) {
-                            light.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, &mut String::new());
-                            transform.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, &mut light.label);
-                            props.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, &mut light.label);
-                            if let Some(t) = cfg.label_last_edit {
-                                if t.elapsed() >= Duration::from_millis(500) {
-                                    if let Some(ent) = cfg.old_label_entity.take() {
-                                        if let Some(orig) = cfg.label_original.take() {
-                                            UndoableAction::push_to_undo(
-                                                &mut self.undo_stack,
-                                                UndoableAction::Label(ent, orig, EntityType::Light),
-                                            );
-                                            log::debug!("Pushed label change to undo stack after 500ms debounce period");
-                                        }
-                                    }
-                                    cfg.label_last_edit = None;
-                                }
-                            }
-                    } else {
-                        log_once::debug_once!("Unable to query light inside resource inspector");
-                    }
-
-                    if let Ok((camera, camera_component, follow_target)) = self.world.query_one_mut::<(&mut Camera, &mut CameraComponent, Option<&mut CameraFollowTarget>)>(*entity) {
-                        camera.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, &mut String::new());
-                        camera_component.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, &mut camera.label.clone());
-                        if let Some(target) = follow_target {
-                            target.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, &mut camera.label.clone());
-                        }
-
-                        ui.separator();
-                        ui.label("Camera Controls:");
-                        if ui.button("Set as Active Camera").clicked() {
-                            *self.active_camera = Some(*entity);
-                            log::info!("Set camera '{}' as active", camera.label);
-                        }
-                        
-                        if matches!(self.editor_mode, EditorState::Editing) {
-                            if ui.button("Switch to This Camera").clicked() {
-                                *self.active_camera = Some(*entity);
-                                log::info!("Switched to camera '{}'", camera.label);
-                            }
-                        }
-                    }
-                } else {
-                    ui.label("No entity selected, therefore no info to provide. Go on, what are you waiting for? Click an entity!");
-                }
-            }
-        }
-
-        let mut menu_action: Option<EditorTabMenuAction> = None;
-        let area = egui::Area::new("context_menu".into())
-            .fixed_pos(cfg.context_menu_pos)
-            .order(egui::Order::Foreground);
-
-        if cfg.show_context_menu {
-            let menu_tab = cfg
-                .context_menu_tab
-                .clone()
-                .unwrap_or(EditorTab::ModelEntityList);
-
-            let mut popup_rect = None;
-
-            area.show(ui.ctx(), |ui| {
-                egui::Frame::popup(ui.style()).show(ui, |ui| {
-                    popup_rect.replace(ui.max_rect());
-
-                    match menu_tab {
-                        EditorTab::AssetViewer => {
-                            ui.set_min_width(150.0);
-                            if ui.selectable_label(false, "Import resource").clicked() {
-                                menu_action = Some(EditorTabMenuAction::ImportResource);
-                            }
-                            if ui.selectable_label(false, "Refresh assets").clicked() {
-                                menu_action = Some(EditorTabMenuAction::RefreshAssets);
-                            }
-                        }
-                        EditorTab::ModelEntityList => {
-                            ui.set_min_width(150.0);
-                            if ui.selectable_label(false, "Add Entity").clicked() {
-                                menu_action = Some(EditorTabMenuAction::AddEntity);
-                            }
-                            if ui.selectable_label(false, "Delete Entity").clicked() {
-                                menu_action = Some(EditorTabMenuAction::DeleteEntity);
-                            }
-                        }
-                        EditorTab::ResourceInspector => {
-                            ui.set_min_width(150.0);
-                            if ui.selectable_label(false, "Add Component").clicked() {
-                                menu_action = Some(EditorTabMenuAction::AddComponent);
-                            }
-                        }
-                        EditorTab::Viewport => {
-                            ui.set_min_width(150.0);
-                            if ui.selectable_label(false, "Viewport Option").clicked() {
-                                menu_action = Some(EditorTabMenuAction::ViewportOption);
-                            }
-                        }
-                    }
-                })
-            });
-
-            if let Some(action) = menu_action {
-                if Some(tab.clone()) == cfg.context_menu_tab {
-                    match action {
-                        EditorTabMenuAction::ImportResource => {
-                            log::debug!("Import Resource clicked");
-
-                            match import_object() {
-                                Ok(_) => {
-                                    crate::success!("Resource(s) imported successfully!");
-                                }
-                                Err(e) => {
-                                    crate::warn!("Failed to import resource(s): {e}");
-                                }
-                            }
-                            cfg.show_context_menu = false;
-                            cfg.context_menu_tab = None;
-                            return;
-                        }
-                        EditorTabMenuAction::RefreshAssets => {
-                            log::debug!("Refresh assets clicked");
-                            if let Ok(mut res) = RESOURCES.write() {
-                                match res.update_mem() {
-                                    Ok(res_cfg) => {
-                                        *res = res_cfg;
-                                        crate::success!("Assets refreshed successfully!");
-                                    }
-                                    Err(e) => {
-                                        crate::fatal!("Failed to refresh assets: {}", e);
-                                    }
-                                }
-                            }
-                            cfg.show_context_menu = false;
-                            cfg.context_menu_tab = None;
-                            return;
-                        }
-                        EditorTabMenuAction::AddEntity => {
-                            log::debug!("Add Entity clicked");
-                            *self.signal = Signal::CreateEntity;
-                            cfg.show_context_menu = false;
-                            cfg.context_menu_tab = None;
-                            return;
-                        }
-                        EditorTabMenuAction::DeleteEntity => {
-                            log::debug!("Delete Entity clicked");
-                            *self.signal = Signal::Delete;
-                            cfg.show_context_menu = false;
-                            cfg.context_menu_tab = None;
-                            return;
-                        }
-                        EditorTabMenuAction::AddComponent => {
-                            log::debug!("Add Component clicked");
-                            if let Some(entity) = self.selected_entity {
-                                if let Ok(..) = self.world.query_one_mut::<&AdoptedEntity>(*entity) {
-                                    log::debug!("Queried selected entity, it is an entity");
-                                    *self.signal = Signal::AddComponent(*entity, EntityType::Entity);
-                                }
-
-                                if let Ok(..) = self.world.query_one_mut::<&Light>(*entity) {
-                                    log::debug!("Queried selected entity, it is a light");
-                                    *self.signal = Signal::AddComponent(*entity, EntityType::Light);
-                                }
-                            } else {
-                                crate::warn!("What are you adding a component to? Theres no entity selected...");
-                            }
-
-                            cfg.show_context_menu = false;
-                            cfg.context_menu_tab = None;
-                            return;
-                        }
-                        EditorTabMenuAction::ViewportOption => {
-                            log::debug!("Viewport Option clicked");
-                            cfg.show_context_menu = false;
-                            cfg.context_menu_tab = None;
-                            return;
-                        },
-                        EditorTabMenuAction::RemoveComponent => {
-                            log::debug!("Remove Component clicked");
-                            if let Some(entity) = self.selected_entity {
-                                if let Ok(script) = self.world.query_one_mut::<&ScriptComponent>(*entity) {
-                                    log::debug!("Queried selected entity, it has a script component");
-                                    *self.signal = Signal::RemoveComponent(*entity, ComponentType::Script(script.clone()));
-                                } else {
-                                    crate::warn!("Selected entity does not have a script component to remove");
-                                }
-                            } else {
-                                panic!("Paradoxical error: Cannot remove a component when its not selected...");
-                            }
-
-                            cfg.show_context_menu = false;
-                            cfg.context_menu_tab = None;
-                            return;
-                        }
-                    }
-                }
-            }
-
-            if let Some(rect) = popup_rect {
-                if cfg.show_context_menu && Some(tab.clone()) == cfg.context_menu_tab {
-                    if ui
-                        .ctx()
-                        .input(|i| i.pointer.button_clicked(egui::PointerButton::Primary))
-                    {
-                        if let Some(pos) = ui.ctx().input(|i| i.pointer.interact_pos()) {
-                            if !rect.contains(pos) {
-                                cfg.show_context_menu = false;
-                                cfg.context_menu_tab = None;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
-
-pub(crate) fn import_object() -> anyhow::Result<()> {
-    let model_ext = vec!["glb", "fbx", "obj"];
-    let texture_ext = vec!["png"];
-
-    let files = rfd::FileDialog::new()
-        .add_filter("All Files", &["*"])
-        .add_filter("Model", &model_ext)
-        .add_filter("Texture", &texture_ext)
-        .pick_files();
-    if let Some(files) = files {
-        for file in files {
-            let ext = file.extension().unwrap().to_str().unwrap();
-            let mut copied = false;
-            for mde in model_ext.iter() {
-                if ext.contains(mde) {
-                    // copy over to models folder
-                    if let Some(project) = crate::states::PROJECT.read().ok() {
-                        let models_dir = PathBuf::from(project.project_path.clone())
-                            .join("resources")
-                            .join("models");
-                        if !models_dir.exists() {
-                            std::fs::create_dir_all(&models_dir)?;
-                        }
-                        let dest = models_dir.join(file.file_name().unwrap());
-                        std::fs::copy(&file, &dest)?;
-                        log::info!("Copied model file to {:?}", dest);
-                        copied = true;
-                    }
-                }
-            }
-            for tex in texture_ext.iter() {
-                if ext.contains(tex) {
-                    // copy over to textures folder
-                    if let Some(project) = crate::states::PROJECT.read().ok() {
-                        let textures_dir = PathBuf::from(project.project_path.clone())
-                            .join("resources")
-                            .join("textures");
-                        if !textures_dir.exists() {
-                            std::fs::create_dir_all(&textures_dir)?;
-                        }
-                        let dest = textures_dir.join(file.file_name().unwrap());
-                        std::fs::copy(&file, &dest)?;
-                        log::info!("Copied texture file to {:?}", dest);
-                        copied = true;
-                    }
-                }
-            }
-
-            if !copied {
-                if let Some(project) = crate::states::PROJECT.read().ok() {
-                    // everything else copies over to resources root dir
-                    let resources_dir =
-                        PathBuf::from(project.project_path.clone()).join("resources");
-                    if !resources_dir.exists() {
-                        std::fs::create_dir_all(&resources_dir)?;
-                    }
-                    let dest = resources_dir.join(file.file_name().unwrap());
-                    std::fs::copy(&file, &dest)?;
-                    log::info!("Copied other resource file to {:?}", dest);
-                }
-            }
-        }
-        // save it all to ensure the eucc recognises it
-        let mut proj = PROJECT.write().unwrap();
-        proj.write_to_all()?;
-        Ok(())
-    } else {
-        return Err(anyhow::anyhow!("File dialogue returned None"));
-    }
-}
-
-#[derive(Debug, Clone, Copy)]
-pub enum EditorTabMenuAction {
-    ImportResource,
-    RefreshAssets,
-    AddEntity,
-    DeleteEntity,
-    AddComponent,
-    RemoveComponent,
-    ViewportOption,
-}
diff --git a/eucalyptus/src/editor/input.rs b/eucalyptus/src/editor/input.rs
deleted file mode 100644
index 595f00c..0000000
--- a/eucalyptus/src/editor/input.rs
+++ /dev/null
@@ -1,286 +0,0 @@
-use super::*;
-use dropbear_engine::{
-    entity::{AdoptedEntity, Transform},
-    input::{Controller, Keyboard, Mouse},
-};
-use gilrs::{Button, GamepadId};
-use log;
-use transform_gizmo_egui::GizmoMode;
-use winit::{
-    dpi::PhysicalPosition, event::MouseButton, event_loop::ActiveEventLoop, keyboard::KeyCode,
-};
-
-impl Keyboard for Editor {
-    fn key_down(&mut self, key: KeyCode, _event_loop: &ActiveEventLoop) {
-        #[cfg(not(target_os = "macos"))]
-        let ctrl_pressed = self.input_state.pressed_keys.contains(&KeyCode::ControlLeft)
-            || self.input_state.pressed_keys.contains(&KeyCode::ControlRight);
-        #[cfg(target_os = "macos")]
-        let ctrl_pressed = self.input_state.pressed_keys.contains(&KeyCode::SuperLeft)
-            || self.input_state.pressed_keys.contains(&KeyCode::SuperRight);
-
-        let _alt_pressed = self.input_state.pressed_keys.contains(&KeyCode::AltLeft)
-            || self.input_state.pressed_keys.contains(&KeyCode::AltRight);
-
-        let shift_pressed = self.input_state.pressed_keys.contains(&KeyCode::ShiftLeft)
-            || self.input_state.pressed_keys.contains(&KeyCode::ShiftRight);
-
-        let is_double_press = self.double_key_pressed(key);
-
-        let is_playing = matches!(self.editor_state, EditorState::Playing);
-
-        match key {
-            KeyCode::KeyG => {
-                if self.is_viewport_focused && !is_playing {
-                    self.viewport_mode = crate::utils::ViewportMode::Gizmo;
-                    crate::info!("Switched to Viewport::Gizmo");
-
-                    if let Some(window) = &self.window {
-                        window.set_cursor_visible(true);
-                    }
-                } else {
-                    self.input_state.pressed_keys.insert(key);
-                }
-            }
-            KeyCode::KeyF => {
-                if self.is_viewport_focused && !is_playing {
-                    self.viewport_mode = crate::utils::ViewportMode::CameraMove;
-                    crate::info!("Switched to Viewport::CameraMove");
-                    if let Some(window) = &self.window {
-                        window.set_cursor_visible(false);
-
-                        let size = window.inner_size();
-                        let center = winit::dpi::PhysicalPosition::new(
-                            size.width as f64 / 2.0,
-                            size.height as f64 / 2.0,
-                        );
-                        let _ = window.set_cursor_position(center);
-                    }
-                } else {
-                    self.input_state.pressed_keys.insert(key);
-                }
-            }
-            KeyCode::Delete => {
-                if !is_playing {
-                    if let Some(_) = &self.selected_entity {
-                        self.signal = Signal::Delete;
-                    } else {
-                        crate::warn!("Failed to delete: No entity selected");
-                    }
-                } else {
-                    self.input_state.pressed_keys.insert(key);
-                }
-            }
-            KeyCode::Escape => {
-                if is_double_press {
-                    if let Some(_) = &self.selected_entity {
-                        self.selected_entity = None;
-                        log::debug!("Deselected entity");
-                    }
-                } else if self.is_viewport_focused && !is_playing {
-                    self.viewport_mode = crate::utils::ViewportMode::None;
-                    crate::info!("Switched to Viewport::None");
-                    if let Some(window) = &self.window {
-                        window.set_cursor_visible(true);
-                    }
-                } else {
-                    self.input_state.pressed_keys.insert(key);
-                }
-            }
-            KeyCode::KeyQ => {
-                if ctrl_pressed && !is_playing {
-                    match self.save_project_config() {
-                        Ok(_) => {}
-                        Err(e) => {
-                            crate::fatal!("Error saving project: {}", e);
-                        }
-                    }
-                    log::info!("Successfully saved project, about to quit...");
-                    crate::success_without_console!("Successfully saved project");
-                    self.scene_command = SceneCommand::Quit;
-                } else if is_playing {
-                    crate::warn!("Unable to save-quit project, please pause your playing state, then try again");
-                }
-            }
-            KeyCode::KeyC => {
-                if ctrl_pressed && !is_playing {
-                    if let Some(entity) = &self.selected_entity {
-                        let query = self
-                            .world
-                            .query_one::<(&AdoptedEntity, &Transform, &ModelProperties)>(*entity);
-                        if let Ok(mut q) = query {
-                            if let Some((e, t, props)) = q.get() {
-                                let s_entity = SceneEntity {
-                                    model_path: e.model().path.clone(),
-                                    label: e.model().label.clone(),
-                                    transform: *t,
-                                    properties: props.clone(),
-                                    script: None,
-                                    entity_id: None,
-                                };
-                                self.signal = Signal::Copy(s_entity);
-
-                                crate::info!("Copied!");
-
-                                log::debug!("Copied selected entity");
-                            } else {
-                                crate::warn!(
-                                    "Unable to copy entity: Unable to fetch world entity properties"
-                                );
-                            }
-                        } else {
-                            crate::warn!("Unable to copy entity: Unable to obtain lock");
-                        }
-                    } else {
-                        crate::warn!("Unable to copy entity: None selected");
-                    }
-                } else if matches!(self.viewport_mode, ViewportMode::Gizmo) {
-                    crate::info!("GizmoMode set to scale");
-                    self.gizmo_mode = GizmoMode::all_scale();
-                } else {
-                    self.input_state.pressed_keys.insert(key);
-                }
-            }
-            KeyCode::KeyV => {
-                if ctrl_pressed && !is_playing {
-                    match &self.signal {
-                        Signal::Copy(entity) => {
-                            self.signal = Signal::Paste(entity.clone());
-                        }
-                        _ => {
-                            crate::warn!("Unable to paste: You haven't selected anything!");
-                        }
-                    }
-                } 
-                else {
-                    self.input_state.pressed_keys.insert(key);
-                }
-            }
-            KeyCode::KeyS => {
-                if ctrl_pressed {
-                    if !is_playing {
-                        match self.save_project_config() {
-                            Ok(_) => {
-                                crate::success!("Successfully saved project");
-                            }
-                            Err(e) => {
-                                crate::fatal!("Error saving project: {}", e);
-                            }
-                        }
-                    } else {
-                        crate::warn!("Unable to save project config, please quit your playing and try again");
-                    }
-                    
-                } else {
-                    self.input_state.pressed_keys.insert(key);
-                }
-            }
-            KeyCode::KeyZ => {
-                if ctrl_pressed && !is_playing {
-                    if shift_pressed {
-                        // redo
-                    } else {
-                        // undo
-                        log::debug!("Undo signal sent");
-                        self.signal = Signal::Undo;
-                    }
-                } else if matches!(self.viewport_mode, ViewportMode::Gizmo) && !is_playing {
-                    crate::info!("GizmoMode set to translate");
-                    self.gizmo_mode = GizmoMode::all_translate();
-                } else {
-                    self.input_state.pressed_keys.insert(key);
-                }
-            }
-            KeyCode::F1 => {
-                if !is_playing {
-                    if self.is_using_debug_camera() {
-                        self.switch_to_player_camera();
-                    } else {
-                        self.switch_to_debug_camera();
-                    }
-                }
-            }
-            KeyCode::KeyX => {
-                if matches!(self.viewport_mode, ViewportMode::Gizmo) && !is_playing {
-                    crate::info!("GizmoMode set to rotate");
-                    self.gizmo_mode = GizmoMode::all_rotate();
-                } else {
-                    self.input_state.pressed_keys.insert(key);
-                }
-            }
-            KeyCode::KeyP => {
-                if !is_playing {
-                    if ctrl_pressed {
-                        self.signal = Signal::Play
-                    }
-                }
-            }
-            _ => {
-                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 Editor {
-    fn mouse_move(&mut self, position: PhysicalPosition<f64>) {
-        if (self.is_viewport_focused
-            && matches!(self.viewport_mode, crate::utils::ViewportMode::CameraMove))
-            || (matches!(self.editor_state, EditorState::Playing) && !self.input_state.is_cursor_locked)
-        {
-            if let Some(window) = &self.window {
-                let size = window.inner_size();
-                let center =
-                    PhysicalPosition::new(size.width as f64 / 2.0, size.height as f64 / 2.0);
-
-                let dx = position.x - center.x;
-                let dy = position.y - center.y;
-                if let Some(active_camera) = self.active_camera {
-                    if let Ok(mut q) = self.world.query_one::<(&mut Camera, &CameraComponent, Option<&CameraFollowTarget>)>(active_camera) {
-                        if let Some((camera, _, _)) = q.get() {
-                            camera.track_mouse_delta(dx, dy);
-                        } else {
-                            log_once::warn_once!("Unable to fetch the query result of camera: {:?}", active_camera)
-                        }
-                    } else {
-                        log_once::warn_once!("Unable to query camera, component and option<camerafollowtarget> for active camera: {:?}", active_camera);
-                    }
-                } else {
-                    log_once::warn_once!("No active camera found");
-                }
-
-                let _ = window.set_cursor_position(center);
-                window.set_cursor_visible(false);
-            }
-        }
-        self.input_state.mouse_pos = (position.x, position.y);
-    }
-
-    fn mouse_down(&mut self, button: MouseButton) {
-        match button {
-            _ => { self.input_state.mouse_button.insert(button); }
-        }
-    }
-
-    fn mouse_up(&mut self, button: MouseButton) {
-        self.input_state.mouse_button.remove(&button);
-    }
-}
-
-impl Controller for Editor {
-    fn button_down(&mut self, _button: Button, _id: GamepadId) {}
-
-    fn button_up(&mut self, _button: Button, _id: GamepadId) {}
-
-    fn left_stick_changed(&mut self, _x: f32, _y: f32, _id: GamepadId) {}
-
-    fn right_stick_changed(&mut self, _x: f32, _y: f32, _id: GamepadId) {}
-
-    fn on_connect(&mut self, _id: GamepadId) {}
-
-    fn on_disconnect(&mut self, _id: GamepadId) {}
-}
diff --git a/eucalyptus/src/editor/mod.rs b/eucalyptus/src/editor/mod.rs
deleted file mode 100644
index b21c026..0000000
--- a/eucalyptus/src/editor/mod.rs
+++ /dev/null
@@ -1,886 +0,0 @@
-pub mod dock;
-pub mod input;
-pub mod scene;
-pub mod component;
-
-pub(crate) use crate::editor::dock::*;
-
-use std::{
-    collections::{HashMap, HashSet},
-    path::PathBuf,
-    sync::{Arc, LazyLock},
-    time::{Duration, Instant},
-};
-
-use dropbear_engine::{
-    camera::Camera, entity::{AdoptedEntity, Transform}, graphics::Graphics, lighting::{Light, LightManager}, scene::SceneCommand
-};
-use egui::{self, Context};
-use egui_dock_fork::{DockArea, DockState, NodeIndex, Style};
-use hecs::{World};
-use log;
-use parking_lot::Mutex;
-use transform_gizmo_egui::{EnumSet, Gizmo, GizmoMode};
-use wgpu::{Color, Extent3d, RenderPipeline};
-use winit::{keyboard::KeyCode, window::Window};
-
-use crate::{build::build, camera::{
-    CameraAction, CameraComponent, CameraFollowTarget, CameraType, DebugCamera, UndoableCameraAction
-}, debug, scripting::{input::InputState, ScriptAction, ScriptManager}, states::{CameraConfig, EntityNode, LightConfig, ModelProperties, SceneEntity, ScriptComponent, PROJECT, SCENES}, utils::ViewportMode};
-
-pub struct Editor {
-    scene_command: SceneCommand,
-    world: World,
-    dock_state: DockState<EditorTab>,
-    texture_id: Option<egui::TextureId>,
-    size: Extent3d,
-    render_pipeline: Option<RenderPipeline>,
-    light_manager: LightManager,
-    color: Color,
-
-    active_camera: Option<hecs::Entity>,
-
-    is_viewport_focused: bool,
-    // is_cursor_locked: bool,
-    window: Option<Arc<Window>>,
-
-    show_new_project: bool,
-    project_name: String,
-    project_path: Option<PathBuf>,
-    pending_scene_switch: bool,
-
-    gizmo: Gizmo,
-    selected_entity: Option<hecs::Entity>,
-    viewport_mode: ViewportMode,
-
-    signal: Signal,
-    undo_stack: Vec<UndoableAction>,
-    // todo: add redo (later)
-    // redo_stack: Vec<UndoableAction>,
-
-    editor_state: EditorState,
-    gizmo_mode: EnumSet<GizmoMode>,
-
-    script_manager: ScriptManager,
-    play_mode_backup: Option<PlayModeBackup>,
-
-    input_state: InputState,
-}
-
-impl Editor {
-    pub fn new() -> Self {
-        let tabs = vec![EditorTab::Viewport];
-        let mut dock_state = DockState::new(tabs);
-
-        let surface = dock_state.main_surface_mut();
-        let [_old, right] =
-            surface.split_right(NodeIndex::root(), 0.25, vec![EditorTab::ModelEntityList]);
-        let [_old, _] =
-            surface.split_left(NodeIndex::root(), 0.20, vec![EditorTab::ResourceInspector]);
-        let [_old, _] = surface.split_below(right, 0.5, vec![EditorTab::AssetViewer]);
-
-        // this shit doesnt work :(
-        // nvm it works (sorta)
-        std::thread::spawn(move || {
-            loop {
-                std::thread::sleep(Duration::from_secs(1));
-                let deadlocks = parking_lot::deadlock::check_deadlock();
-                if deadlocks.is_empty() {
-                    continue;
-                }
-
-                log::error!("{} deadlocks detected", deadlocks.len());
-                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());
-                    }
-                }
-            }
-        });
-
-        Self {
-            scene_command: SceneCommand::None,
-            dock_state,
-            texture_id: None,
-            size: Extent3d::default(),
-            render_pipeline: None,
-            color: Color::default(),
-            is_viewport_focused: false,
-            // is_cursor_locked: false,
-            window: None,
-            world: World::new(),
-            show_new_project: false,
-            project_name: String::new(),
-            project_path: None,
-            pending_scene_switch: false,
-            gizmo: Gizmo::default(),
-            selected_entity: None,
-            viewport_mode: ViewportMode::None,
-            signal: Signal::None,
-            undo_stack: Vec::new(),
-            script_manager: ScriptManager::new().unwrap(),
-            editor_state: EditorState::Editing,
-            gizmo_mode: EnumSet::empty(),
-            play_mode_backup: None,
-            input_state: InputState::new(),
-            light_manager: LightManager::new(),
-            active_camera: None,
-            // ..Default::default()
-            // note to self: DO NOT USE ..DEFAULT::DEFAULT(), IT WILL CAUSE OVERFLOW
-        }
-    }
-
-    fn double_key_pressed(&mut self, key: KeyCode) -> bool {
-        let now = Instant::now();
-
-        if let Some(last_time) = self.input_state.last_key_press_times.get(&key) {
-            let time_diff = now.duration_since(*last_time);
-
-            if time_diff <= self.input_state.double_press_threshold {
-                self.input_state.last_key_press_times.remove(&key);
-                return true;
-            }
-        }
-
-        self.input_state.last_key_press_times.insert(key, now);
-        false
-    }
-
-    /// Save the current world state to the active scene
-    pub fn save_current_scene(&mut self) -> anyhow::Result<()> {
-        let mut scenes = SCENES.write().unwrap();
-
-        let scene_index = if scenes.is_empty() {
-            return Err(anyhow::anyhow!("No scenes loaded to save"));
-        } else {
-            0
-        };
-
-        let scene = &mut scenes[scene_index];
-        scene.entities.clear();
-        scene.lights.clear();
-        scene.cameras.clear();
-
-        for (id, (adopted, transform, properties, script)) in self
-            .world
-            .query::<(
-                &AdoptedEntity,
-                Option<&Transform>,
-                &ModelProperties,
-                Option<&ScriptComponent>,
-            )>()
-            .iter()
-        {
-            let transform = transform.unwrap_or(&Transform::default()).clone();
-
-            let scene_entity = SceneEntity {
-                model_path: adopted.model().path.clone(),
-                label: adopted.model().label.clone(),
-                transform,
-                properties: properties.clone(),
-                script: script.cloned(),
-                entity_id: Some(id),
-            };
-
-            scene.entities.push(scene_entity);
-            log::debug!("Pushed entity: {}", adopted.label());
-        }
-
-        for (id, (light_component, transform, light)) in self
-            .world
-            .query::<(
-                &dropbear_engine::lighting::LightComponent,
-                &Transform,
-                &Light,
-            )>()
-            .iter()
-        {
-            let light_config = LightConfig {
-                label: light.label().to_string(),
-                transform: *transform,
-                light_component: light_component.clone(),
-                enabled: light_component.enabled,
-                entity_id: Some(id),
-            };
-
-            scene.lights.push(light_config);
-            log::debug!("Pushed light into lights: {}", light.label());
-        }
-
-        for (_id, (camera, component, follow_target)) in self
-            .world
-            .query::<(&Camera, &CameraComponent, Option<&CameraFollowTarget>)>()
-            .iter() 
-        {
-            let camera_config = CameraConfig::from_ecs_camera(camera, component, follow_target);
-            scene.cameras.push(camera_config);
-            log::debug!("Pushed camera into cameras: {}", camera.label);
-        }
-
-
-        log::info!(
-            "Saved {} entities and camera configs to scene '{}'",
-            scene.entities.len(),
-            scene.scene_name
-        );
-
-        Ok(())
-    }
-
-    pub fn save_project_config(&mut self) -> anyhow::Result<()> {
-        self.save_current_scene()?;
-
-        {
-            let mut config = PROJECT.write().unwrap();
-            config.dock_layout = Some(self.dock_state.clone());
-        }
-
-        {
-            let (scene_clone, project_path) = {
-                let scenes = SCENES.read().unwrap();
-                let project = PROJECT.read().unwrap();
-                (scenes[0].clone(), project.project_path.clone())
-            };
-
-            scene_clone.write_to(&project_path)?;
-
-            let mut config = PROJECT.write().unwrap();
-            config.write_to_all()?;
-        }
-
-        Ok(())
-    }
-
-    pub fn load_project_config(&mut self, graphics: &Graphics) -> anyhow::Result<()> {
-        let config = PROJECT.read().unwrap();
-
-        self.project_path = Some(config.project_path.clone());
-
-        if let Some(layout) = &config.dock_layout {
-            self.dock_state = layout.clone();
-        }
-
-        {
-            let scenes = SCENES.read().unwrap();
-            if let Some(first_scene) = scenes.first() {
-                self.active_camera = Some(first_scene.load_into_world(&mut self.world, graphics)?);
-
-                log::info!(
-                    "Successfully loaded scene with {} entities and {} camera configs",
-                    first_scene.entities.len(),
-                    first_scene.cameras.len(),
-                );
-            } else {
-                let existing_debug_camera = self.world
-                    .query::<(&Camera, &CameraComponent)>()
-                    .iter()
-                    .find_map(|(entity, (_, component))| {
-                        if matches!(component.camera_type, CameraType::Debug) {
-                            Some(entity)
-                        } else {
-                            None
-                        }
-                    });
-
-                if let Some(camera_entity) = existing_debug_camera {
-                    log::info!("Using existing debug camera");
-                    self.active_camera = Some(camera_entity);
-                } else {
-                    log::info!("No scenes found, creating default debug camera");
-
-                    let debug_camera = Camera::predetermined(graphics, Some("Debug Camera"));
-                    let component = DebugCamera::new();
-
-                    let e = self.world.spawn((debug_camera, component));
-                    self.active_camera = Some(e);
-                }
-            }
-        }
-
-        Ok(())
-    }
-
-    pub fn show_ui(&mut self, ctx: &Context) {
-        egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
-            egui::MenuBar::new().ui(ui, |ui| {
-                ui.menu_button("File", |ui| {
-                    if ui
-                        .button("Main Menu (New + Open + Editor Settings)")
-                        .clicked()
-                    {
-                        self.scene_command = SceneCommand::SwitchScene("main_menu".into());
-                    }
-
-                    if ui.button("Save").clicked() {
-                        match self.save_project_config() {
-                            Ok(_) => {}
-                            Err(e) => {
-                                crate::fatal!("Error saving project: {}", e);
-                            }
-                        }
-                        crate::success!("Successfully saved project");
-                    }
-                    if ui.button("Project Settings").clicked() {};
-                    if matches!(self.editor_state, EditorState::Playing) {
-                        if ui.button("Stop").clicked() {
-                            self.signal = Signal::StopPlaying;
-                        }
-                    } else {
-                        if ui.button("Play").clicked() {
-                            self.signal = Signal::Play;
-                        }
-                    }
-                    ui.menu_button("Export", |ui| {
-                        // todo: create a window for better build menu
-                        if ui.button("Build").clicked() {
-                            {
-                                if let Ok(proj) = PROJECT.read() {
-                                    match build(proj.project_path.join(format!("{}.eucp", proj.project_name.clone())).clone()) {
-                                        Ok(thingy) => crate::success!("Project output at {}", thingy.display()),
-                                        Err(e) => {
-                                            crate::fatal!("Unable to build project [{}]: {}", proj.project_path.clone().display(), e);
-                                        },
-                                    }
-                                }
-                            }
-                        }
-                        ui.label("Package"); // todo: create a window for label
-                    });
-                    ui.separator();
-                    if ui.button("Quit").clicked() {
-                        match self.save_project_config() {
-                            Ok(_) => {}
-                            Err(e) => {
-                                crate::fatal!("Error saving project: {}", e);
-                            }
-                        }
-                        crate::success!("Successfully saved project");
-                    }
-                });
-                ui.menu_button("Edit", |ui| {
-                    if ui.button("Copy").clicked() {
-                        if let Some(entity) = &self.selected_entity {
-                            let query = self.world.query_one::<(&AdoptedEntity, &Transform, &ModelProperties)>(*entity);
-                            if let Ok(mut q) = query {
-                                if let Some((e, t, props)) = q.get() {
-                                    let s_entity = crate::states::SceneEntity {
-                                        model_path: e.model().path.clone(),
-                                        label: e.model().label.clone(),
-                                        transform: *t,
-                                        properties: props.clone(),
-                                        script: None,
-                                        entity_id: None,
-                                    };
-                                    self.signal = Signal::Copy(s_entity);
-
-                                    crate::info!("Copied selected entity!");
-                                } else {
-                                    crate::warn!("Unable to copy entity: Unable to fetch world entity properties");
-                                }
-                            } else {
-                                crate::warn!("Unable to copy entity: Unable to obtain lock");
-                            }
-                        } else {
-                            crate::warn!("Unable to copy entity: None selected");
-                        }
-                    }
-
-                    if ui.button("Paste").clicked() {
-                        match &self.signal {
-                            Signal::Copy(entity) => {
-                                self.signal = Signal::Paste(entity.clone());
-                            }
-                            _ => {
-                                crate::warn!("Unable to paste: You haven't selected anything!");
-                            }
-                        }
-                    }
-
-                    if ui.button("Undo").clicked() {
-                        self.signal = Signal::Undo;
-                    }
-                    ui.label("Redo");
-                });
-
-                ui.menu_button("Window", |ui_window| {
-                    if ui_window.button("Open Asset Viewer").clicked() {
-                        self.dock_state.push_to_focused_leaf(EditorTab::AssetViewer);
-                    }
-                    if ui_window.button("Open Resource Inspector").clicked() {
-                        self.dock_state
-                            .push_to_focused_leaf(EditorTab::ResourceInspector);
-                    }
-                    if ui_window.button("Open Entity List").clicked() {
-                        self.dock_state
-                            .push_to_focused_leaf(EditorTab::ModelEntityList);
-                    }
-                    if ui_window.button("Open Viewport").clicked() {
-                        self.dock_state.push_to_focused_leaf(EditorTab::Viewport);
-                    }
-                });
-                if let Ok(cfg) = PROJECT.read() {
-                    if cfg.editor_settings.is_debug_menu_shown {
-                        debug::show_menu_bar(ui, &mut self.signal);
-                    }
-                }
-            });
-        });
-
-        egui::CentralPanel::default().show(&ctx, |ui| {
-            DockArea::new(&mut self.dock_state)
-                .style(Style::from_egui(ui.style().as_ref()))
-                .show_inside(
-                    ui,
-                    &mut EditorTabViewer {
-                        view: self.texture_id.unwrap(),
-                        nodes: EntityNode::from_world(&self.world),
-                        gizmo: &mut self.gizmo,
-                        tex_size: self.size,
-                        world: &mut self.world,
-                        selected_entity: &mut self.selected_entity,
-                        viewport_mode: &mut self.viewport_mode,
-                        undo_stack: &mut self.undo_stack,
-                        signal: &mut self.signal,
-                        active_camera: &mut self.active_camera,
-                        gizmo_mode: &mut self.gizmo_mode,
-                        editor_mode: &mut self.editor_state,
-                    },
-                );
-        });
-
-        crate::utils::show_new_project_window(
-            ctx,
-            &mut self.show_new_project,
-            &mut self.project_name,
-            &mut self.project_path,
-            |name, path| {
-                crate::utils::start_project_creation(name.to_string(), Some(path.clone()));
-                self.pending_scene_switch = true;
-            },
-        );
-
-        if self.pending_scene_switch {
-            self.scene_command = SceneCommand::SwitchScene("editor".to_string());
-            self.pending_scene_switch = false;
-        }
-    }
-
-    pub fn switch_to_debug_camera(&mut self) {
-        let debug_camera = self.world.query::<(&Camera, &CameraComponent)>()
-            .iter()
-            .find_map(|(e, (_cam, comp))| {
-                if matches!(comp.camera_type, CameraType::Debug) {
-                    Some(e)
-                } else {
-                    None
-                }
-            });
-
-        if let Some(camera_entity) = debug_camera {
-            self.active_camera = Some(camera_entity);
-            crate::info!("Switched to debug camera");
-        } else {
-            crate::warn!("No debug camera found in the world");
-        }
-    }
-
-    pub fn switch_to_player_camera(&mut self) {
-        let player_camera = self.world.query::<(&Camera, &CameraComponent)>()
-            .iter()
-            .find_map(|(e, (_cam, comp))| {
-                if matches!(comp.camera_type, CameraType::Player) {
-                    Some(e)
-                } else {
-                    None
-                }
-            });
-
-        if let Some(camera_entity) = player_camera {
-            self.active_camera = Some(camera_entity);
-            crate::info!("Switched to player camera");
-        } else {
-            crate::warn!("No player camera found in the world");
-        }
-    }
-
-    pub fn is_using_debug_camera(&self) -> bool {
-        if let Some(active_camera_entity) = self.active_camera {
-            if let Ok(mut query) = self.world.query_one::<&CameraComponent>(active_camera_entity) {
-                if let Some(component) = query.get() {
-                    return matches!(component.camera_type, CameraType::Debug);
-                }
-            }
-        }
-        false
-    }
-}
-
-pub static LOGGED: LazyLock<Mutex<HashSet<String>>> = LazyLock::new(|| Mutex::new(HashSet::new()));
-
-fn show_entity_tree(
-    ui: &mut egui::Ui,
-    nodes: &mut Vec<EntityNode>,
-    selected: &mut Option<hecs::Entity>,
-    id_source: &str,
-) {
-    egui_dnd::Dnd::new(ui, id_source).show_vec(nodes, |ui, item, handle, _dragging| match item
-        .clone()
-    {
-        EntityNode::Entity { id, name } => {
-            ui.horizontal(|ui| {
-                handle.ui(ui, |ui| {
-                    ui.label("⏹️");
-                });
-                let resp = ui.selectable_label(selected.as_ref().eq(&Some(&id)), name);
-                if resp.clicked() {
-                    *selected = Some(id);
-                }
-            });
-        }
-        EntityNode::Script { name, path: _ } => {
-            ui.horizontal(|ui| {
-                handle.ui(ui, |ui| {
-                    ui.label("📜");
-                });
-                ui.label(format!("{name}"));
-            });
-        }
-        EntityNode::Group {
-            ref name,
-            ref mut children,
-            ref mut collapsed,
-        } => {
-            ui.horizontal(|ui| {
-                handle.ui(ui, |ui| {
-                    let header = egui::CollapsingHeader::new(name)
-                        .default_open(!*collapsed)
-                        .show(ui, |ui| {
-                            show_entity_tree(ui, children, selected, name);
-                        });
-                    *collapsed = !header.body_returned.is_some();
-                });
-            });
-        }
-        EntityNode::Light {
-            id,
-            name,
-        } => {
-            ui.horizontal(|ui| {
-                handle.ui(ui, |ui| {
-                    ui.label("💡");
-                });
-                let resp = ui.selectable_label(selected.as_ref().eq(&Some(&id)), name);
-                if resp.clicked() {
-                    *selected = Some(id);
-                }
-            });
-        }
-        EntityNode::Camera { id, name, camera_type } => {
-            ui.horizontal(|ui| {
-                handle.ui(ui, |ui| {
-                    let icon = match camera_type {
-                        CameraType::Debug => "🎥", // Debug camera
-                        CameraType::Player => "📹", // Player camera
-                        CameraType::Normal => "📷", // Normal camera
-                    };
-                    ui.label(icon);
-                });
-                let display_name = format!("{} ({})", name, match camera_type {
-                    CameraType::Debug => "Debug",
-                    CameraType::Player => "Player", 
-                    CameraType::Normal => "Normal",
-                });
-                let resp = ui.selectable_label(selected.as_ref().eq(&Some(&id)), display_name);
-                if resp.clicked() {
-                    *selected = Some(id);
-                }
-            });
-        }
-    });
-}
-
-/// Describes an action that is undoable
-#[derive(Debug)]
-pub enum UndoableAction {
-    Transform(hecs::Entity, Transform),
-    Spawn(hecs::Entity),
-    Label(hecs::Entity, String, EntityType),
-    RemoveComponent(hecs::Entity, ComponentType),
-    CameraAction(UndoableCameraAction),
-}
-#[derive(Debug)]
-pub enum EntityType {
-    Entity,
-    Light,
-    Camera,
-}
-
-impl UndoableAction {
-    pub fn push_to_undo(undo_stack: &mut Vec<UndoableAction>, action: Self) {
-        undo_stack.push(action);
-        // log::debug!("Undo Stack contents: {:#?}", undo_stack);
-    }
-
-    pub fn undo(&self, world: &mut hecs::World) -> anyhow::Result<()> {
-        match self {
-            UndoableAction::Transform(entity, transform) => {
-                        if let Ok(mut q) = world.query_one::<&mut Transform>(*entity) {
-                            if let Some(e_t) = q.get() {
-                                *e_t = *transform;
-                                log::debug!("Reverted transform");
-                                Ok(())
-                            } else {
-                                Err(anyhow::anyhow!("Unable to query the entity"))
-                            }
-                        } else {
-                            Err(anyhow::anyhow!("Could not find an entity to query"))
-                        }
-                    }
-            UndoableAction::Spawn(entity) => {
-                        if world.despawn(*entity).is_ok() {
-                            log::debug!("Undid spawn by despawning entity {:?}", entity);
-                            Ok(())
-                        } else {
-                            Err(anyhow::anyhow!("Failed to despawn entity {:?}", entity))
-                        }
-                    }
-            UndoableAction::Label(entity, original_label, entity_type) => {
-                match entity_type {
-                    EntityType::Entity => {
-                        if let Ok(mut q) = world.query_one::<&mut AdoptedEntity>(*entity) {
-                            if let Some(adopted) = q.get() {
-                                adopted.model_mut().label = original_label.clone();
-                                log::debug!("Reverted label for entity {:?} to '{}'", entity, original_label);
-                                Ok(())
-                            } else {
-                                Err(anyhow::anyhow!("Unable to query the entity for label revert"))
-                            }
-                        } else {
-                            Err(anyhow::anyhow!("Could not find an entity to query for label revert"))
-                        }
-                    },
-                    EntityType::Light => {
-                        if let Ok(mut q) = world.query_one::<&mut Light>(*entity) {
-                            if let Some(adopted) = q.get() {
-                                adopted.label = original_label.clone();
-                                log::debug!("Reverted label for light {:?} to '{}'", entity, original_label);
-                                Ok(())
-                            } else {
-                                Err(anyhow::anyhow!("Unable to query the light for label revert"))
-                            }
-                        } else {
-                            Err(anyhow::anyhow!("Could not find a light to query for label revert"))
-                        }
-                    },
-                    EntityType::Camera => {
-                        if let Ok(mut q) = world.query_one::<&mut Camera>(*entity) {
-                            if let Some(adopted) = q.get() {
-                                adopted.label = original_label.clone();
-                                log::debug!("Reverted label for camera {:?} to '{}'", entity, original_label);
-                                Ok(())
-                            } else {
-                                Err(anyhow::anyhow!("Unable to query the camera for label revert"))
-                            }
-                        } else {
-                            Err(anyhow::anyhow!("Could not find a camera to query for label revert"))
-                        }
-                    }
-                }
-            },
-            UndoableAction::RemoveComponent(entity, c_type) => {
-                match c_type {
-                    ComponentType::Script(component) => {
-                        world.insert_one(*entity, component.clone())?;
-                    },
-                    ComponentType::Camera(camera, component, follow) => {
-                        if let Some(f) = follow {
-                            world.insert(*entity, (camera.clone(), component.clone(), f.clone()))?;
-                        } else {
-                            world.insert(*entity, (camera.clone(), component.clone()))?;
-                        }
-                    }
-                }
-                Ok(())
-            },
-            UndoableAction::CameraAction(action) => {
-                match action {
-                    UndoableCameraAction::Speed(entity, speed) => {
-                        if let Ok((cam, comp)) = world.query_one_mut::<(&mut Camera, &mut CameraComponent)>(*entity) {
-                            comp.speed = *speed;
-                            comp.update(cam);
-                        }
-                    },
-                    UndoableCameraAction::Sensitivity(entity, sensitivity) => {
-                        if let Ok((cam, comp)) = world.query_one_mut::<(&mut Camera, &mut CameraComponent)>(*entity) {
-                            comp.sensitivity = *sensitivity;
-                            comp.update(cam);
-                        }
-                    },
-                    UndoableCameraAction::FOV(entity, fov) => {
-                        if let Ok((cam, comp)) = world.query_one_mut::<(&mut Camera, &mut CameraComponent)>(*entity) {
-                            comp.fov_y = *fov;
-                            comp.update(cam);
-                        }
-                    },
-                    UndoableCameraAction::Type(entity, camera_type) => {
-                        if let Ok((cam, comp)) = world.query_one_mut::<(&mut Camera, &mut CameraComponent)>(*entity) {
-                            comp.camera_type = *camera_type;
-                            comp.update(cam);
-                        }
-                    },
-                };
-                Ok(())
-            }
-        }
-    }
-}
-
-/// This enum will be used to describe the type of command/signal. This is only between
-/// the editor and unlike SceneCommand, this will ping a signal everywhere in that scene
-pub enum Signal {
-    None,
-    Copy(SceneEntity),
-    Paste(SceneEntity),
-    Delete,
-    Undo,
-    ScriptAction(ScriptAction),
-    CameraAction(CameraAction),
-    Play,
-    StopPlaying,
-    AddComponent(hecs::Entity, EntityType),
-    RemoveComponent(hecs::Entity, ComponentType),
-    CreateEntity,
-    LogEntities,
-}
-
-impl Default for Editor {
-    fn default() -> Self {
-        Editor::new()
-    }
-}
-
-#[derive(Debug)]
-pub enum ComponentType {
-    Script(ScriptComponent),
-    Camera(Camera, CameraComponent, Option<CameraFollowTarget>),
-}
-
-#[derive(Clone)]
-pub struct PlayModeBackup {
-    entities: Vec<(hecs::Entity, Transform, ModelProperties, Option<ScriptComponent>)>,
-    camera_data: Vec<(hecs::Entity, Camera, CameraComponent, Option<CameraFollowTarget>)>,
-}
-
-impl PlayModeBackup {
-    pub fn create_backup(editor: &mut Editor) -> anyhow::Result<()> {
-        let mut entities = Vec::new();
-        
-        for (entity_id, (_, transform, properties)) in editor
-            .world
-            .query::<(&AdoptedEntity, &Transform, &ModelProperties)>()
-            .iter()
-        {
-            let script = editor.world.query_one::<&ScriptComponent>(entity_id).ok().and_then(|mut s| {
-                s.get().map(|script| script.clone())
-            });
-            entities.push((entity_id, *transform, properties.clone(), script));
-        }
-
-        let mut camera_data = Vec::new();
-        
-        for (entity_id, (camera, component, follow_target)) in editor
-            .world
-            .query::<(&Camera, &CameraComponent, Option<&CameraFollowTarget>)>()
-            .iter()
-        {
-            camera_data.push((entity_id, camera.clone(), component.clone(), follow_target.cloned()));
-        }
-
-        editor.play_mode_backup = Some(PlayModeBackup {
-            entities,
-            camera_data,
-        });
-
-        log::info!("Created play mode backup with {} entities and {} cameras", 
-            editor.play_mode_backup.as_ref().unwrap().entities.len(),
-            editor.play_mode_backup.as_ref().unwrap().camera_data.len()
-        );
-        Ok(())
-    }
-
-    pub fn restore(editor: &mut Editor) -> anyhow::Result<()> {
-        if let Some(backup) = &editor.play_mode_backup {
-            // Restore entity states
-            for (entity_id, original_transform, original_properties, original_script) in &backup.entities {
-                if let Ok(mut transform) = editor.world.get::<&mut Transform>(*entity_id) {
-                    *transform = *original_transform;
-                }
-                
-                if let Ok(mut properties) = editor.world.get::<&mut ModelProperties>(*entity_id) {
-                    *properties = original_properties.clone();
-                }
-                
-                let has_script = editor.world.get::<&ScriptComponent>(*entity_id).is_ok();
-                match (has_script, original_script) {
-                    (true, Some(original)) => {
-                        if let Ok(mut script) = editor.world.get::<&mut ScriptComponent>(*entity_id) {
-                            *script = original.clone();
-                        }
-                    }
-                    (true, None) => {
-                        let _ = editor.world.remove_one::<ScriptComponent>(*entity_id);
-                    }
-                    (false, Some(original)) => {
-                        let _ = editor.world.insert_one(*entity_id, original.clone());
-                    }
-                    (false, None) => {
-                        // No change needed
-                    }
-                }
-            }
-
-            // Restore camera states
-            for (entity_id, original_camera, original_component, original_follow_target) in &backup.camera_data {
-                if let Ok(mut camera) = editor.world.get::<&mut Camera>(*entity_id) {
-                    *camera = original_camera.clone();
-                }
-                
-                if let Ok(mut component) = editor.world.get::<&mut CameraComponent>(*entity_id) {
-                    *component = original_component.clone();
-                }
-
-                let has_follow_target = editor.world.get::<&CameraFollowTarget>(*entity_id).is_ok();
-                match (has_follow_target, original_follow_target) {
-                    (true, Some(original)) => {
-                        if let Ok(mut follow_target) = editor.world.get::<&mut CameraFollowTarget>(*entity_id) {
-                            *follow_target = original.clone();
-                        }
-                    }
-                    (true, None) => {
-                        let _ = editor.world.remove_one::<CameraFollowTarget>(*entity_id);
-                    }
-                    (false, Some(original)) => {
-                        let _ = editor.world.insert_one(*entity_id, original.clone());
-                    }
-                    (false, None) => {
-                        // No change needed
-                    }
-                }
-            }
-
-            log::info!("Restored scene from play mode backup");
-            
-            editor.play_mode_backup = None;
-            Ok(())
-        } else {
-            Err(anyhow::anyhow!("No play mode backup found to restore"))
-        }
-    }
-}
-
-pub enum EditorState {
-    Editing,
-    Playing,
-}
\ No newline at end of file
diff --git a/eucalyptus/src/editor/scene.rs b/eucalyptus/src/editor/scene.rs
deleted file mode 100644
index ef39d76..0000000
--- a/eucalyptus/src/editor/scene.rs
+++ /dev/null
@@ -1,930 +0,0 @@
-use egui::{Align2, Image};
-use dropbear_engine::{
-    entity::{AdoptedEntity, Transform}, graphics::{Graphics, Shader}, lighting::{Light, LightComponent}, model::{DrawLight, DrawModel}, scene::{Scene, SceneCommand}
-};
-use log;
-use parking_lot::Mutex;
-use wgpu::Color;
-use wgpu::util::DeviceExt;
-use winit::{event_loop::ActiveEventLoop, keyboard::KeyCode};
-use dropbear_engine::graphics::InstanceRaw;
-use dropbear_engine::model::Model;
-use dropbear_engine::starter::plane::PlaneBuilder;
-use super::*;
-use crate::{
-    camera::PlayerCamera, utils::PendingSpawn
-};
-use crate::states::PropertyValue;
-use crate::utils::PROTO_TEXTURE;
-
-pub static PENDING_SPAWNS: LazyLock<Mutex<Vec<PendingSpawn>>> =
-    LazyLock::new(|| Mutex::new(Vec::new()));
-
-impl Scene for Editor {
-    fn load(&mut self, graphics: &mut Graphics) {
-        if self.active_camera.is_none() {
-            self.load_project_config(graphics).unwrap();
-        }
-
-        let shader = Shader::new(
-            graphics,
-            include_str!("../shader.wgsl"),
-            Some("viewport_shader"),
-        );
-
-        self.light_manager.create_light_array_resources(graphics);
-
-        let texture_bind_group = &graphics.texture_bind_group().clone();
-        if let Some(active_camera) = self.active_camera {
-            if let Ok(mut q) = self.world.query_one::<(&Camera, &CameraComponent, Option<&CameraFollowTarget>)>(active_camera) {
-                if let Some((camera, _component, _follow_target)) = q.get() {
-                    let pipeline = graphics.create_render_pipline(
-                        &shader,
-                        vec![
-                            texture_bind_group,
-                            camera.layout(),
-                            self.light_manager.layout()
-                        ],
-                        None,
-                    );
-                    self.render_pipeline = Some(pipeline);
-
-                    self.light_manager.create_render_pipeline(
-                        graphics,
-                        include_str!("../light.wgsl"),
-                        camera,
-                        Some("Light Pipeline")
-                    );
-                } else {
-                    log_once::warn_once!("Unable to fetch the query result of camera: {:?}", active_camera)
-                }
-            } else {
-                log_once::warn_once!("Unable to query camera, component and option<camerafollowtarget> for active camera: {:?}", active_camera);
-            }
-        } else {
-            log_once::warn_once!("No active camera found");
-        }
-
-        self.window = Some(graphics.state.window.clone());
-    }
-
-    fn update(&mut self, dt: f32, graphics: &mut Graphics) {
-        if let Some((_, tab)) = self.dock_state.find_active_focused() {
-            self.is_viewport_focused = matches!(tab, EditorTab::Viewport);
-        } else {
-            self.is_viewport_focused = false;
-        }
-
-        {
-            let mut pending_spawns = PENDING_SPAWNS.lock();
-            for spawn in pending_spawns.drain(..) {
-                match AdoptedEntity::new(graphics, &spawn.asset_path, Some(&spawn.asset_name)) {
-                    Ok(adopted) => {
-                        let entity_id =
-                            self.world
-                                .spawn((adopted, spawn.transform, spawn.properties));
-                        self.selected_entity = Some(entity_id);
-
-                        UndoableAction::push_to_undo(
-                            &mut self.undo_stack,
-                            UndoableAction::Spawn(entity_id),
-                        );
-
-                        log::info!(
-                            "Successfully spawned {} with ID {:?}",
-                            spawn.asset_name,
-                            entity_id
-                        );
-                    }
-                    Err(e) => {
-                        log::error!("Failed to spawn {}: {}", spawn.asset_name, e);
-                    }
-                }
-            }
-        }
-
-        if matches!(self.editor_state, EditorState::Playing) {
-            if self.input_state.pressed_keys.contains(&KeyCode::Escape) {
-                self.signal = Signal::StopPlaying;
-            }
-
-            let mut script_entities = Vec::new();
-            for (entity_id, script) in self.world.query::<&mut ScriptComponent>().iter() {
-                log_once::debug_once!("Script Entity -> id: {:?}, component: {:?}", entity_id, script);
-                script.name = script.path.file_name().unwrap().to_str().unwrap().to_string();
-                script_entities.push((entity_id, script.name.clone()));
-            }
-
-            if script_entities.is_empty() {
-                log_once::warn_once!("Script entities is empty");
-            }
-
-            for (entity_id, script_name) in script_entities {
-                if let Err(e) = self.script_manager.update_entity_script(entity_id, &script_name, &mut self.world, &self.input_state, dt) {
-                    log_once::warn_once!("Failed to update script '{}' for entity {:?}: {}", script_name, entity_id, e);
-                }
-            }
-        }
-
-        if self.is_viewport_focused
-            && matches!(self.viewport_mode, crate::utils::ViewportMode::CameraMove)
-        // && self.is_using_debug_camera()
-        {
-            let movement_keys: std::collections::HashSet<KeyCode> = self
-                .input_state
-                .pressed_keys
-                .iter()
-                .filter(|&&key| {
-                    matches!(
-                        key,
-                        KeyCode::KeyW
-                            | KeyCode::KeyA
-                            | KeyCode::KeyS
-                            | KeyCode::KeyD
-                            | KeyCode::Space
-                            | KeyCode::ShiftLeft
-                    )
-                })
-                .copied()
-                .collect();
-
-            // Handle camera input through ECS
-            if let Some(active_camera) = self.active_camera {
-                if let Ok(mut query) = self.world.query_one::<(&mut Camera, &CameraComponent)>(active_camera) {
-                    if let Some((camera, component)) = query.get() {
-                        // Handle keyboard input based on camera type
-                        match component.camera_type {
-                            CameraType::Debug => {
-                                DebugCamera::handle_keyboard_input(camera, &movement_keys);
-                                DebugCamera::handle_mouse_input(camera, component, self.input_state.mouse_delta);
-                            }
-                            CameraType::Player => {
-                                PlayerCamera::handle_keyboard_input(camera, &movement_keys);
-                                PlayerCamera::handle_mouse_input(camera, component, self.input_state.mouse_delta);
-                            }
-                            CameraType::Normal => {
-                                // Handle normal camera input if needed
-                                DebugCamera::handle_keyboard_input(camera, &movement_keys);
-                                DebugCamera::handle_mouse_input(camera, component, self.input_state.mouse_delta);
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        match &self.signal {
-            Signal::Paste(scene_entity) => {
-                        match AdoptedEntity::new(
-                            graphics,
-                            &scene_entity.model_path.to_project_path(self.project_path.clone().unwrap()).unwrap(),
-                            Some(&scene_entity.label),
-                        ) {
-                            Ok(adopted) => {
-                                let entity_id = self.world.spawn((
-                                    adopted,
-                                    scene_entity.transform,
-                                    ModelProperties::default(),
-                                ));
-                                self.selected_entity = Some(entity_id);
-                                log::debug!(
-                                    "Successfully paste-spawned {} with ID {:?}",
-                                    scene_entity.label,
-                                    entity_id
-                                );
-
-                                crate::success_without_console!("Paste!");
-                                self.signal = Signal::Copy(scene_entity.clone());
-                            }
-                            Err(e) => {
-                                crate::warn!("Failed to paste-spawn {}: {}", scene_entity.label, e);
-                            }
-                        }
-                    }
-            Signal::Delete => {
-                        if let Some(sel_e) = &self.selected_entity {
-                            let is_viewport_cam = if let Ok(mut q) = self.world.query_one::<&CameraComponent>(*sel_e) { if let Some(c) = q.get() { if matches!(c.camera_type, CameraType::Debug) { true } else { false } } else { false } } else { false };
-                            if is_viewport_cam {
-                                crate::warn!("You can't delete the viewport camera");
-                                self.signal = Signal::None;
-                            } else {
-                                match self.world.despawn(*sel_e) {
-                                    Ok(_) => {
-                                        crate::info!("Decimated entity");
-                                        self.signal = Signal::None;
-                                    }
-                                    Err(e) => {
-                                        crate::warn!("Failed to delete entity: {}", e);
-                                        self.signal = Signal::None;
-                                    }
-                                }
-                            }
-                        }
-                    }
-            Signal::Undo => {
-                        if let Some(action) = self.undo_stack.pop() {
-                            match action.undo(&mut self.world) {
-                                Ok(_) => {
-                                    crate::info!("Undid action");
-                                }
-                                Err(e) => {
-                                    crate::warn!("Failed to undo action: {}", e);
-                                }
-                            }
-                        } else {
-                            crate::warn_without_console!("Nothing to undo");
-                            log::debug!("No undoable actions in stack");
-                        }
-                        self.signal = Signal::None;
-                    }
-            Signal::None => {}
-            Signal::Copy(_) => {}
-            Signal::ScriptAction(action) => match action {
-                        ScriptAction::AttachScript {
-                            script_path,
-                            script_name,
-                        } => {
-                            if let Some(selected_entity) = self.selected_entity {
-                                match crate::scripting::move_script_to_src(script_path) {
-                                    Ok(moved_path) => {
-                                        let new_script = ScriptComponent {
-                                            name: script_name.clone(),
-                                            path: moved_path.clone(),
-                                        };
-
-                                        let replaced = if let Ok(mut sc) =
-                                            self.world.get::<&mut ScriptComponent>(selected_entity)
-                                        {
-                                            sc.name = new_script.name.clone();
-                                            sc.path = new_script.path.clone();
-                                            true
-                                        } else {
-                                            match crate::scripting::attach_script_to_entity(
-                                                &mut self.world,
-                                                selected_entity,
-                                                new_script.clone(),
-                                            ) {
-                                                Ok(_) => false,
-                                                Err(e) => {
-                                                    crate::fatal!(
-                                                        "Failed to attach script to entity {:?}: {}",
-                                                        selected_entity,
-                                                        e
-                                                    );
-                                                    self.signal = Signal::None;
-                                                    return;
-                                                }
-                                            }
-                                        };
-
-                                        if let Err(e) = crate::scripting::convert_entity_to_group(
-                                            &self.world,
-                                            selected_entity,
-                                        ) {
-                                            log::warn!("convert_entity_to_group failed (non-fatal): {}", e);
-                                        }
-
-                                        crate::success!(
-                                            "{} script '{}' at {} to entity {:?}",
-                                            if replaced { "Reattached" } else { "Attached" },
-                                            script_name,
-                                            moved_path.display(),
-                                            selected_entity
-                                        );
-                                    }
-                                    Err(e) => {
-                                        crate::fatal!("Move failed: {}", e);
-                                    }
-                                }
-                            } else {
-                                crate::fatal!("AttachScript requested but no entity is selected");
-                            }
-
-                            self.signal = Signal::None;
-                        }
-                        ScriptAction::CreateAndAttachScript {
-                            script_path,
-                            script_name,
-                        } => {
-                            if let Some(selected_entity) = self.selected_entity {
-                                let new_script = ScriptComponent {
-                                    name: script_name.clone(),
-                                    path: script_path.clone(),
-                                };
-
-                                let replaced = if let Ok(mut sc) =
-                                    self.world.get::<&mut ScriptComponent>(selected_entity)
-                                {
-                                    sc.name = new_script.name.clone();
-                                    sc.path = new_script.path.clone();
-                                    true
-                                } else {
-                                    match crate::scripting::attach_script_to_entity(
-                                        &mut self.world,
-                                        selected_entity,
-                                        new_script.clone(),
-                                    ) {
-                                        Ok(_) => false,
-                                        Err(e) => {
-                                            crate::fatal!("Failed to attach new script: {}", e);
-                                            self.signal = Signal::None;
-                                            return;
-                                        }
-                                    }
-                                };
-
-                                if let Err(e) =
-                                    crate::scripting::convert_entity_to_group(&self.world, selected_entity)
-                                {
-                                    log::warn!("convert_entity_to_group failed (non-fatal): {}", e);
-                                }
-
-                                crate::success!(
-                                    "{} new script '{}' at {} to entity {:?}",
-                                    if replaced { "Replaced" } else { "Attached" },
-                                    script_name,
-                                    script_path.display(),
-                                    selected_entity
-                                );
-                            } else {
-                                crate::warn_without_console!("No selected entity to attach new script");
-                                log::warn!("CreateAndAttachScript requested but no entity is selected");
-                            }
-                            self.signal = Signal::None;
-                        }
-                        ScriptAction::RemoveScript => {
-                            if let Some(selected_entity) = self.selected_entity {
-                                if let Ok(script) = self.world.remove_one::<ScriptComponent>(selected_entity) {
-                                    crate::success!("Removed script from entity {:?}", selected_entity);
-
-                                    if let Err(e) = crate::scripting::convert_entity_to_group(
-                                        &self.world,
-                                        selected_entity,
-                                    ) {
-                                        log::warn!("convert_entity_to_group failed (non-fatal): {}", e);
-                                    }
-                                    log::debug!("Pushing remove component to undo stack");
-                                    UndoableAction::push_to_undo(&mut self.undo_stack, UndoableAction::RemoveComponent(selected_entity, ComponentType::Script(script)));
-                                } else {
-                                    crate::warn!(
-                                        "No script component found on entity {:?}",
-                                        selected_entity
-                                    );
-                                }
-                            } else {
-                                crate::warn!("No entity selected to remove script from");
-                            }
-
-                            self.signal = Signal::None;
-                        }
-                        ScriptAction::EditScript => {
-                            if let Some(selected_entity) = self.selected_entity {
-                                if let Ok(mut q) = self.world.query_one::<&ScriptComponent>(selected_entity)
-                                {
-                                    if let Some(script) = q.get() {
-                                        match open::that(script.path.clone()) {
-                                            Ok(()) => {
-                                                crate::success!("Opened {}", script.name)
-                                            }
-                                            Err(e) => {
-                                                crate::warn!("Error while opening {}: {}", script.name, e);
-                                            }
-                                        }
-                                    }
-                                } else {
-                                    crate::warn!(
-                                        "No script component found on entity {:?}",
-                                        selected_entity
-                                    );
-                                }
-                            } else {
-                                crate::warn!("No entity selected to edit script");
-                            }
-                            self.signal = Signal::None;
-                        }
-            },
-            Signal::Play => {
-                // Check if a player camera target exists
-                let has_player_camera_target = self.world
-                    .query::<(&Camera, &CameraComponent, &CameraFollowTarget)>()
-                    .iter()
-                    .any(|(_, (_, comp, _))| matches!(comp.camera_type, CameraType::Player));
-
-                if has_player_camera_target {
-                    if let Err(e) = PlayModeBackup::create_backup(self) {
-                        crate::fatal!("Failed to create play mode backup: {}", e);
-                        self.signal = Signal::None;
-                        return;
-                    }
-
-                    self.editor_state = EditorState::Playing;
-
-                    self.switch_to_player_camera();
-
-                    let mut script_entities = Vec::new();
-                    for (entity_id, script) in self.world.query::<&ScriptComponent>().iter() {
-                        script_entities.push((entity_id, script.clone()));
-                    }
-
-                    for (entity_id, script) in script_entities {
-                        log::debug!("Initialising entity script [{}] from path: {}", script.name, script.path.display());
-                        match self.script_manager.load_script(&script.path) {
-                            Ok(script_name) => {
-                                if let Err(e) = self.script_manager.init_entity_script(entity_id, &script_name, &mut self.world, &self.input_state) {
-                                    log::warn!("Failed to initialise script '{}' for entity {:?}: {}", script.name, entity_id, e);
-                                    self.signal = Signal::StopPlaying;
-                                } else {
-                                    crate::success_without_console!("You are in play mode now! Press Escape to exit");
-                                    log::info!("You are in play mode now! Press Escape to exit");
-                                }
-                            }
-                            Err(e) => {
-                                // todo: proper error menu
-                                crate::fatal!("Failed to load script '{}': {}", script.name, e);
-                                self.signal = Signal::StopPlaying;
-                            }
-                        }
-                    }
-                } else {
-                    crate::fatal!("Unable to build: Player camera not attached to an entity");
-                }
-
-                self.signal = Signal::None;
-            }
-            Signal::StopPlaying => {
-                if let Err(e) = PlayModeBackup::restore(self) {
-                    crate::warn!("Failed to restore from play mode backup: {}", e);
-                    log::warn!("Failed to restore scene state: {}", e);
-                }
-
-                self.editor_state = EditorState::Editing;
-
-                self.switch_to_debug_camera();
-
-                for (entity_id, _) in self.world.query::<&ScriptComponent>().iter() {
-                    self.script_manager.remove_entity_script(entity_id);
-                }
-
-                crate::success!("Exited play mode");
-                log::info!("Back to the editor you go...");
-
-                self.signal = Signal::None;
-            },
-            Signal::CameraAction(action) => match action {
-                CameraAction::SetPlayerTarget { entity, offset } => {
-                    // Find player camera and add/update CameraFollowTarget component
-                    let player_camera = self.world
-                        .query::<(&Camera, &CameraComponent)>()
-                        .iter()
-                        .find_map(|(e, (_, comp))| {
-                            if matches!(comp.camera_type, CameraType::Player) {
-                                Some(e)
-                            } else {
-                                None
-                            }
-                        });
-
-                    if let Some(camera_entity) = player_camera {
-                        let mut follow_target = (false, CameraFollowTarget::default());
-                        // Find the target entity label
-                        if let Ok(mut query) = self.world.query_one::<&AdoptedEntity>(*entity) {
-                            if let Some(adopted) = query.get() {
-                                follow_target = (true, CameraFollowTarget {
-                                    follow_target: adopted.label().to_string(),
-                                    offset: *offset,
-                                });
-                            }
-                        }
-
-                        if follow_target.0 {
-                            let _ = self.world.insert_one(camera_entity, follow_target);
-                            crate::info!("Set player camera target to entity {:?}", entity);
-                        }
-                    }
-                    self.signal = Signal::None;
-                }
-                CameraAction::ClearPlayerTarget => {
-                    let player_camera = self.world
-                        .query::<(&Camera, &CameraComponent)>()
-                        .iter()
-                        .find_map(|(e, (_, comp))| {
-                            if matches!(comp.camera_type, CameraType::Player) {
-                                Some(e)
-                            } else {
-                                None
-                            }
-                        });
-
-                    if let Some(camera_entity) = player_camera {
-                        let _ = self.world.remove_one::<CameraFollowTarget>(camera_entity);
-                    }
-                    crate::info!("Cleared player camera target");
-                    self.signal = Signal::None;
-                }
-            },
-            Signal::AddComponent(entity, e_type) => {
-                match e_type {
-                    EntityType::Entity => {
-                        if let Ok(e) = self.world.query_one_mut::<&AdoptedEntity>(*entity) {
-                            let mut local_signal: Option<Signal> = None;
-                            let label = e.label().clone();
-                            let mut show = true;
-                            egui::Window::new(format!("Add component for {}", label))
-                                .title_bar(true)
-                                .open(&mut show)
-                                .scroll([false, true])
-                                .anchor(Align2::CENTER_CENTER, [0.0, 0.0])
-                                .enabled(true)
-                                .show(&graphics.get_egui_context(), |ui| {
-                                if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Scripting")).clicked() {
-                                    log::debug!("Adding scripting component to entity [{}]", label);
-                                    if let Err(e) = self.world.insert_one(*entity, ScriptComponent::default()) {
-                                        crate::warn!("Failed to add scripting component to entity: {}", e);
-                                    } else {
-                                        crate::success!("Added the scripting component");
-                                    }
-                                    local_signal = Some(Signal::None);
-                                }
-                                if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Camera")).clicked() {
-                                    log::debug!("Adding camera component to entity [{}]", label);
-                                    
-                                    let has_camera = self.world.query_one::<(&Camera, &CameraComponent)>(*entity).is_ok();
-                                    
-                                    if has_camera {
-                                        crate::warn!("Entity [{}] already has a camera component", label);
-                                    } else {
-                                        let camera = Camera::predetermined(graphics, Some(&format!("{} Camera", label)));
-                                        let component = CameraComponent::new();
-                                        
-                                        if let Err(e) = self.world.insert(*entity, (camera, component)) {
-                                            crate::warn!("Failed to add camera component to entity: {}", e);
-                                        } else {
-                                            crate::success!("Added the camera component");
-                                        }
-                                    }
-                                    local_signal = Some(Signal::None);
-                                }
-                            });
-                            if !show {
-                                self.signal = Signal::None;
-                            }
-                            if let Some(signal) = local_signal {
-                                self.signal = signal
-                            }
-                        } else {
-                            log_once::warn_once!("Failed to add component to entity: no entity component found");
-                        }
-                    }
-                    EntityType::Light => {
-                        if let Ok(light) = self.world.query_one_mut::<&Light>(*entity) {
-                            let mut show = true;
-                            egui::Window::new(format!("Add component for {}", light.label))
-                                .scroll([false, true])
-                                .anchor(Align2::CENTER_CENTER, [0.0, 0.0])
-                                .enabled(true)
-                                .open(&mut show)
-                                .title_bar(true)
-                                .show(&graphics.get_egui_context(), |ui| {
-                                    if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Scripting")).clicked() {
-                                        log::debug!("Adding scripting component to light [{}]", light.label);
-
-                                        crate::success!("Added the scripting component to light [{}]", light.label);
-                                        self.signal = Signal::None;
-                                    }
-                                });
-                            if !show {
-                                self.signal = Signal::None;
-                            }
-                        } else {
-                            log_once::warn_once!("Failed to add component to light: no light component found");
-                        }
-                    },
-                    EntityType::Camera => {
-                        if let Ok((cam, _comp)) = self.world.query_one_mut::<(&Camera, &CameraComponent)>(*entity) {
-                            let mut show = true;
-                            egui::Window::new(format!("Add component for {}", cam.label))
-                                .scroll([false, true])
-                                .anchor(Align2::CENTER_CENTER, [0.0, 0.0])
-                                .enabled(true)
-                                .open(&mut show)
-                                .title_bar(true)
-                                .show(&graphics.get_egui_context(), |ui| {
-                                    egui_extras::install_image_loaders(ui.ctx());
-                                    ui.add(Image::from_bytes("bytes://theres_nothing", include_bytes!("../../../resources/theres_nothing.jpg")));
-                                    ui.label("Theres nothing...");
-                                    // // scripting
-                                    // if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Scripting")).clicked() {
-                                    //     log::debug!("Adding scripting component to camera [{}]", cam.label);
-
-                                    //     crate::success!("Added the scripting component to camera [{}]", cam.label);
-                                    //     self.signal = Signal::None;
-                                    // }
-                                });
-                            if !show {
-                                self.signal = Signal::None;
-                            }
-                        } else {
-                            log_once::warn_once!("Failed to add component to light: no light component found");
-                        }
-                    }
-                }
-            },
-            Signal::RemoveComponent(entity, c_type) => {
-                match c_type {
-                    ComponentType::Script(_) => {
-                        match self.world.remove_one::<ScriptComponent>(*entity) {
-                            Ok(component) => {
-                                crate::success!("Removed script component from entity {:?}", entity);
-                                UndoableAction::push_to_undo(&mut self.undo_stack, UndoableAction::RemoveComponent(*entity, ComponentType::Script(component)));
-                            }
-                            Err(e) => {
-                                crate::warn!("Failed to remove script component from entity: {}", e);
-                            }
-                        };
-                        self.signal = Signal::None;
-                    },
-                    ComponentType::Camera(_, _, follow) => {
-                        if let Some(_) = follow {
-                            match self.world.remove::<(Camera, CameraComponent, CameraFollowTarget)>(*entity) {
-                                Ok(component) => {
-                                    crate::success!("Removed camera component from entity {:?}", entity);
-                                    UndoableAction::push_to_undo(&mut self.undo_stack, UndoableAction::RemoveComponent(*entity, ComponentType::Camera(component.0, component.1, Some(component.2))));
-                                }
-                                Err(e) => {
-                                    crate::warn!("Failed to remove camera component from entity: {}", e);
-                                }
-                            };
-                        } else {
-                            match self.world.remove::<(Camera, CameraComponent)>(*entity) {
-                                Ok(component) => {
-                                    crate::success!("Removed camera component from entity {:?}", entity);
-                                    UndoableAction::push_to_undo(&mut self.undo_stack, UndoableAction::RemoveComponent(*entity, ComponentType::Camera(component.0, component.1, None)));
-                                }
-                                Err(e) => {
-                                    crate::warn!("Failed to remove script component from entity: {}", e);
-                                }
-                            };
-                        }
-                    }
-                }
-            }
-            Signal::CreateEntity => {
-                // self.show_add_entity_window = true;
-                let mut show = true;
-                egui::Window::new("Add Entity")
-                    .scroll([false, true])
-                    .anchor(Align2::CENTER_CENTER, [0.0, 0.0])
-                    .enabled(true)
-                    .open(&mut show)
-                    .title_bar(true)
-                    .show(&graphics.get_egui_context(), |ui| {
-                        if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Model")).clicked() {
-                            log::debug!("Creating new model");
-                            crate::warn!("Instead of using the `Add Entity` window, double click on the imported model in the asset \n\
-                            viewer to import a new model, then tweak the settings to how you wish after!");
-                            self.signal = Signal::None;
-                        }
-
-                        if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Light")).clicked() {
-                            log::debug!("Creating new lighting");
-                            let transform = Transform::new();
-                            let component = LightComponent::default();
-                            let light = Light::new(graphics, &component, &transform, Some("Light"));
-                            self.world.spawn((light, component, transform));
-                            crate::success!("Created new light");
-
-                            // always ensure the signal is reset after action is dun
-                            self.signal = Signal::None;
-                        }
-
-                        if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Plane")).clicked() {
-                            log::debug!("Creating new plane");
-                            let plane = PlaneBuilder::new()
-                                .with_size(500.0, 200.0)
-                                .build(
-                                    graphics,
-                                    PROTO_TEXTURE,
-                                    Some("Plane")
-                                ).unwrap();
-                            let transform = Transform::new();
-                            let mut props = ModelProperties::new();
-                            props.custom_properties.insert("width".to_string(), PropertyValue::Float(500.0));
-                            props.custom_properties.insert("height".to_string(), PropertyValue::Float(200.0));
-                            props.custom_properties.insert("tiles_x".to_string(), PropertyValue::Int(500));
-                            props.custom_properties.insert("tiles_z".to_string(), PropertyValue::Int(200));
-                            self.world.spawn((plane, transform, props));
-                            crate::success!("Created new plane");
-
-                            self.signal = Signal::None;
-                        }
-
-                        if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Cube")).clicked() {
-                            log::debug!("Creating new cube");
-                            let model = Model::load_from_memory(
-                                graphics,
-                                include_bytes!("../../../resources/cube.obj").to_vec(),
-                                Some("Cube")
-                            );
-                            match model {
-                                Ok(model) => {
-                                    let cube = AdoptedEntity::adopt(
-                                        graphics,
-                                        model,
-                                        Some("Cube")
-                                    );
-                                    self.world.spawn((cube, Transform::new(), ModelProperties::new()));
-                                }
-                                Err(e) => {
-                                    crate::fatal!("Failed to load cube model: {}", e);
-                                }
-                            }
-                            crate::success!("Created new cube");
-
-                            self.signal = Signal::None;
-                        }
-
-                        if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Cube")).clicked() {
-                            log::debug!("Creating new cube");
-                            let model = Model::load_from_memory(
-                                graphics,
-                                include_bytes!("../../../resources/cube.obj").to_vec(),
-                                Some("Cube")
-                            );
-                            match model {
-                                Ok(model) => {
-                                    let cube = AdoptedEntity::adopt(
-                                        graphics,
-                                        model,
-                                        Some("Cube")
-                                    );
-                                    self.world.spawn((cube, Transform::new(), ModelProperties::new()));
-                                }
-                                Err(e) => {
-                                    crate::fatal!("Failed to load cube model: {}", e);
-                                }
-                            }
-                            crate::success!("Created new cube");
-
-                            self.signal = Signal::None;
-                        }
-
-                        if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Camera")).clicked() {
-                            log::debug!("Creating new cube");
-                            let camera = Camera::predetermined(graphics, None);
-                            let component = CameraComponent::new();
-                            self.world.spawn((camera, component));
-                            crate::success!("Created new camera");
-
-                            self.signal = Signal::None;
-                        }
-                    });
-                if !show {
-                    self.signal = Signal::None;
-                }
-            },
-            Signal::LogEntities => {
-                log::info!("====================");
-                for entity in self.world.iter() {
-                    if let Some(entity) = entity.get::<&AdoptedEntity>() {
-                        log::info!("Model: {:?}", entity.label());
-                    }
-
-                    if let Some(entity) = entity.get::<&Light>() {
-                        log::info!("Light: {:?}", entity.label());
-                    }
-                }
-                log::info!("====================");
-                self.signal = Signal::None;
-            }
-        }
-
-        let current_size = graphics.state.viewport_texture.size;
-        self.size = current_size;
-        
-        let new_aspect = current_size.width as f64 / current_size.height as f64;
-        if let Some(active_camera) = self.active_camera {
-            if let Ok(mut query) = self.world.query_one::<&mut Camera>(active_camera) {
-                if let Some(camera) = query.get() {
-                    camera.aspect = new_aspect;
-                }
-            }
-        }
-
-        for (_entity_id, (camera, _component, follow_target)) in self
-            .world
-            .query::<(&mut Camera, &CameraComponent, Option<&CameraFollowTarget>)>()
-            .iter()
-        {
-            if let Some(target) = follow_target {
-                for (_target_entity_id, (adopted, transform)) in self
-                    .world
-                    .query::<(&AdoptedEntity, &Transform)>()
-                    .iter()
-                {
-                    if adopted.label() == &target.follow_target {
-                        let target_pos = transform.position;
-                        camera.eye = target_pos + target.offset;
-                        camera.target = target_pos;
-                        break;
-                    }
-                }
-            }
-        }
-
-        for (_entity_id, (camera, component)) in self.world.query::<(&mut Camera, &mut CameraComponent)>().iter() {
-            component.update(camera);
-            camera.update(graphics);
-        }
-
-        let query = self.world.query_mut::<(&mut AdoptedEntity, &Transform)>();
-        for (_, (entity, transform)) in query {
-            entity.update(&graphics, transform);
-        }
-
-        let light_query = self.world.query_mut::<(&mut LightComponent, &Transform, &mut Light)>();
-        for (_, (light_component, transform, light)) in light_query {
-            light.update(light_component, transform);
-        }
-
-        self.light_manager.update(graphics, &self.world);
-    }
-
-    fn render(&mut self, graphics: &mut Graphics) {
-        // cornflower blue
-        let color = Color {
-            r: 100.0 / 255.0,
-            g: 149.0 / 255.0,
-            b: 237.0 / 255.0,
-            a: 1.0,
-        };
-
-        self.color = color.clone();
-        self.size = graphics.state.viewport_texture.size.clone();
-        self.texture_id = Some(graphics.state.texture_id.clone());
-        self.show_ui(&graphics.get_egui_context());
-
-        self.window = Some(graphics.state.window.clone());
-        crate::logging::render(&graphics.get_egui_context());
-        if let Some(pipeline) = &self.render_pipeline {
-            if let Some(active_camera) = self.active_camera {
-                if let Ok(mut query) = self.world.query_one::<&Camera>(active_camera) {
-                    if let Some(camera) = query.get() {
-                        let mut light_query = self.world.query::<(&Light, &LightComponent)>();
-                        let mut entity_query = self.world.query::<(&AdoptedEntity, &Transform)>();
-                        {
-                            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 light_query.iter() {
-                                    if component.enabled {
-                                        render_pass.set_vertex_buffer(1, light.instance_buffer.as_ref().unwrap().slice(..));
-                                        render_pass.draw_light_model(
-                                            light.model(),
-                                            camera.bind_group(),
-                                            light.bind_group(),
-                                        );
-                                    }
-                                }
-                            }
-
-                            let mut model_batches: HashMap<*const Model, Vec<InstanceRaw>> = HashMap::new();
-
-                            for (_, (entity, _)) in entity_query.iter() {
-                                let model_ptr = entity.model() as *const Model;
-                                let instance_raw = entity.instance.to_raw();
-                                model_batches.entry(model_ptr).or_insert(Vec::new()).push(instance_raw);
-                            }
-
-                            render_pass.set_pipeline(pipeline);
-
-                            for (model_ptr, instances) in model_batches {
-                                let model = unsafe { &*model_ptr };
-
-                                let instance_buffer = graphics.state.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
-                                    label: Some("Batched Instance Buffer"),
-                                    contents: bytemuck::cast_slice(&instances),
-                                    usage: wgpu::BufferUsages::VERTEX,
-                                });
-
-                                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) {}
-
-    fn run_command(&mut self) -> SceneCommand {
-        std::mem::replace(&mut self.scene_command, SceneCommand::None)
-    }
-}
\ No newline at end of file
diff --git a/eucalyptus/src/lib.rs b/eucalyptus/src/lib.rs
deleted file mode 100644
index 0526262..0000000
--- a/eucalyptus/src/lib.rs
+++ /dev/null
@@ -1,21 +0,0 @@
-#[cfg(feature = "editor")]
-pub mod editor;
-#[cfg(feature = "editor")]
-pub mod menu;
-#[cfg(feature = "editor")]
-pub mod build;
-#[cfg(feature = "editor")]
-pub mod debug;
-
-
-pub mod camera;
-pub mod logging;
-pub mod scripting;
-pub mod states;
-pub mod utils;
-
-#[cfg(feature = "editor")]
-pub const APP_INFO: app_dirs2::AppInfo = app_dirs2::AppInfo {
-    name: "Eucalyptus",
-    author: "4tkbytes",
-};
\ No newline at end of file
diff --git a/eucalyptus/src/light.wgsl b/eucalyptus/src/light.wgsl
deleted file mode 100644
index 57bedfa..0000000
--- a/eucalyptus/src/light.wgsl
+++ /dev/null
@@ -1,61 +0,0 @@
-// Shader for rendering the white cube for lighting (or diff depending on colour)
-
-struct Camera {
-    view_pos: vec4<f32>,
-    view_proj: mat4x4<f32>,
-}
-@group(0) @binding(0)
-var<uniform> camera: Camera;
-
-struct Light {
-    position: vec4<f32>,
-    direction: vec4<f32>, // x, y, z, outer_cutoff_angle
-    color: vec4<f32>, // r, g, b, light_type (0, 1, 2)
-    constant: f32,
-    lin: f32,
-    quadratic: f32,
-    cutoff: f32,
-}
-
-@group(1) @binding(0)
-var<uniform> light: Light;
-
-struct InstanceInput {
-    @location(5) model_matrix_0: vec4<f32>,
-    @location(6) model_matrix_1: vec4<f32>,
-    @location(7) model_matrix_2: vec4<f32>,
-    @location(8) model_matrix_3: vec4<f32>,
-}
-
-struct VertexInput {
-    @location(0) position: vec3<f32>,
-};
-
-struct VertexOutput {
-    @builtin(position) clip_position: vec4<f32>,
-    @location(0) color: vec3<f32>,
-};
-
-@vertex
-fn vs_main(
-    model: VertexInput,
-    instance: InstanceInput,
-) -> VertexOutput {
-    let model_matrix = mat4x4<f32>(
-        instance.model_matrix_0,
-        instance.model_matrix_1,
-        instance.model_matrix_2,
-        instance.model_matrix_3,
-    );
-    var out: VertexOutput;
-    out.clip_position = camera.view_proj * model_matrix * vec4<f32>(model.position, 1.0);
-    out.color = light.color.xyz;
-    return out;
-}
-
-// Fragment shader
-
-@fragment
-fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
-    return vec4<f32>(in.color, 1.0);
-}
\ No newline at end of file
diff --git a/eucalyptus/src/logging.rs b/eucalyptus/src/logging.rs
deleted file mode 100644
index ebed13e..0000000
--- a/eucalyptus/src/logging.rs
+++ /dev/null
@@ -1,242 +0,0 @@
-//! This crate will allow for logging to specific locations. Essentially, just removing boilerplate
-//!
-//! # Supported logging locations:
-//! - Toasts (egui)
-//! - Console
-//! - File (to be implemented)
-
-#[cfg(feature = "editor")]
-use egui::Context;
-
-#[cfg(feature = "editor")]
-use egui_toast_fork::Toasts;
-
-#[cfg(feature = "editor")]
-use once_cell::sync::Lazy;
-#[cfg(feature = "editor")]
-use parking_lot::Mutex;
-
-#[cfg(feature = "editor")]
-pub static GLOBAL_TOASTS: Lazy<Mutex<Toasts>> = Lazy::new(|| {
-    Mutex::new(
-        Toasts::new()
-            .anchor(egui::Align2::RIGHT_BOTTOM, (-10.0, -10.0))
-            .direction(egui::Direction::BottomUp),
-    )
-});
-
-/// Renders the toasts. Requires an egui context.
-///
-/// Useful when paired with a function that contains [`crate`]
-#[cfg(feature = "editor")]
-pub(crate) fn render(context: &Context) {
-    let mut toasts = GLOBAL_TOASTS.lock();
-    toasts.show(context);
-}
-
-/// Fatal log macro
-///
-/// This is useful for when there is a fatal error like a missing file cannot be found.
-///
-/// This macro creates a toast under the [`egui_toast_fork::ToastKind::Error`] and logs
-/// with [`log::error!`]
-#[macro_export]
-macro_rules! fatal {
-    ($($arg:tt)*) => {{
-        let _msg = format!($($arg)*);
-        log::error!("{}", _msg);
-
-        #[cfg(feature = "editor")]
-        {
-            use egui_toast_fork::{Toast, ToastKind};
-            use crate::logging::GLOBAL_TOASTS;
-            let mut toasts = GLOBAL_TOASTS.lock();
-            toasts.add(Toast {
-                text: _msg.into(),
-                kind: ToastKind::Error,
-                options: egui_toast_fork::ToastOptions::default()
-                    .duration_in_seconds(3.0)
-                    .show_progress(true),
-                style: egui_toast_fork::ToastStyle::default(),
-            });
-        }
-    }};
-}
-
-/// Success log macro
-///
-/// This is useful for when loading a save is successful.
-///
-/// This macro creates a toast under the [`egui_toast_fork::ToastKind::Success`] and logs
-/// with [`log::info!`]
-#[macro_export]
-macro_rules! success {
-    ($($arg:tt)*) => {{
-        let _msg = format!($($arg)*);
-        log::debug!("{}", _msg);
-
-        #[cfg(feature = "editor")]
-        {
-            use egui_toast_fork::{Toast, ToastKind};
-            use crate::logging::GLOBAL_TOASTS;
-            let mut toasts = GLOBAL_TOASTS.lock();
-            toasts.add(Toast {
-                    text: _msg.into(),
-                    kind: ToastKind::Success,
-                    options: egui_toast_fork::ToastOptions::default()
-                        .duration_in_seconds(3.0)
-                        .show_progress(true),
-                    style: egui_toast_fork::ToastStyle::default(),
-                });
-            };
-        }
-    };
-}
-
-/// Warn log macro
-///
-/// This is useful for when there is a non-fatal error like unable to copy.
-///
-/// This macro creates a toast under the [`egui_toast_fork::ToastKind::Warning`] and logs
-/// with [`log::warn!`]
-#[macro_export]
-macro_rules! warn {
-    ($($arg:tt)*) => {{
-        let _msg = format!($($arg)*);
-        log::warn!("{}", _msg);
-
-        #[cfg(feature = "editor")]
-        {
-            use egui_toast_fork::{Toast, ToastKind};
-            use crate::logging::GLOBAL_TOASTS;
-            let mut toasts = GLOBAL_TOASTS.lock();
-            toasts.add(Toast {
-                    text: _msg.into(),
-                    kind: ToastKind::Warning,
-                    options: egui_toast_fork::ToastOptions::default()
-                        .duration_in_seconds(3.0)
-                        .show_progress(true),
-                    style: egui_toast_fork::ToastStyle::default(),
-                });
-        }
-    }};
-}
-
-/// Info log macro
-///
-/// This is useful for notifying the user of a change, where it doesn't have to be important.
-///
-/// This macro creates a toast under the [`egui_toast_fork::ToastKind::Info`] and logs
-/// with [`log::debug!`]
-#[macro_export]
-macro_rules! info {
-    ($($arg:tt)*) => {{
-        let _msg = format!($($arg)*);
-        log::debug!("{}", _msg);
-
-        #[cfg(feature = "editor")]
-        {
-            use egui_toast_fork::{Toast, ToastKind};
-            use crate::logging::GLOBAL_TOASTS;
-            let mut toasts = GLOBAL_TOASTS.lock();
-            toasts.add(Toast {
-                text: _msg.into(),
-                kind: ToastKind::Info,
-                options: egui_toast_fork::ToastOptions::default()
-                    .duration_in_seconds(1.0)
-                    .show_progress(false),
-                style: egui_toast_fork::ToastStyle::default(),
-            });
-        }
-    }};
-}
-
-/// Macro for logging info without the console
-///
-/// This macro should be "info_toast", however in the case that I ever need to add some more functionality,
-/// this would be useful.
-///
-/// Its feature-heavy counterpart would be [`crate::success!`].
-///
-/// It creates a toast under [`egui_toast_fork::ToastKind::Info`].
-#[macro_export]
-macro_rules! info_without_console {
-    ($($arg:tt)*) => {
-        let _msg = format!($($arg)*);
-
-        #[cfg(feature = "editor")]
-        {
-            use egui_toast_fork::{Toast, ToastKind};
-            use crate::logging::GLOBAL_TOASTS;
-            let mut toasts = GLOBAL_TOASTS.lock();
-            toasts.add(Toast {
-                text: _msg.into(),
-                kind: ToastKind::Info,
-                options: egui_toast_fork::ToastOptions::default()
-                    .duration_in_seconds(1.0)
-                    .show_progress(false),
-                style: egui_toast_fork::ToastStyle::default(),
-            });
-        }
-    };
-}
-
-/// Macro for logging a successful action without the console
-///
-/// This macro should be "success_toast", however in the case that I ever need to add some more functionality,
-/// this would be useful.
-///
-/// Its feature-heavy counterpart would be [`crate::success!`].
-///
-/// It creates a toast under [`egui_toast_fork::ToastKind::Success`].
-#[macro_export]
-macro_rules! success_without_console {
-    ($($arg:tt)*) => {
-        let _msg = format!($($arg)*);
-
-        #[cfg(feature = "editor")]
-        {
-            use egui_toast_fork::{Toast, ToastKind};
-            use crate::logging::GLOBAL_TOASTS;
-            let mut toasts = GLOBAL_TOASTS.lock();
-            toasts.add(Toast {
-                text: _msg.into(),
-                kind: ToastKind::Success,
-                options: egui_toast_fork::ToastOptions::default()
-                    .duration_in_seconds(3.0)
-                    .show_progress(true),
-                style: egui_toast_fork::ToastStyle::default(),
-            });
-        }
-    };
-}
-
-/// Macro for logging a successful action without the console
-///
-/// This macro should be "success_toast", however in the case that I ever need to add some more functionality,
-/// this would be useful.
-///
-/// Its feature-heavy counterpart would be [`crate::warn!`].
-///
-/// It creates a toast under [`egui_toast_fork::ToastKind::Warning`].
-#[macro_export]
-macro_rules! warn_without_console {
-    ($($arg:tt)*) => {
-        let _msg = format!($($arg)*);
-
-        #[cfg(feature = "editor")]
-        {
-            use egui_toast_fork::{Toast, ToastKind};
-            use crate::logging::GLOBAL_TOASTS;
-            let mut toasts = GLOBAL_TOASTS.lock();
-            toasts.add(Toast {
-                text: _msg.into(),
-                kind: ToastKind::Warning,
-                options: egui_toast_fork::ToastOptions::default()
-                    .duration_in_seconds(3.0)
-                    .show_progress(true),
-                style: egui_toast_fork::ToastStyle::default(),
-            });
-        }
-    };
-}
diff --git a/eucalyptus/src/main.rs b/eucalyptus/src/main.rs
deleted file mode 100644
index 133d6e9..0000000
--- a/eucalyptus/src/main.rs
+++ /dev/null
@@ -1,181 +0,0 @@
-#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
-
-#[cfg(feature = "editor")]
-use std::{cell::RefCell, rc::Rc, fs, path::PathBuf};
-#[cfg(feature = "editor")]
-use clap::{Arg, Command};
-
-#[cfg(feature = "editor")]
-use dropbear_engine::{WindowConfiguration, scene};
-use eucalyptus::APP_INFO;
-
-// #[tokio::main]
-#[cfg(feature = "editor")]
-// async 
-fn main() -> anyhow::Result<()> {
-    println!("Editor feature: {}", cfg!(feature = "editor"));
-    println!("Data-only feature: {}", cfg!(feature = "data-only"));
-    
-    if cfg!(feature = "data-only") {
-        panic!("You are using the data-only feature while compiled with the editor feature? Huh?");
-    }
-    #[cfg(target_os = "android")]
-    compile_error!("The `editor` feature is not supported on Android. If you are attempting\
- to use the Eucalyptus editor on Android, please don't. Instead, use the `data-only` feature\
- to use with dependencies or create your own game on Desktop. Sorry :(");
-    let matches = Command::new("eucalyptus")
-        .about("A visual game editor")
-        .version("1.0.0")
-        .subcommand_required(false)
-        .arg_required_else_help(false)
-        .subcommand(
-            Command::new("build")
-                .about("Build a eucalyptus project, but only the .eupak file and its resources")
-                .arg(
-                    Arg::new("project")
-                        .help("Path to the .eucp project file")
-                        .value_name("PROJECT_FILE")
-                        .required(false),
-                ),
-        )
-        .subcommand(
-            Command::new("package")
-                .about("Package a eucalyptus project, which compiles the runtime and the resource .eupak file")
-                .arg(
-                    Arg::new("project")
-                        .help("Path to the .eucp project file")
-                        .value_name("PROJECT_FILE")
-                        .required(false),
-                ),
-        )
-        .subcommand(Command::new("read")
-            .about("Reads and displays the contents of a .eupak file for debugging")
-            .arg(
-                    Arg::new("eupak_file")
-                        .help("Path to the .eupak file")
-                        .value_name("RESOURCE_FILE")
-                        .required(false),
-                ),
-        )
-        .subcommand(Command::new("health").about("Check the health of the eucalyptus installation"))
-        .get_matches();
-
-    match matches.subcommand() {
-        Some(("build", sub_matches)) => {
-            let project_path = match sub_matches.get_one::<String>("project") {
-                Some(path) => PathBuf::from(path),
-                None => match find_eucp_file() {
-                    Ok(path) => path,
-                    Err(e) => {
-                        eprintln!("Error: {}", e);
-                        std::process::exit(1);
-                    }
-                },
-            };
-
-            eucalyptus::build::build(project_path)?;
-        }
-        Some(("package", sub_matches)) => {
-            let project_path = match sub_matches.get_one::<String>("project") {
-                Some(path) => PathBuf::from(path),
-                None => match find_eucp_file() {
-                    Ok(path) => path,
-                    Err(e) => {
-                        eprintln!("Error: {}", e);
-                        std::process::exit(1);
-                    }
-                },
-            };
-
-            eucalyptus::build::package(project_path, sub_matches)?;
-        }
-        Some(("health", _)) => {
-            eucalyptus::build::health()?;
-        }
-        Some(("read", sub_matches)) => {
-            let project_path = match sub_matches.get_one::<String>("eupak_file") {
-                Some(path) => PathBuf::from(path),
-                None => match find_eucp_file() {
-                    Ok(path) => path,
-                    Err(e) => {
-                        eprintln!("Error: {}", e);
-                        std::process::exit(1);
-                    }
-                },
-            };
-
-            eucalyptus::build::read_from_eupak(project_path)?;
-        }
-        None => {
-            let config = WindowConfiguration {
-                title: "Eucalyptus, built with dropbear".into(),
-                windowed_mode: dropbear_engine::WindowedModes::Maximised,
-                max_fps: dropbear_engine::App::NO_FPS_CAP,
-                app_info: APP_INFO,
-            };
-
-            let _app = dropbear_engine::run_app!(config, |mut scene_manager, mut input_manager| {
-                let main_menu = Rc::new(RefCell::new(eucalyptus::menu::MainMenu::new()));
-                let editor = Rc::new(RefCell::new(eucalyptus::editor::Editor::new()));
-
-                scene::add_scene_with_input(
-                    &mut scene_manager,
-                    &mut input_manager,
-                    main_menu,
-                    "main_menu",
-                );
-                scene::add_scene_with_input(
-                    &mut scene_manager,
-                    &mut input_manager,
-                    editor,
-                    "editor",
-                );
-
-                scene_manager.switch("main_menu");
-
-                (scene_manager, input_manager)
-            })
-            .unwrap();
-        }
-        _ => unreachable!(),
-    }
-    Ok(())
-}
-
-#[cfg(not(feature = "editor"))]
-fn main() {
-    compile_error!("\n\nYou have not enabled the \"editor\" feature, therefore cannot use the eucalyptus editor. 
-Either import as a lib to use its structs and enums or enable the editor feature or enable the \"editor\" feature or just the default features\n\n");
-}
-
-#[cfg(all(feature = "editor", feature = "data-only"))]
-compile_error!("Features `editor` and `data-only` cannot be enabled at the same time");
-
-
-#[cfg(feature = "editor")]
-fn find_eucp_file() -> Result<PathBuf, String> {
-    let current_dir = std::env::current_dir().map_err(|_| "Failed to get current directory")?;
-
-    let entries = fs::read_dir(&current_dir).map_err(|_| "Failed to read current directory")?;
-
-    let mut eucp_files = Vec::new();
-
-    for entry in entries {
-        if let Ok(entry) = entry {
-            if let Some(file_name) = entry.file_name().to_str() {
-                if file_name.ends_with(".eucp") {
-                    eucp_files.push(entry.path());
-                }
-            }
-        }
-    }
-
-    match eucp_files.len() {
-        0 => Err("No .eucp files found in current directory".to_string()),
-        1 => Ok(eucp_files[0].clone()),
-        _ => Err(format!(
-            "Multiple .eucp files found: {:#?}. Please specify which one to use.",
-            eucp_files
-        )),
-    }
-}
diff --git a/eucalyptus/src/menu.rs b/eucalyptus/src/menu.rs
deleted file mode 100644
index e49ca9a..0000000
--- a/eucalyptus/src/menu.rs
+++ /dev/null
@@ -1,384 +0,0 @@
-use std::{
-    fs,
-    sync::mpsc::{self, Receiver},
-};
-
-use anyhow::anyhow;
-use dropbear_engine::{
-    input::{Controller, Keyboard, Mouse},
-    scene::{Scene, SceneCommand},
-};
-use egui::{self, FontId, Frame, RichText};
-use egui_toast_fork::{ToastOptions, Toasts};
-use gilrs;
-use git2::Repository;
-use log::{self, debug};
-use winit::{
-    dpi::PhysicalPosition, event::MouseButton, event_loop::ActiveEventLoop, keyboard::KeyCode,
-};
-
-use crate::states::{PROJECT, ProjectConfig};
-
-#[derive(Default)]
-pub struct MainMenu {
-    scene_command: SceneCommand,
-    show_new_project: bool,
-    project_name: String,
-    project_path: Option<std::path::PathBuf>,
-    project_error: Option<Vec<String>>,
-
-    progress_rx: Option<Receiver<ProjectProgress>>,
-
-    show_progress: bool,
-    progress: f32,
-    progress_message: String,
-    toast: Toasts,
-    is_in_file_dialogue: bool,
-}
-
-pub enum ProjectProgress {
-    Step {
-        progress: f32,
-        message: String,
-    },
-    #[allow(dead_code)]
-    Error(String),
-    Done,
-}
-
-impl MainMenu {
-    pub fn new() -> Self {
-        Self {
-            show_progress: false,
-            toast: Toasts::new()
-                .anchor(egui::Align2::RIGHT_BOTTOM, (-10.0, -10.0))
-                .direction(egui::Direction::BottomUp),
-            ..Default::default()
-        }
-    }
-
-    fn start_project_creation(&mut self) {
-        let (tx, rx) = mpsc::channel();
-        let project_name = self.project_name.clone();
-        let project_path = self.project_path.clone();
-
-        self.progress_rx = Some(rx);
-        self.show_progress = true;
-        self.progress = 0.0;
-        self.progress_message = "Starting project creation...".to_string();
-
-        std::thread::spawn(move || {
-            let mut errors = Vec::new();
-            let folders = [
-                ("git", 0.1, "Creating a git folder..."),
-                ("src", 0.2, "Creating src folder..."),
-                ("resources/models", 0.3, "Creating models folder..."),
-                ("resources/shaders", 0.4, "Creating shader folder..."),
-                ("resources/textures", 0.5, "Creating textures folder..."),
-                ("src2", 0.6, "Creating project config file..."),
-                ("scenes", 0.7, "Creating the scenes folder"),
-            ];
-
-            if let Some(path) = &project_path {
-                for (folder, progress, message) in folders {
-                    tx.send(ProjectProgress::Step {
-                        progress,
-                        message: message.to_string(),
-                    })
-                    .ok();
-
-                    let full_path = path.join(folder);
-                    let result: anyhow::Result<()> = if folder == "src" {
-                        if !full_path.exists() {
-                            fs::create_dir(&full_path)
-                                .map_err(|e| anyhow::anyhow!(e))
-                                .map(|_| ())
-                        } else {
-                            Ok(())
-                        }
-                    } else if folder == "git" {
-                        match Repository::init(path) {
-                            Ok(_) => Ok(()),
-                            Err(e) => {
-                                if matches!(e.code(), git2::ErrorCode::Exists) {
-                                    log::warn!("Git repository already exists");
-                                    Ok(())
-                                } else {
-                                    Err(anyhow!(e))
-                                }
-                            }
-                        }
-                    } else if folder == "src2" {
-                        if let Some(path) = &project_path {
-                            let mut config = ProjectConfig::new(project_name.clone(), &path);
-                            let _ = config.write_to_all();
-                            let mut global = PROJECT.write().unwrap();
-                            *global = config;
-                            Ok(())
-                        } else {
-                            Err(anyhow!("Project path not found"))
-                        }
-                    } else {
-                        if !full_path.exists() {
-                            fs::create_dir_all(&full_path)
-                                .map_err(|e| anyhow!(e))
-                                .map(|_| ())
-                        } else {
-                            log::warn!("{:?} already exists", full_path);
-                            Ok(())
-                        }
-                    };
-                    if let Err(e) = result {
-                        tx.send(ProjectProgress::Error(e.to_string())).ok();
-                        errors.push(e);
-                    }
-                }
-                tx.send(ProjectProgress::Step {
-                    progress: 1.0,
-                    message: "Project creation complete!".to_string(),
-                })
-                .ok();
-
-                tx.send(ProjectProgress::Done).ok();
-            }
-        });
-
-        log::debug!("Starting project creation");
-    }
-}
-
-impl Scene for MainMenu {
-    fn load(&mut self, _graphics: &mut dropbear_engine::graphics::Graphics) {
-        log::info!("Loaded menu scene");
-    }
-
-    fn update(&mut self, _dt: f32, _graphics: &mut dropbear_engine::graphics::Graphics) {}
-
-    fn render(&mut self, graphics: &mut dropbear_engine::graphics::Graphics) {
-        let screen_size: (f32, f32) = (
-            graphics.state.window.inner_size().width as f32 - 100.0,
-            graphics.state.window.inner_size().height as f32 - 100.0,
-        );
-        let egui_ctx = graphics.get_egui_context();
-
-        egui::CentralPanel::default()
-                .frame(Frame::new())
-                .show(&egui_ctx, |ui| {
-                    ui.vertical_centered(|ui| {
-                        ui.add_space(64.0);
-                        ui.label(RichText::new("Eucalyptus").font(FontId::proportional(32.0)));
-                        ui.add_space(40.0);
-
-                        let button_size = egui::vec2(300.0, 60.0);
-
-                        if ui
-                            .add_sized(button_size, egui::Button::new("New Project"))
-                            .clicked()
-                        {
-                            log::debug!("Creating new project");
-                            self.show_new_project = true;
-                        }
-                        ui.add_space(20.0);
-
-                        if ui
-                            .add_sized(button_size, egui::Button::new("Open Project"))
-                            .clicked()
-                        {
-                            log::debug!("Opening project");
-                            self.is_in_file_dialogue = true;
-                            if let Some(path) = rfd::FileDialog::new()
-                                .add_filter("Eucalyptus Configuration Files", &["eucp"])
-                                .pick_file()
-                            {
-                                match ProjectConfig::read_from(&path) {
-                                    Ok(config) => {
-                                        log::info!("Loaded project!");
-                                        let mut global = PROJECT.write().unwrap();
-                                        *global = config;
-                                        // println!("Loaded config info: {:#?}", global);
-                                        self.scene_command =
-                                            SceneCommand::SwitchScene(String::from("editor"));
-                                    }
-                                    Err(e) => if e.to_string().contains("missing field") {
-                                        self.toast.add(egui_toast_fork::Toast {
-                                            kind: egui_toast_fork::ToastKind::Error,
-                                            text: format!("Your project version is not up to date with the current project version").into(),
-                                            options: ToastOptions::default()
-                                                .duration_in_seconds(10.0)
-                                                .show_progress(true),
-                                            ..Default::default()
-                                        });
-                                        log::error!("Failed to load project: {}", e);
-                                    }
-                                };
-                            } else {
-                                log::warn!("File dialog returned \"None\"");
-                            }
-                            self.is_in_file_dialogue = false;
-                        }
-                        ui.add_space(20.0);
-
-                        if ui
-                            .add_sized(button_size, egui::Button::new("Settings"))
-                            .clicked()
-                        {
-                            log::debug!("Settings (not implemented)");
-                        }
-                        ui.add_space(20.0);
-
-                        if ui
-                            .add_sized(button_size, egui::Button::new("Quit"))
-                            .clicked()
-                        {
-                            self.scene_command = SceneCommand::Quit
-                        }
-                        ui.add_space(20.0);
-                    });
-                });
-
-        let mut show_new_project = self.show_new_project;
-        egui::Window::new("Create new project")
-            .open(&mut show_new_project)
-            .resizable(true)
-            .collapsible(false)
-            .fixed_size(screen_size)
-            .show(&egui_ctx, |ui| {
-                ui.vertical(|ui| {
-                    ui.heading("Project Name:");
-                    ui.add_space(5.0);
-
-                    ui.text_edit_singleline(&mut self.project_name);
-                    ui.add_space(10.0);
-
-                    ui.heading("Project Location: ");
-                    ui.add_space(5.0);
-
-                    if let Some(ref path) = self.project_path {
-                        ui.label(format!("Chosen location: {}", path.display()));
-                        ui.add_space(5.0);
-                    }
-
-                    ui.add_space(5.0);
-                    if ui.button("Choose Location").clicked() {
-                        self.is_in_file_dialogue = true;
-                        if let Some(path) = rfd::FileDialog::new()
-                            .set_title("Save Project")
-                            .set_file_name(&self.project_name)
-                            .pick_folder()
-                        {
-                            self.project_path = Some(path);
-                            log::debug!("Project will be saved at: {:?}", self.project_path);
-                        }
-                        self.is_in_file_dialogue = false;
-                    }
-
-                    let can_create = self.project_path.is_some() && !self.project_name.is_empty();
-                    if ui
-                        .add_enabled(can_create, egui::Button::new("Create Project"))
-                        .clicked()
-                    {
-                        log::info!("Creating new project at {:?}", self.project_path);
-                        self.start_project_creation();
-                        ui.ctx().request_repaint();
-                    }
-                });
-            });
-        self.show_new_project = show_new_project;
-
-        if let Some(rx) = self.progress_rx.as_mut() {
-            while let Ok(progress) = rx.try_recv() {
-                match progress {
-                    ProjectProgress::Step { progress, message } => {
-                        self.progress = progress;
-                        self.progress_message = message;
-                    }
-                    ProjectProgress::Error(err) => {
-                        self.project_error.get_or_insert_with(Vec::new).push(err);
-                    }
-                    ProjectProgress::Done if self.project_error.is_none() => {
-                        self.is_in_file_dialogue = false;
-                        self.show_new_project = false;
-                        self.show_progress = false;
-                        self.scene_command = SceneCommand::SwitchScene("editor".to_string());
-                    }
-                    ProjectProgress::Done => {}
-                }
-            }
-        }
-
-        if self.show_progress {
-            egui::Window::new("Creating Project...")
-                .collapsible(true)
-                .resizable(false)
-                .fixed_size([400.0, 120.0])
-                .show(&egui_ctx, |ui| {
-                    ui.label(&self.progress_message);
-                    ui.add_space(10.0);
-
-                    ui.add(egui::ProgressBar::new(self.progress).show_percentage());
-                    if let Some(errors) = &self.project_error {
-                        ui.colored_label(egui::Color32::RED, "Errors:");
-                        for err in errors {
-                            ui.label(err);
-                        }
-                    }
-                });
-        }
-
-        self.toast.show(&graphics.get_egui_context());
-    }
-
-    fn exit(&mut self, _event_loop: &ActiveEventLoop) {
-        log::info!("Exiting menu scene");
-    }
-
-    fn run_command(&mut self) -> SceneCommand {
-        std::mem::replace(&mut self.scene_command, SceneCommand::None)
-    }
-}
-
-impl Keyboard for MainMenu {
-    fn key_down(&mut self, key: KeyCode, event_loop: &ActiveEventLoop) {
-        if key == KeyCode::Escape {
-            if !self.show_new_project && !self.is_in_file_dialogue {
-                event_loop.exit();
-            }
-        }
-    }
-
-    fn key_up(&mut self, _key: KeyCode, _event_loop: &ActiveEventLoop) {}
-}
-
-impl Mouse for MainMenu {
-    fn mouse_move(&mut self, _position: PhysicalPosition<f64>) {}
-
-    fn mouse_down(&mut self, _button: MouseButton) {}
-
-    fn mouse_up(&mut self, _button: MouseButton) {}
-}
-
-impl Controller for MainMenu {
-    fn button_down(&mut self, button: gilrs::Button, id: gilrs::GamepadId) {
-        debug!("Controller button {:?} pressed! [{}]", button, id);
-    }
-
-    fn button_up(&mut self, button: gilrs::Button, id: gilrs::GamepadId) {
-        debug!("Controller button {:?} released! [{}]", button, id);
-    }
-
-    fn left_stick_changed(&mut self, x: f32, y: f32, id: gilrs::GamepadId) {
-        debug!("Left stick changed: x = {} | y = {} | id = {}", x, y, id);
-    }
-
-    fn right_stick_changed(&mut self, x: f32, y: f32, id: gilrs::GamepadId) {
-        debug!("Right stick changed: x = {} | y = {} | id = {}", x, y, id);
-    }
-
-    fn on_connect(&mut self, id: gilrs::GamepadId) {
-        debug!("Controller connected [{}]", id);
-    }
-
-    fn on_disconnect(&mut self, id: gilrs::GamepadId) {
-        debug!("Controller disconnected [{}]", id);
-    }
-}
diff --git a/eucalyptus/src/scene1.rs b/eucalyptus/src/scene1.rs
deleted file mode 100644
index a5cf8cf..0000000
--- a/eucalyptus/src/scene1.rs
+++ /dev/null
@@ -1,232 +0,0 @@
-use std::collections::HashSet;
-use std::sync::Arc;
-
-use dropbear_engine::async_trait::async_trait;
-use dropbear_engine::camera::Camera;
-use dropbear_engine::entity::{AdoptedEntity, Transform};
-use dropbear_engine::graphics::{Graphics, Shader};
-use dropbear_engine::hecs::World;
-use dropbear_engine::input::Controller;
-use dropbear_engine::nalgebra::{Point3, Vector3};
-use dropbear_engine::scene::SceneCommand;
-use dropbear_engine::wgpu::{Color, RenderPipeline};
-use dropbear_engine::winit::dpi::PhysicalPosition;
-use dropbear_engine::winit::event::MouseButton;
-use dropbear_engine::winit::window::Window;
-use dropbear_engine::{gilrs, hecs};
-use dropbear_engine::{
-    input::{Keyboard, Mouse},
-    log::debug,
-    scene::Scene,
-    winit::{event_loop::ActiveEventLoop, keyboard::KeyCode},
-};
-
-// this file purely exists as reference. do not add to module, and probably will be deleted sooner or later
-
-pub struct TestingScene1 {
-    world: hecs::World,
-    render_pipeline: Option<RenderPipeline>,
-    camera: Camera,
-    pressed_keys: HashSet<KeyCode>,
-    is_cursor_locked: bool,
-    window: Option<Arc<Window>>,
-    scene_command: SceneCommand,
-}
-
-impl TestingScene1 {
-    pub fn new() -> Self {
-        debug!("TestingScene1 instance created");
-        Self {
-            world: World::new(),
-            is_cursor_locked: true,
-            render_pipeline: None,
-            camera: Camera::default(),
-            pressed_keys: Default::default(),
-            window: Default::default(),
-            scene_command: Default::default(),
-        }
-    }
-}
-
-impl Scene for TestingScene1 {
-    fn load(&mut self, graphics: &mut Graphics) {
-        let shader = Shader::new(
-            graphics,
-            include_str!("../../dropbear-engine/resources/shaders/shader.wgsl"),
-            Some("default"),
-        );
-
-        let horse_model =
-            AdoptedEntity::new(graphics, "models/low_poly_horse.glb", Some("horse")).unwrap();
-
-        self.world.spawn((horse_model, Transform::default()));
-
-        let camera = Camera::new(
-            graphics,
-            Point3::new(0.0, 1.0, 2.0),
-            Point3::new(0.0, 0.0, 0.0),
-            Vector3::y(),
-            (graphics.state.config.width / graphics.state.config.height) as f32,
-            45.0,
-            0.1,
-            100.0,
-            0.125,
-            0.002,
-        );
-
-        let pipeline = graphics.create_render_pipline(
-            &shader,
-            vec![&graphics.state.texture_bind_layout, camera.layout()],
-        );
-
-        self.camera = camera;
-        self.window = Some(graphics.state.window.clone());
-
-        // ensure that this is the last line
-        self.render_pipeline = Some(pipeline);
-    }
-
-    fn update(&mut self, _dt: f32, graphics: &mut Graphics) {
-        // hold down movement
-        for key in &self.pressed_keys {
-            match key {
-                KeyCode::KeyW => self.camera.move_forwards(),
-                KeyCode::KeyA => self.camera.move_left(),
-                KeyCode::KeyD => self.camera.move_right(),
-                KeyCode::KeyS => self.camera.move_back(),
-                KeyCode::ShiftLeft => self.camera.move_down(),
-                KeyCode::Space => self.camera.move_up(),
-                _ => {}
-            }
-        }
-
-        if !self.is_cursor_locked {
-            self.window.as_mut().unwrap().set_cursor_visible(true);
-        }
-
-        let query = self.world.query_mut::<(&mut AdoptedEntity, &Transform)>();
-        for (_, (entity, transform)) in query {
-            entity.update(&graphics, transform);
-        }
-
-        self.camera.update(graphics);
-    }
-
-    fn render(&mut self, graphics: &mut Graphics) {
-        // cornflower blue
-        let color = Color {
-            r: 100.0 / 255.0,
-            g: 149.0 / 255.0,
-            b: 237.0 / 255.0,
-            a: 1.0,
-        };
-
-        // Note: new versions use egui rendering for viewport instead
-        // of using the full window
-        let texture_id = graphics.state.texture_id.clone();
-        let (display_width, display_height) = graphics.screen_size;
-        egui::CentralPanel::default()
-            .frame(egui::Frame::new())
-            .show(graphics.get_egui_context(), |ui| {
-                let rect = ui.max_rect();
-                ui.put(
-                    rect,
-                    egui::Image::new((texture_id, [display_width, display_height].into()))
-                        .fit_to_exact_size([display_width, display_height].into()),
-                );
-            });
-
-        // render everything here
-        if let Some(pipeline) = &self.render_pipeline {
-            {
-                let mut query = self.world.query::<(&AdoptedEntity, &Transform)>();
-                let mut render_pass = graphics.clear_colour(color);
-                render_pass.set_pipeline(pipeline);
-
-                for (_, (entity, _)) in query.iter() {
-                    entity.render(&mut render_pass, &self.camera);
-                }
-            }
-        }
-
-        self.window = Some(graphics.state.window.clone());
-    }
-
-    fn exit(&mut self, _event_loop: &ActiveEventLoop) {}
-
-    fn run_command(&mut self) -> SceneCommand {
-        std::mem::replace(&mut self.scene_command, SceneCommand::None)
-    }
-}
-
-impl Keyboard for TestingScene1 {
-    fn key_down(&mut self, key: KeyCode, event_loop: &ActiveEventLoop) {
-        // debug!("Key pressed: {:?}", key);
-        match key {
-            KeyCode::Escape => event_loop.exit(),
-            KeyCode::F1 => self.is_cursor_locked = !self.is_cursor_locked,
-            // KeyCode::F2 => self.switch_to = Some("testing_scene_2".into()),
-            _ => {
-                self.pressed_keys.insert(key);
-            }
-        }
-    }
-
-    fn key_up(&mut self, key: KeyCode, _event_loop: &ActiveEventLoop) {
-        // debug!("Key released: {:?}", key);
-        self.pressed_keys.remove(&key);
-    }
-}
-
-impl Mouse for TestingScene1 {
-    fn mouse_down(&mut self, _button: MouseButton) {
-        // debug!("Mouse button pressed: {:?}", button)
-    }
-
-    fn mouse_up(&mut self, _button: MouseButton) {
-        // debug!("Mouse button released: {:?}", button);
-    }
-
-    fn mouse_move(&mut self, position: PhysicalPosition<f64>) {
-        if self.is_cursor_locked {
-            if let Some(window) = &self.window {
-                let size = window.inner_size();
-                let center =
-                    PhysicalPosition::new(size.width as f64 / 2.0, size.height as f64 / 2.0);
-
-                let dx = position.x - center.x;
-                let dy = position.y - center.y;
-                self.camera.track_mouse_delta(dx as f32, dy as f32);
-
-                window.set_cursor_position(center).ok();
-                window.set_cursor_visible(false);
-            }
-        }
-    }
-}
-
-impl Controller for TestingScene1 {
-    fn button_down(&mut self, button: gilrs::Button, id: gilrs::GamepadId) {
-        debug!("Controller button {:?} pressed! [{}]", button, id);
-    }
-
-    fn button_up(&mut self, button: gilrs::Button, id: gilrs::GamepadId) {
-        debug!("Controller button {:?} released! [{}]", button, id);
-    }
-
-    fn left_stick_changed(&mut self, x: f32, y: f32, id: gilrs::GamepadId) {
-        debug!("Left stick changed: x = {} | y = {} | id = {}", x, y, id);
-    }
-
-    fn right_stick_changed(&mut self, x: f32, y: f32, id: gilrs::GamepadId) {
-        debug!("Right stick changed: x = {} | y = {} | id = {}", x, y, id);
-    }
-
-    fn on_connect(&mut self, id: gilrs::GamepadId) {
-        debug!("Controller connected [{}]", id);
-    }
-
-    fn on_disconnect(&mut self, id: gilrs::GamepadId) {
-        debug!("Controller disconnected [{}]", id);
-    }
-}
diff --git a/eucalyptus/src/scripting/camera.rs b/eucalyptus/src/scripting/camera.rs
deleted file mode 100644
index 9921526..0000000
--- a/eucalyptus/src/scripting/camera.rs
+++ /dev/null
@@ -1,386 +0,0 @@
-use glam::DVec3;
-use rustyscript::{serde_json, Runtime};
-use serde::{Deserialize, Serialize};
-
-use crate::{camera::CameraType, scripting::ScriptableModule};
-
-impl ScriptableModule for SerializableCamera {
-    fn register(runtime: &mut Runtime) -> anyhow::Result<()> {
-        runtime.register_function("moveForward", Self::move_forward)?;
-        runtime.register_function("moveBackward", Self::move_backward)?;
-        runtime.register_function("moveLeft", Self::move_left)?;
-        runtime.register_function("moveRight", Self::move_right)?;
-        runtime.register_function("moveUp", Self::move_up)?;
-        runtime.register_function("moveDown", Self::move_down)?;
-
-        // Mouse look
-        runtime.register_function("trackMouseDelta", Self::track_mouse_delta)?;
-
-        // Camera switching and management
-        runtime.register_function("switchToCameraByLabel", Self::switch_to_camera_by_label)?;
-        runtime.register_function("getActiveCameraLabel", Self::get_active_camera_label)?;
-        runtime.register_function("getAllCameraLabels", Self::get_all_camera_labels)?;
-        runtime.register_function("getCamerasByType", Self::get_cameras_by_type)?;
-
-        // Multi-camera manipulation
-        runtime.register_function("manipulateCameraByLabel", Self::manipulate_camera_by_label)?;
-        runtime.register_function("getCameraByLabel", Self::get_camera_by_label)?;
-        runtime.register_function("setCameraByLabel", Self::set_camera_by_label)?;
-
-        Ok(())
-    }
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct SerializableCamera {
-    pub label: String,
-    pub eye: DVec3,
-    pub target: DVec3,
-    pub up: DVec3,
-    pub aspect: f64,
-    pub fov: f64,
-    pub near: f64,
-    pub far: f64,
-    pub yaw: f64,
-    pub pitch: f64,
-    
-    pub speed: f64,
-    pub sensitivity: f64,
-
-    pub camera_type: CameraType,
-}
-
-impl SerializableCamera {
-    /// Moves the camera forward. 
-    /// 
-    /// # Parameters
-    /// * args[0] - Self as a Camera. The value of the speed can be adjusted
-    pub fn move_forward(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
-        if args.len() != 1 {
-            return Err(rustyscript::Error::Runtime("moveForward requires 1 arguments".to_string()));
-        }
-
-        let mut camera: SerializableCamera = serde_json::from_value(args[0].clone())
-            .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera: {}", e)))?;
-
-        let forward = (camera.target - camera.eye).normalize();
-        camera.eye += forward * camera.speed;
-        camera.target += forward * camera.speed;
-
-        serde_json::to_value(camera)
-            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Camera: {}", e)))
-    }
-
-    /// Moves the camera back. 
-    /// 
-    /// # Parameters
-    /// * args[0] - Self as a Camera. The value of the speed can be adjusted
-    pub fn move_backward(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
-        if args.len() != 1 {
-            return Err(rustyscript::Error::Runtime("moveBackward requires 1 arguments".to_string()));
-        }
-
-        let mut camera: SerializableCamera = serde_json::from_value(args[0].clone())
-            .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera: {}", e)))?;
-
-        let forward = (camera.target - camera.eye).normalize();
-        camera.eye -= forward * camera.speed;
-        camera.target -= forward * camera.speed;
-
-        serde_json::to_value(camera)
-            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Camera: {}", e)))
-    }
-
-    /// Moves the camera left. 
-    /// 
-    /// # Parameters
-    /// * args[0] - Self as a Camera. The value of the speed can be adjusted
-    pub fn move_left(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
-        if args.len() != 1 {
-            return Err(rustyscript::Error::Runtime("moveLeft requires 1 arguments".to_string()));
-        }
-
-        let mut camera: SerializableCamera = serde_json::from_value(args[0].clone())
-            .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera: {}", e)))?;
-
-        let forward = (camera.target - camera.eye).normalize();
-        let right = camera.up.cross(forward).normalize();
-        camera.eye -= right * camera.speed;
-        camera.target -= right * camera.speed;
-
-        serde_json::to_value(camera)
-            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Camera: {}", e)))
-    }
-
-    /// Moves the camera right. 
-    /// 
-    /// # Parameters
-    /// * args[0] - Self as a Camera. The value of the speed can be adjusted
-    pub fn move_right(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
-        if args.len() != 1 {
-            return Err(rustyscript::Error::Runtime("moveRight requires 1 arguments".to_string()));
-        }
-
-        let mut camera: SerializableCamera = serde_json::from_value(args[0].clone())
-            .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera: {}", e)))?;
-
-        let forward = (camera.target - camera.eye).normalize();
-        let right = camera.up.cross(forward).normalize();
-        camera.eye += right * camera.speed;
-        camera.target += right * camera.speed;
-
-        serde_json::to_value(camera)
-            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Camera: {}", e)))
-    }
-
-    /// Moves the camera up. 
-    /// 
-    /// # Parameters
-    /// * args[0] - Self as a Camera. The value of the speed can be adjusted
-    pub fn move_up(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
-        if args.len() != 1 {
-            return Err(rustyscript::Error::Runtime("moveUp requires 1 arguments".to_string()));
-        }
-
-        let mut camera: SerializableCamera = serde_json::from_value(args[0].clone())
-            .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera: {}", e)))?;
-
-        let up = camera.up.normalize();
-        camera.eye += up * camera.speed;
-        camera.target += up * camera.speed;
-
-        serde_json::to_value(camera)
-            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Camera: {}", e)))
-    }
-
-    /// Moves the camera down. 
-    /// 
-    /// # Parameters
-    /// * args[0] - Self as a Camera. The value of the speed can be adjusted
-    pub fn move_down(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
-        if args.len() != 1 {
-            return Err(rustyscript::Error::Runtime("moveDown requires 1 arguments".to_string()));
-        }
-
-        let mut camera: SerializableCamera = serde_json::from_value(args[0].clone())
-            .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera: {}", e)))?;
-
-        let up = camera.up.normalize();
-        camera.eye -= up * camera.speed;
-        camera.target -= up * camera.speed;
-
-        serde_json::to_value(camera)
-            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Camera: {}", e)))
-    }
-
-    /// Sets the tracking of the camera to the mouse delta
-    /// 
-    /// # Parameters
-    /// * args[0] - Self as a Camera
-    /// * args[1] - The dx value of the mouse position as a number
-    /// * args[2] - The dy value of the mouse position as a number
-    pub fn track_mouse_delta(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
-        if args.len() != 3 {
-            return Err(rustyscript::Error::Runtime("trackMouseDelta requires 3 arguments (camera, dx, dy)".to_string()));
-        }
-
-        let mut camera: SerializableCamera = serde_json::from_value(args[0].clone())
-            .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera: {}", e)))?;
-
-        let dx = args[1].as_f64().ok_or_else(|| {
-            rustyscript::Error::Runtime("dx must be a number".to_string())
-        })?;
-
-        let dy = args[2].as_f64().ok_or_else(|| {
-            rustyscript::Error::Runtime("dy must be a number".to_string())
-        })?;
-
-        camera.yaw += dx * camera.sensitivity;
-        camera.pitch += dy * camera.sensitivity;
-
-        camera.pitch = camera.pitch.clamp(-89.0_f64.to_radians(), 89.0_f64.to_radians());
-
-        let direction = DVec3::new(
-            camera.yaw.cos() * camera.pitch.cos(),
-            camera.pitch.sin(),
-            camera.yaw.sin() * camera.pitch.cos(),
-        );
-        camera.target = camera.eye + direction;
-
-        serde_json::to_value(camera)
-            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Camera: {}", e)))
-    }
-
-    pub fn switch_to_camera_by_label(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
-        if args.len() != 1 {
-            return Err(rustyscript::Error::Runtime("switchToCameraByLabel requires 1 argument (label)".to_string()));
-        }
-
-        let label = args[0].as_str().ok_or_else(|| {
-            rustyscript::Error::Runtime("Label must be a string".to_string())
-        })?;
-
-        let action = CameraAction {
-            action: "switch_camera".to_string(),
-            label: Some(label.to_string()),
-            camera_type: None,
-            camera_data: None,
-            property: None,
-            value: None,
-        };
-
-        serde_json::to_value(action)
-            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize camera action: {}", e)))
-    }
-
-    /// Get the label of the currently active camera
-    pub fn get_active_camera_label(_args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
-        let action = CameraAction {
-            action: "get_active_camera".to_string(),
-            label: None,
-            camera_type: None,
-            camera_data: None,
-            property: None,
-            value: None,
-        };
-
-        serde_json::to_value(action)
-            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize camera action: {}", e)))
-    }
-
-    /// Get all camera labels in the world
-    pub fn get_all_camera_labels(_args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
-        let action = CameraAction {
-            action: "get_all_cameras".to_string(),
-            label: None,
-            camera_type: None,
-            camera_data: None,
-            property: None,
-            value: None,
-        };
-
-        serde_json::to_value(action)
-            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize camera action: {}", e)))
-    }
-
-    /// Get cameras by type
-    pub fn get_cameras_by_type(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
-        if args.len() != 1 {
-            return Err(rustyscript::Error::Runtime("getCamerasByType requires 1 argument (camera_type)".to_string()));
-        }
-
-        let camera_type = args[0].as_str().ok_or_else(|| {
-            rustyscript::Error::Runtime("Camera type must be a string".to_string())
-        })?;
-
-        // Validate camera type
-        match camera_type {
-            "Normal" | "Debug" | "Player" => {
-                let action = CameraAction {
-                    action: "get_cameras_by_type".to_string(),
-                    label: None,
-                    camera_type: Some(camera_type.to_string()),
-                    camera_data: None,
-                    property: None,
-                    value: None,
-                };
-
-                serde_json::to_value(action)
-                    .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize camera action: {}", e)))
-            }
-            _ => Err(rustyscript::Error::Runtime("Invalid camera type. Use 'Normal', 'Debug', or 'Player'".to_string()))
-        }
-    }
-
-    /// Manipulate a specific camera by label without switching to it
-    pub fn manipulate_camera_by_label(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
-        if args.len() != 3 {
-            return Err(rustyscript::Error::Runtime("manipulateCameraByLabel requires 3 arguments (label, property, value)".to_string()));
-        }
-
-        let label = args[0].as_str().ok_or_else(|| {
-            rustyscript::Error::Runtime("Label must be a string".to_string())
-        })?;
-
-        let property = args[1].as_str().ok_or_else(|| {
-            rustyscript::Error::Runtime("Property must be a string".to_string())
-        })?;
-
-        // Validate property
-        match property {
-            "position" | "target" | "speed" | "sensitivity" | "fov" | "yaw" | "pitch" => {
-                let action = CameraAction {
-                    action: "manipulate_camera".to_string(),
-                    label: Some(label.to_string()),
-                    camera_type: None,
-                    camera_data: None,
-                    property: Some(property.to_string()),
-                    value: Some(args[2].clone()),
-                };
-
-                serde_json::to_value(action)
-                    .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize camera action: {}", e)))
-            }
-            _ => Err(rustyscript::Error::Runtime("Invalid property. Use 'position', 'target', 'speed', 'sensitivity', 'fov', 'yaw', or 'pitch'".to_string()))
-        }
-    }
-
-    /// Get a camera's data by label
-    pub fn get_camera_by_label(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
-        if args.len() != 1 {
-            return Err(rustyscript::Error::Runtime("getCameraByLabel requires 1 argument (label)".to_string()));
-        }
-
-        let label = args[0].as_str().ok_or_else(|| {
-            rustyscript::Error::Runtime("Label must be a string".to_string())
-        })?;
-
-        let action = CameraAction {
-            action: "get_camera_by_label".to_string(),
-            label: Some(label.to_string()),
-            camera_type: None,
-            camera_data: None,
-            property: None,
-            value: None,
-        };
-
-        serde_json::to_value(action)
-            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize camera action: {}", e)))
-    }
-
-    /// Set a camera's complete data by label
-    pub fn set_camera_by_label(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> {
-        if args.len() != 2 {
-            return Err(rustyscript::Error::Runtime("setCameraByLabel requires 2 arguments (label, camera_data)".to_string()));
-        }
-
-        let label = args[0].as_str().ok_or_else(|| {
-            rustyscript::Error::Runtime("Label must be a string".to_string())
-        })?;
-
-        let camera_data: SerializableCamera = serde_json::from_value(args[1].clone())
-            .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera data: {}", e)))?;
-
-        let action = CameraAction {
-            action: "set_camera_by_label".to_string(),
-            label: Some(label.to_string()),
-            camera_type: None,
-            camera_data: Some(camera_data),
-            property: None,
-            value: None,
-        };
-
-        serde_json::to_value(action)
-            .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize camera action: {}", e)))
-    }
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct CameraAction {
-    pub action: String,
-    pub label: Option<String>,
-    pub camera_type: Option<String>,
-    pub camera_data: Option<SerializableCamera>,
-    pub property: Option<String>,
-    pub value: Option<serde_json::Value>,
-}
\ No newline at end of file
diff --git a/eucalyptus/src/scripting/entity.rs b/eucalyptus/src/scripting/entity.rs
deleted file mode 100644
index c4a8652..0000000
--- a/eucalyptus/src/scripting/entity.rs
+++ /dev/null
@@ -1,332 +0,0 @@
-use crate::{scripting::ScriptableModule, states::{ModelProperties, PropertyValue}};
-use rustyscript::{serde_json, Runtime};
-use serde::{Deserialize, Serialize};
-
-#[derive(Clone, Serialize, Deserialize)]
-pub struct SerializableModelProperties {
-    pub properties: std::collections::HashMap<String, serde_json::Value>,
-}
-
-impl From<&ModelProperties> for SerializableModelProperties {
-    fn from(props: &ModelProperties) -> Self {
-        let mut properties = std::collections::HashMap::new();
-        
-        for (key, value) in props.custom_properties.iter() {
-            let json_value = match value {
-                PropertyValue::String(s) => serde_json::Value::String(s.clone()),
-                PropertyValue::Int(i) => serde_json::Value::Number(serde_json::Number::from(*i)),
-                PropertyValue::Float(f) => serde_json::Value::Number(
-                    serde_json::Number::from_f64(*f as f64).unwrap_or(serde_json::Number::from(0))
-                ),
-                PropertyValue::Bool(b) => serde_json::Value::Bool(*b),
-                PropertyValue::Vec3(v) => serde_json::Value::Array(vec![
-                    serde_json::Value::Number(serde_json::Number::from_f64(v[0] as f64).unwrap()),
-                    serde_json::Value::Number(serde_json::Number::from_f64(v[1] as f64).unwrap()),
-                    serde_json::Value::Number(serde_json::Number::from_f64(v[2] as f64).unwrap()),
-                ]),
-            };
-            properties.insert(key.clone(), json_value);
-        }
-        
-        Self { properties }
-    }
-}
-
-impl SerializableModelProperties {
-    pub fn to_model_properties(&self) -> ModelProperties {
-        let mut props = ModelProperties::default();
-        
-        for (key, value) in &self.properties {
-            let property_value = match value {
-                serde_json::Value::String(s) => PropertyValue::String(s.clone()),
-                serde_json::Value::Number(n) => {
-                    if let Some(i) = n.as_i64() {
-                        PropertyValue::Int(i)
-                    } else if let Some(f) = n.as_f64() {
-                        PropertyValue::Float(f)
-                    } else {
-                        continue;
-                    }
-                },
-                serde_json::Value::Bool(b) => PropertyValue::Bool(*b),
-                serde_json::Value::Array(arr) => {
-                    if arr.len() == 3 {
-                        if let (Some(x), Some(y), Some(z)) = (
-                            arr[0].as_f64(),
-                            arr[1].as_f64(),
-                            arr[2].as_f64(),
-                        ) {
-                            PropertyValue::Vec3([x as f32, y as f32, z as f32])
-                        } else {
-                            continue;
-                        }
-                    } else {
-                        continue;
-                    }
-                },
-                _ => continue,
-            };
-            props.set_property(key.clone(), property_value);
-        }
-        
-        props
-    }
-}
-
-impl ScriptableModule for ModelProperties {
-    fn register(runtime: &mut Runtime) -> anyhow::Result<()> {
-        runtime.register_function("getProperty", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 2 {
-                return Err(rustyscript::Error::Runtime("getProperty requires 2 arguments (properties, key)".to_string()));
-            }
-            
-            let props: SerializableModelProperties = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
-            
-            let key = args[1].as_str()
-                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
-            
-            let value = props.properties.get(key)
-                .cloned()
-                .unwrap_or(serde_json::Value::Null);
-            
-            Ok(value)
-        })?;
-
-        // Set property (string)
-        runtime.register_function("setPropertyString", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 3 {
-                return Err(rustyscript::Error::Runtime("setPropertyString requires 3 arguments (properties, key, value)".to_string()));
-            }
-            
-            let mut props: SerializableModelProperties = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
-            
-            let key = args[1].as_str()
-                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
-            
-            let value = args[2].as_str()
-                .ok_or_else(|| rustyscript::Error::Runtime("Value must be a string".to_string()))?;
-            
-            props.properties.insert(key.to_string(), serde_json::Value::String(value.to_string()));
-            
-            serde_json::to_value(props)
-                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize properties: {}", e)))
-        })?;
-
-        // Set property (int)
-        runtime.register_function("setPropertyInt", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 3 {
-                return Err(rustyscript::Error::Runtime("setPropertyInt requires 3 arguments (properties, key, value)".to_string()));
-            }
-            
-            let mut props: SerializableModelProperties = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
-            
-            let key = args[1].as_str()
-                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
-            
-            let value = args[2].as_i64()
-                .ok_or_else(|| rustyscript::Error::Runtime("Value must be an integer".to_string()))?;
-            
-            props.properties.insert(key.to_string(), serde_json::Value::Number(serde_json::Number::from(value)));
-            
-            serde_json::to_value(props)
-                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize properties: {}", e)))
-        })?;
-
-        // Set property (float)
-        runtime.register_function("setPropertyFloat", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 3 {
-                return Err(rustyscript::Error::Runtime("setPropertyFloat requires 3 arguments (properties, key, value)".to_string()));
-            }
-            
-            let mut props: SerializableModelProperties = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
-            
-            let key = args[1].as_str()
-                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
-            
-            let value = args[2].as_f64()
-                .ok_or_else(|| rustyscript::Error::Runtime("Value must be a number".to_string()))?;
-            
-            props.properties.insert(key.to_string(), serde_json::Value::Number(
-                serde_json::Number::from_f64(value).unwrap_or(serde_json::Number::from(0))
-            ));
-            
-            serde_json::to_value(props)
-                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize properties: {}", e)))
-        })?;
-
-        // Set property (bool)
-        runtime.register_function("setPropertyBool", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 3 {
-                return Err(rustyscript::Error::Runtime("setPropertyBool requires 3 arguments (properties, key, value)".to_string()));
-            }
-            
-            let mut props: SerializableModelProperties = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
-            
-            let key = args[1].as_str()
-                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
-            
-            let value = args[2].as_bool()
-                .ok_or_else(|| rustyscript::Error::Runtime("Value must be a boolean".to_string()))?;
-            
-            props.properties.insert(key.to_string(), serde_json::Value::Bool(value));
-            
-            serde_json::to_value(props)
-                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize properties: {}", e)))
-        })?;
-
-        // Set property (Vec3)
-        runtime.register_function("setPropertyVec3", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 3 {
-                return Err(rustyscript::Error::Runtime("setPropertyVec3 requires 3 arguments (properties, key, value)".to_string()));
-            }
-            
-            let mut props: SerializableModelProperties = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
-            
-            let key = args[1].as_str()
-                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
-            
-            let value = args[2].as_array()
-                .ok_or_else(|| rustyscript::Error::Runtime("Value must be an array".to_string()))?;
-            
-            if value.len() != 3 {
-                return Err(rustyscript::Error::Runtime("Vec3 array must have exactly 3 elements".to_string()));
-            }
-            
-            let x = value[0].as_f64().ok_or_else(|| rustyscript::Error::Runtime("Vec3 elements must be numbers".to_string()))?;
-            let y = value[1].as_f64().ok_or_else(|| rustyscript::Error::Runtime("Vec3 elements must be numbers".to_string()))?;
-            let z = value[2].as_f64().ok_or_else(|| rustyscript::Error::Runtime("Vec3 elements must be numbers".to_string()))?;
-            
-            let vec3_array = serde_json::Value::Array(vec![
-                serde_json::Value::Number(serde_json::Number::from_f64(x).unwrap()),
-                serde_json::Value::Number(serde_json::Number::from_f64(y).unwrap()),
-                serde_json::Value::Number(serde_json::Number::from_f64(z).unwrap()),
-            ]);
-            
-            props.properties.insert(key.to_string(), vec3_array);
-            
-            serde_json::to_value(props)
-                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize properties: {}", e)))
-        })?;
-
-        // Typed getters
-        runtime.register_function("getString", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 2 {
-                return Err(rustyscript::Error::Runtime("getString requires 2 arguments (properties, key)".to_string()));
-            }
-            
-            let props: SerializableModelProperties = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
-            
-            let key = args[1].as_str()
-                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
-            
-            let value = props.properties.get(key)
-                .and_then(|v| v.as_str())
-                .unwrap_or("")
-                .to_string();
-            
-            Ok(serde_json::Value::String(value))
-        })?;
-
-        runtime.register_function("getInt", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 2 {
-                return Err(rustyscript::Error::Runtime("getInt requires 2 arguments (properties, key)".to_string()));
-            }
-            
-            let props: SerializableModelProperties = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
-            
-            let key = args[1].as_str()
-                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
-            
-            let value = props.properties.get(key)
-                .and_then(|v| v.as_i64())
-                .unwrap_or(0);
-            
-            Ok(serde_json::Value::Number(serde_json::Number::from(value)))
-        })?;
-
-        runtime.register_function("getFloat", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 2 {
-                return Err(rustyscript::Error::Runtime("getFloat requires 2 arguments (properties, key)".to_string()));
-            }
-            
-            let props: SerializableModelProperties = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
-            
-            let key = args[1].as_str()
-                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
-            
-            let value = props.properties.get(key)
-                .and_then(|v| v.as_f64())
-                .unwrap_or(0.0);
-            
-            Ok(serde_json::Value::Number(serde_json::Number::from_f64(value).unwrap()))
-        })?;
-
-        runtime.register_function("getBool", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 2 {
-                return Err(rustyscript::Error::Runtime("getBool requires 2 arguments (properties, key)".to_string()));
-            }
-            
-            let props: SerializableModelProperties = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
-            
-            let key = args[1].as_str()
-                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
-            
-            let value = props.properties.get(key)
-                .and_then(|v| v.as_bool())
-                .unwrap_or(false);
-            
-            Ok(serde_json::Value::Bool(value))
-        })?;
-
-        runtime.register_function("getVec3", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 2 {
-                return Err(rustyscript::Error::Runtime("getVec3 requires 2 arguments (properties, key)".to_string()));
-            }
-            
-            let props: SerializableModelProperties = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
-            
-            let key = args[1].as_str()
-                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
-            
-            let value = props.properties.get(key)
-                .and_then(|v| v.as_array())
-                .cloned()
-                .unwrap_or_else(|| vec![
-                    serde_json::Value::Number(serde_json::Number::from(0)),
-                    serde_json::Value::Number(serde_json::Number::from(0)),
-                    serde_json::Value::Number(serde_json::Number::from(0)),
-                ]);
-            
-            Ok(serde_json::Value::Array(value))
-        })?;
-
-        runtime.register_function("hasProperty", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 2 {
-                return Err(rustyscript::Error::Runtime("hasProperty requires 2 arguments (properties, key)".to_string()));
-            }
-            
-            let props: SerializableModelProperties = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?;
-            
-            let key = args[1].as_str()
-                .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?;
-            
-            let has_property = props.properties.contains_key(key);
-            Ok(serde_json::Value::Bool(has_property))
-        })?;
-
-        log::info!("[Script] Initialised entity custom properties module");
-        Ok(())
-    }
-}
\ No newline at end of file
diff --git a/eucalyptus/src/scripting/input.rs b/eucalyptus/src/scripting/input.rs
deleted file mode 100644
index d590516..0000000
--- a/eucalyptus/src/scripting/input.rs
+++ /dev/null
@@ -1,288 +0,0 @@
-use std::{collections::{HashMap, HashSet}, time::{Duration, Instant}};
-
-use rustyscript::{serde_json, Runtime};
-use serde::{Deserialize, Serialize};
-use winit::{event::MouseButton, keyboard::KeyCode};
-
-use crate::scripting::ScriptableModule;
-
-#[derive(Clone)]
-pub struct InputState {
-    #[allow(dead_code)]
-    pub last_key_press_times: HashMap<KeyCode, Instant>,
-    #[allow(dead_code)]
-    pub double_press_threshold: Duration,
-    pub mouse_pos: (f64, f64),
-    pub mouse_button: HashSet<MouseButton>,    
-    pub pressed_keys: HashSet<KeyCode>,
-    pub mouse_delta: Option<(f64, f64)>,
-    pub is_cursor_locked: bool,
-}
-
-#[derive(Clone, Serialize, Deserialize)]
-pub struct SerializableInputState {
-    pub mouse_pos: (f64, f64),
-    pub pressed_keys: Vec<String>,
-    pub mouse_delta: Option<(f64, f64)>,
-    pub is_cursor_locked: bool,
-}
-
-impl From<&InputState> for SerializableInputState {
-    fn from(input_state: &InputState) -> Self {
-        Self {
-            mouse_pos: input_state.mouse_pos,
-            pressed_keys: input_state.pressed_keys.iter()
-                .filter_map(|key| keycode_to_string(*key))
-                .collect(),
-            mouse_delta: input_state.mouse_delta,
-            is_cursor_locked: input_state.is_cursor_locked,
-        }
-    }
-}
-
-impl Default for InputState {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-impl ScriptableModule for InputState {
-    fn register(runtime: &mut Runtime) -> anyhow::Result<()> {
-        runtime.register_function("isKeyPressed", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 2 {
-                return Err(rustyscript::Error::Runtime("isKeyPressed requires 2 arguments (inputState, keyCode)".to_string()));
-            }
-            
-            let input_state: SerializableInputState = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid input state: {}", e)))?;
-            
-            let key_code_str = args[1].as_str()
-                .ok_or_else(|| rustyscript::Error::Runtime("Key code must be a string".to_string()))?;
-            
-            let is_pressed = input_state.pressed_keys.contains(&key_code_str.to_string());
-            Ok(serde_json::Value::Bool(is_pressed))
-        })?;
-
-        runtime.register_function("getMouseX", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 1 {
-                return Err(rustyscript::Error::Runtime("getMouseX requires 1 argument (inputState)".to_string()));
-            }
-            
-            let input_state: SerializableInputState = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid input state: {}", e)))?;
-            
-            Ok(serde_json::Value::Number(serde_json::Number::from_f64(input_state.mouse_pos.0).unwrap()))
-        })?;
-
-        runtime.register_function("getMouseY", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 1 {
-                return Err(rustyscript::Error::Runtime("getMouseY requires 1 argument (inputState)".to_string()));
-            }
-            
-            let input_state: SerializableInputState = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid input state: {}", e)))?;
-            
-            Ok(serde_json::Value::Number(serde_json::Number::from_f64(input_state.mouse_pos.1).unwrap()))
-        })?;
-
-        runtime.register_function("getMouseDeltaX", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 1 {
-                return Err(rustyscript::Error::Runtime("getMouseDeltaX requires 1 argument (inputState)".to_string()));
-            }
-            
-            let input_state: SerializableInputState = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid input state: {}", e)))?;
-            
-            let delta_x = input_state.mouse_delta.map(|d| d.0).unwrap_or(0.0);
-            Ok(serde_json::Value::Number(serde_json::Number::from_f64(delta_x).unwrap()))
-        })?;
-
-        runtime.register_function("getMouseDeltaY", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 1 {
-                return Err(rustyscript::Error::Runtime("getMouseDeltaY requires 1 argument (inputState)".to_string()));
-            }
-            
-            let input_state: SerializableInputState = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid input state: {}", e)))?;
-            
-            let delta_y = input_state.mouse_delta.map(|d| d.1).unwrap_or(0.0);
-            Ok(serde_json::Value::Number(serde_json::Number::from_f64(delta_y).unwrap()))
-        })?;
-
-        runtime.register_function("lockCursor", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 1 {
-                return Err(rustyscript::Error::Runtime("lockCursor requires 1 argument (locked)".to_string()));
-            }
-            
-            let _locked = args[0].as_bool()
-                .ok_or_else(|| rustyscript::Error::Runtime("Locked parameter must be a boolean".to_string()))?;
-            
-            // This function would need to communicate back to the engine to actually lock the cursor
-            // For now, just return success
-            Ok(serde_json::Value::Bool(true))
-        })?;
-
-        log::info!("[Script] Initialised input module");
-        Ok(())
-    }
-}
-
-impl InputState {
-    pub fn new() -> Self {
-        Self {
-            mouse_pos: Default::default(),
-            mouse_button: Default::default(),
-            pressed_keys: HashSet::new(),
-            last_key_press_times: HashMap::new(),
-            double_press_threshold: Duration::from_millis(300),
-            mouse_delta: None,
-            is_cursor_locked: false,
-        }
-    }
-
-    pub fn lock_cursor(&mut self, toggle: bool) {
-        self.is_cursor_locked = toggle;
-    }
-}
-
-/// Helper function to convert string to KeyCode
-#[allow(dead_code)]
-fn string_to_keycode(key_str: &str) -> Option<KeyCode> {
-    match key_str {
-        "KeyA" => Some(KeyCode::KeyA),
-        "KeyB" => Some(KeyCode::KeyB),
-        "KeyC" => Some(KeyCode::KeyC),
-        "KeyD" => Some(KeyCode::KeyD),
-        "KeyE" => Some(KeyCode::KeyE),
-        "KeyF" => Some(KeyCode::KeyF),
-        "KeyG" => Some(KeyCode::KeyG),
-        "KeyH" => Some(KeyCode::KeyH),
-        "KeyI" => Some(KeyCode::KeyI),
-        "KeyJ" => Some(KeyCode::KeyJ),
-        "KeyK" => Some(KeyCode::KeyK),
-        "KeyL" => Some(KeyCode::KeyL),
-        "KeyM" => Some(KeyCode::KeyM),
-        "KeyN" => Some(KeyCode::KeyN),
-        "KeyO" => Some(KeyCode::KeyO),
-        "KeyP" => Some(KeyCode::KeyP),
-        "KeyQ" => Some(KeyCode::KeyQ),
-        "KeyR" => Some(KeyCode::KeyR),
-        "KeyS" => Some(KeyCode::KeyS),
-        "KeyT" => Some(KeyCode::KeyT),
-        "KeyU" => Some(KeyCode::KeyU),
-        "KeyV" => Some(KeyCode::KeyV),
-        "KeyW" => Some(KeyCode::KeyW),
-        "KeyX" => Some(KeyCode::KeyX),
-        "KeyY" => Some(KeyCode::KeyY),
-        "KeyZ" => Some(KeyCode::KeyZ),
-        "Space" => Some(KeyCode::Space),
-        "ShiftLeft" => Some(KeyCode::ShiftLeft),
-        "ShiftRight" => Some(KeyCode::ShiftRight),
-        "ControlLeft" => Some(KeyCode::ControlLeft),
-        "ControlRight" => Some(KeyCode::ControlRight),
-        "AltLeft" => Some(KeyCode::AltLeft),
-        "AltRight" => Some(KeyCode::AltRight),
-        "Escape" => Some(KeyCode::Escape),
-        "Enter" => Some(KeyCode::Enter),
-        "Tab" => Some(KeyCode::Tab),
-        "ArrowUp" => Some(KeyCode::ArrowUp),
-        "ArrowDown" => Some(KeyCode::ArrowDown),
-        "ArrowLeft" => Some(KeyCode::ArrowLeft),
-        "ArrowRight" => Some(KeyCode::ArrowRight),
-        "Digit0" => Some(KeyCode::Digit0),
-        "Digit1" => Some(KeyCode::Digit1),
-        "Digit2" => Some(KeyCode::Digit2),
-        "Digit3" => Some(KeyCode::Digit3),
-        "Digit4" => Some(KeyCode::Digit4),
-        "Digit5" => Some(KeyCode::Digit5),
-        "Digit6" => Some(KeyCode::Digit6),
-        "Digit7" => Some(KeyCode::Digit7),
-        "Digit8" => Some(KeyCode::Digit8),
-        "Digit9" => Some(KeyCode::Digit9),
-        "F1" => Some(KeyCode::F1),
-        "F2" => Some(KeyCode::F2),
-        "F3" => Some(KeyCode::F3),
-        "F4" => Some(KeyCode::F4),
-        "F5" => Some(KeyCode::F5),
-        "F6" => Some(KeyCode::F6),
-        "F7" => Some(KeyCode::F7),
-        "F8" => Some(KeyCode::F8),
-        "F9" => Some(KeyCode::F9),
-        "F10" => Some(KeyCode::F10),
-        "F11" => Some(KeyCode::F11),
-        "F12" => Some(KeyCode::F12),
-        _ => None,
-    }
-}
-
-/// Helper function to convert KeyCode to string
-fn keycode_to_string(key_code: KeyCode) -> Option<String> {
-    match key_code {
-        KeyCode::KeyA => Some("KeyA".to_string()),
-        KeyCode::KeyB => Some("KeyB".to_string()),
-        KeyCode::KeyC => Some("KeyC".to_string()),
-        KeyCode::KeyD => Some("KeyD".to_string()),
-        KeyCode::KeyE => Some("KeyE".to_string()),
-        KeyCode::KeyF => Some("KeyF".to_string()),
-        KeyCode::KeyG => Some("KeyG".to_string()),
-        KeyCode::KeyH => Some("KeyH".to_string()),
-        KeyCode::KeyI => Some("KeyI".to_string()),
-        KeyCode::KeyJ => Some("KeyJ".to_string()),
-        KeyCode::KeyK => Some("KeyK".to_string()),
-        KeyCode::KeyL => Some("KeyL".to_string()),
-        KeyCode::KeyM => Some("KeyM".to_string()),
-        KeyCode::KeyN => Some("KeyN".to_string()),
-        KeyCode::KeyO => Some("KeyO".to_string()),
-        KeyCode::KeyP => Some("KeyP".to_string()),
-        KeyCode::KeyQ => Some("KeyQ".to_string()),
-        KeyCode::KeyR => Some("KeyR".to_string()),
-        KeyCode::KeyS => Some("KeyS".to_string()),
-        KeyCode::KeyT => Some("KeyT".to_string()),
-        KeyCode::KeyU => Some("KeyU".to_string()),
-        KeyCode::KeyV => Some("KeyV".to_string()),
-        KeyCode::KeyW => Some("KeyW".to_string()),
-        KeyCode::KeyX => Some("KeyX".to_string()),
-        KeyCode::KeyY => Some("KeyY".to_string()),
-        KeyCode::KeyZ => Some("KeyZ".to_string()),
-        KeyCode::Space => Some("Space".to_string()),
-        KeyCode::ShiftLeft => Some("ShiftLeft".to_string()),
-        KeyCode::ShiftRight => Some("ShiftRight".to_string()),
-        KeyCode::ControlLeft => Some("ControlLeft".to_string()),
-        KeyCode::ControlRight => Some("ControlRight".to_string()),
-        KeyCode::AltLeft => Some("AltLeft".to_string()),
-        KeyCode::AltRight => Some("AltRight".to_string()),
-        KeyCode::Escape => Some("Escape".to_string()),
-        KeyCode::Enter => Some("Enter".to_string()),
-        KeyCode::Tab => Some("Tab".to_string()),
-        KeyCode::ArrowUp => Some("ArrowUp".to_string()),
-        KeyCode::ArrowDown => Some("ArrowDown".to_string()),
-        KeyCode::ArrowLeft => Some("ArrowLeft".to_string()),
-        KeyCode::ArrowRight => Some("ArrowRight".to_string()),
-        KeyCode::Digit0 => Some("Digit0".to_string()),
-        KeyCode::Digit1 => Some("Digit1".to_string()),
-        KeyCode::Digit2 => Some("Digit2".to_string()),
-        KeyCode::Digit3 => Some("Digit3".to_string()),
-        KeyCode::Digit4 => Some("Digit4".to_string()),
-        KeyCode::Digit5 => Some("Digit5".to_string()),
-        KeyCode::Digit6 => Some("Digit6".to_string()),
-        KeyCode::Digit7 => Some("Digit7".to_string()),
-        KeyCode::Digit8 => Some("Digit8".to_string()),
-        KeyCode::Digit9 => Some("Digit9".to_string()),
-        KeyCode::F1 => Some("F1".to_string()),
-        KeyCode::F2 => Some("F2".to_string()),
-        KeyCode::F3 => Some("F3".to_string()),
-        KeyCode::F4 => Some("F4".to_string()),
-        KeyCode::F5 => Some("F5".to_string()),
-        KeyCode::F6 => Some("F6".to_string()),
-        KeyCode::F7 => Some("F7".to_string()),
-        KeyCode::F8 => Some("F8".to_string()),
-        KeyCode::F9 => Some("F9".to_string()),
-        KeyCode::F10 => Some("F10".to_string()),
-        KeyCode::F11 => Some("F11".to_string()),
-        KeyCode::F12 => Some("F12".to_string()),
-        _ => None,
-    }
-}
-
-#[derive(Clone, Copy)]
-pub struct Key(pub KeyCode);
\ No newline at end of file
diff --git a/eucalyptus/src/scripting/lighting.rs b/eucalyptus/src/scripting/lighting.rs
deleted file mode 100644
index 0e416e3..0000000
--- a/eucalyptus/src/scripting/lighting.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-use rustyscript::Runtime;
-
-use crate::scripting::ScriptableModule;
-
-pub struct Lighting;
-
-impl ScriptableModule for Lighting {
-    fn register(_runtime: &mut Runtime) -> anyhow::Result<()> {
-        Ok(())
-    }
-}
\ No newline at end of file
diff --git a/eucalyptus/src/scripting/math.rs b/eucalyptus/src/scripting/math.rs
deleted file mode 100644
index 761f08d..0000000
--- a/eucalyptus/src/scripting/math.rs
+++ /dev/null
@@ -1,166 +0,0 @@
-use dropbear_engine::entity::Transform;
-use glam::{DQuat, DVec3};
-use rustyscript::{serde_json, Runtime};
-
-use crate::scripting::ScriptableModule;
-
-pub struct Math;
-
-impl ScriptableModule for Math {
-    fn register(runtime: &mut Runtime) -> anyhow::Result<()> {
-        runtime.register_function("createTransform", |_args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            let transform = Transform::new();
-            serde_json::to_value(transform)
-                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Transform: {}", e)))
-        })?;
-
-        runtime.register_function("transformTranslate", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 2 {
-                return Err(rustyscript::Error::Runtime("transformTranslate requires 2 arguments".to_string()));
-            }
-            
-            let mut transform: Transform = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid transform: {}", e)))?;
-            
-            let translation = if let Some(array) = args[1].as_array() {
-                if array.len() != 3 {
-                    return Err(rustyscript::Error::Runtime("Translation array must have 3 elements".to_string()));
-                }
-                DVec3::new(
-                    array[0].as_f64().unwrap_or(0.0),
-                    array[1].as_f64().unwrap_or(0.0),
-                    array[2].as_f64().unwrap_or(0.0),
-                )
-            } else {
-                return Err(rustyscript::Error::Runtime("Translation must be an array".to_string()));
-            };
-            
-            transform.position += translation;
-            serde_json::to_value(transform)
-                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Transform: {}", e)))
-        })?;
-
-        runtime.register_function("transformRotateX", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 2 {
-                return Err(rustyscript::Error::Runtime("transformRotateX requires 2 arguments".to_string()));
-            }
-            
-            let mut transform: Transform = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid transform: {}", e)))?;
-            
-            let angle = args[1].as_f64().unwrap_or(0.0);
-            let rotation = DQuat::from_rotation_x(angle);
-            transform.rotation = rotation * transform.rotation;
-            
-            serde_json::to_value(transform)
-                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Transform: {}", e)))
-        })?;
-
-        runtime.register_function("transformRotateY", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 2 {
-                return Err(rustyscript::Error::Runtime("transformRotateY requires 2 arguments".to_string()));
-            }
-            
-            let mut transform: Transform = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid transform: {}", e)))?;
-            
-            let angle = args[1].as_f64().unwrap_or(0.0);
-            let rotation = DQuat::from_rotation_y(angle);
-            transform.rotation = rotation * transform.rotation;
-            
-            serde_json::to_value(transform)
-                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Transform: {}", e)))
-        })?;
-
-        runtime.register_function("transformRotateZ", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 2 {
-                return Err(rustyscript::Error::Runtime("transformRotateZ requires 2 arguments".to_string()));
-            }
-            
-            let mut transform: Transform = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid transform: {}", e)))?;
-            
-            let angle = args[1].as_f64().unwrap_or(0.0);
-            let rotation = DQuat::from_rotation_z(angle);
-            transform.rotation = rotation * transform.rotation;
-            
-            serde_json::to_value(transform)
-                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Transform: {}", e)))
-        })?;
-
-        runtime.register_function("transformScale", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 2 {
-                return Err(rustyscript::Error::Runtime("transformScale requires 2 arguments".to_string()));
-            }
-            
-            let mut transform: Transform = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid transform: {}", e)))?;
-            
-            let scale = if let Some(num) = args[1].as_f64() {
-                DVec3::splat(num)
-            } else if let Some(array) = args[1].as_array() {
-                if array.len() != 3 {
-                    return Err(rustyscript::Error::Runtime("Scale array must have 3 elements".to_string()));
-                }
-                DVec3::new(
-                    array[0].as_f64().unwrap_or(1.0),
-                    array[1].as_f64().unwrap_or(1.0),
-                    array[2].as_f64().unwrap_or(1.0),
-                )
-            } else {
-                return Err(rustyscript::Error::Runtime("Scale must be a number or array".to_string()));
-            };
-            
-            transform.scale *= scale;
-            serde_json::to_value(transform)
-                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Transform: {}", e)))
-        })?;
-
-        // this shouldn't be here as there is no need for a matrix...
-        runtime.register_function("transformMatrix", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 1 {
-                return Err(rustyscript::Error::Runtime("transformMatrix requires 1 argument".to_string()));
-            }
-            
-            let transform: Transform = serde_json::from_value(args[0].clone())
-                .map_err(|e| rustyscript::Error::Runtime(format!("Invalid transform: {}", e)))?;
-            
-            let matrix = transform.matrix();
-            serde_json::to_value(matrix)
-                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize matrix: {}", e)))
-        })?;
-
-        runtime.register_function("createVec3", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            let x = args.get(0).and_then(|v| v.as_f64()).unwrap_or(0.0);
-            let y = args.get(1).and_then(|v| v.as_f64()).unwrap_or(0.0);
-            let z = args.get(2).and_then(|v| v.as_f64()).unwrap_or(0.0);
-            
-            let vec3 = DVec3::new(x, y, z);
-            serde_json::to_value(vec3)
-                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Vec3: {}", e)))
-        })?;
-
-        runtime.register_function("createQuatIdentity", |_args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            let quat = DQuat::IDENTITY;
-            serde_json::to_value(quat)
-                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Quaternion: {}", e)))
-        })?;
-
-        runtime.register_function("createQuatFromEuler", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            if args.len() != 3 {
-                return Err(rustyscript::Error::Runtime("createQuatFromEuler requires 3 arguments".to_string()));
-            }
-            
-            let x = args[0].as_f64().unwrap_or(0.0);
-            let y = args[1].as_f64().unwrap_or(0.0);
-            let z = args[2].as_f64().unwrap_or(0.0);
-            
-            let quat = DQuat::from_euler(glam::EulerRot::XYZ, x, y, z);
-            serde_json::to_value(quat)
-                .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Quaternion: {}", e)))
-        })?;
-
-        log::info!("[Script] Initialised math module");
-        Ok(())
-    }
-}
\ No newline at end of file
diff --git a/eucalyptus/src/scripting/mod.rs b/eucalyptus/src/scripting/mod.rs
deleted file mode 100644
index 879effe..0000000
--- a/eucalyptus/src/scripting/mod.rs
+++ /dev/null
@@ -1,833 +0,0 @@
-pub mod camera;
-pub mod entity;
-pub mod math;
-pub mod input;
-pub mod lighting;
-
-use dropbear_engine::camera::Camera;
-use dropbear_engine::entity::{AdoptedEntity, Transform};
-use dropbear_engine::lighting::{Light, LightComponent};
-use glam::DVec3;
-use hecs::World;
-use rustyscript::{serde_json, Module, ModuleHandle, Runtime, RuntimeOptions};
-use std::path::PathBuf;
-use std::{collections::HashMap, fs};
-
-/// A trait that describes a module that can be registered. 
-pub trait ScriptableModule {
-    /// Registers the functions for the dropbear typescript API
-    fn register(runtime: &mut Runtime) -> anyhow::Result<()>;
-    // /// Gathers the information into a serializable format that can be sent over to the 
-    // /// dropbear typescript API
-    // fn gather(world: &World, entity_id: hecs::Entity);
-    // /// Fetches the mutated information from the dropbear typescript module and applys it to the world
-    // fn release(world: &mut World, entity_id: hecs::Entity, data: &serde_json::Value) -> anyhow::Result<()>;
-}
-
-use crate::camera::CameraComponent;
-use crate::states::{EntityNode, ModelProperties, ScriptComponent, PROJECT, SOURCE};
-
-pub const TEMPLATE_SCRIPT: &'static str = include_str!("../template.ts");
-
-pub enum ScriptAction {
-    AttachScript {
-        script_path: PathBuf,
-        script_name: String,
-    },
-    CreateAndAttachScript {
-        script_path: PathBuf,
-        script_name: String,
-    },
-    RemoveScript,
-    EditScript,
-}
-
-pub fn move_script_to_src(script_path: &PathBuf) -> anyhow::Result<PathBuf> {
-    let project_path = {
-        let project = PROJECT.read().unwrap();
-        project.project_path.clone()
-    };
-
-    let src_path = {
-        let source_config = SOURCE.read().unwrap();
-        source_config.path.clone()
-    };
-
-    let scripts_dir = src_path;
-    fs::create_dir_all(&scripts_dir)?;
-
-    let filename = script_path
-        .file_name()
-        .ok_or_else(|| anyhow::anyhow!("Invalid script path: no filename"))?;
-
-    let dest_path = scripts_dir.join(filename);
-
-    if dest_path.exists() {
-        log::info!(
-            "Script file already exists at {:?}, returning existing path",
-            dest_path
-        );
-        return Ok(dest_path);
-    }
-
-    const MAX_RETRIES: usize = 5;
-    const RETRY_DELAY_MS: u64 = 60;
-
-    let mut last_err: Option<std::io::Error> = None;
-    for attempt in 0..=MAX_RETRIES {
-        match fs::copy(script_path, &dest_path) {
-            Ok(_) => {
-                log::info!("Copied script from {:?} to {:?}", script_path, dest_path);
-                last_err = None;
-                break;
-            }
-            Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
-                log::warn!(
-                    "Script file already exists at {:?}, continuing anyway",
-                    dest_path
-                );
-                last_err = None;
-                break;
-            }
-            Err(e) => {
-                if e.raw_os_error() == Some(32) && attempt < MAX_RETRIES {
-                    log::warn!(
-                        "Sharing violation copying script (attempt {}/{}). Retrying in {}ms: {}",
-                        attempt + 1,
-                        MAX_RETRIES,
-                        RETRY_DELAY_MS,
-                        e
-                    );
-                    std::thread::sleep(std::time::Duration::from_millis(RETRY_DELAY_MS));
-                    last_err = Some(e);
-                    continue;
-                } else {
-                    return Err(e.into());
-                }
-            }
-        }
-    }
-    if let Some(e) = last_err {
-        return Err(e.into());
-    }
-
-    {
-        let source_config = SOURCE.read().unwrap();
-        source_config.write_to(&project_path)?;
-    }
-
-    log::info!("Moved script from {:?} to {:?}", script_path, dest_path);
-    Ok(dest_path)
-}
-
-pub fn convert_entity_to_group(
-    world: &World,
-    entity_id: hecs::Entity,
-) -> anyhow::Result<EntityNode> {
-    if let Ok(mut query) = world.query_one::<(&AdoptedEntity, &Transform)>(entity_id) {
-        if let Some((adopted, _transform)) = query.get() {
-            let entity_name = adopted.model().label.clone();
-
-            let script_node = if let Ok(script) = world.get::<&ScriptComponent>(entity_id) {
-                Some(EntityNode::Script {
-                    name: script.name.clone(),
-                    path: script.path.clone(),
-                })
-            } else {
-                None
-            };
-
-            let entity_node = EntityNode::Entity {
-                id: entity_id,
-                name: entity_name.clone(),
-            };
-
-            if let Some(script_node) = script_node {
-                Ok(EntityNode::Group {
-                    name: entity_name,
-                    children: vec![entity_node, script_node],
-                    collapsed: false,
-                })
-            } else {
-                Ok(entity_node)
-            }
-        } else {
-            Err(anyhow::anyhow!("Failed to get entity components"))
-        }
-    } else {
-        Err(anyhow::anyhow!("Failed to query entity {:?}", entity_id))
-    }
-}
-
-pub fn attach_script_to_entity(
-    world: &mut World,
-    entity_id: hecs::Entity,
-    script_component: ScriptComponent,
-) -> anyhow::Result<()> {
-    if let Err(e) = world.insert_one(entity_id, script_component) {
-        return Err(anyhow::anyhow!("Failed to attach script to entity: {}", e));
-    }
-
-    log::info!("Successfully attached script to entity {:?}", entity_id);
-    Ok(())
-}
-
-pub struct ScriptManager {
-    pub runtime: Runtime,
-    compiled_scripts: HashMap<String, ModuleHandle>,
-    entity_script_data: HashMap<hecs::Entity, serde_json::Value>,
-}
-
-impl ScriptManager {
-    pub fn new() -> anyhow::Result<Self> {
-        let mut runtime = Runtime::new(RuntimeOptions::default())?;
-        let dropbear_content = include_str!("../dropbear.ts");
-        let dropbear_module = Module::new("./dropbear.ts", dropbear_content);
-        runtime.load_module(&dropbear_module)?;
-        log::debug!("Loaded dropbear module");
-
-        let mut result = Self {
-            runtime,
-            compiled_scripts: HashMap::new(),
-            entity_script_data: HashMap::new(),
-        };
-
-        result = result
-            .register_module::<input::InputState>()?
-            .register_module::<math::Math>()?
-            .register_module::<ModelProperties>()?
-            .register_module::<camera::SerializableCamera>()?
-            .register_module::<lighting::Lighting>()?;
-
-        // Register utility functions
-        result.runtime.register_function("log", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            let msg = args.get(0)
-                .and_then(|v| v.as_str())
-                .unwrap_or("undefined");
-            println!("[Script] {}", msg);
-            Ok(serde_json::Value::Null)
-        })?;
-
-        result.runtime.register_function("time", |_args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
-            let time = std::time::SystemTime::now()
-                .duration_since(std::time::UNIX_EPOCH)
-                .unwrap()
-                .as_secs_f64();
-            Ok(serde_json::Value::Number(serde_json::Number::from_f64(time).unwrap()))
-        })?;
-        log::debug!("Initialised ScriptManager");
-        Ok(result)
-    }
-
-    /// Allows for the registering of other modules to use with the dropbear typescript
-    /// API
-    /// 
-    /// # Parameters
-    /// * A turbofish to a struct that uses the ScriptableModule trait
-    /// 
-    /// # Examples
-    /// ```rust
-    /// let script_manager = ScriptManager::new()?
-    ///     .register_module::<input::InputState>()?
-    ///     .register_module::<camera::Camera>()?
-    ///     .register_module::<lighting::LightingModule>()?;
-    /// ```
-    pub fn register_module<T: ScriptableModule>(mut self) -> anyhow::Result<Self> {
-        T::register(&mut self.runtime)?;
-        Ok(self)
-    }
-
-    pub fn init_entity_script(
-        &mut self,
-        entity_id: hecs::Entity,
-        script_name: &str,
-        world: &mut World,
-        input_state: &input::InputState,
-    ) -> anyhow::Result<()> {
-        log_once::debug_once!("init_entity_script: {} for {:?}", script_name, entity_id);
-
-        if let Some(module) = self.compiled_scripts.get(script_name).cloned() {
-            // construct RawSceneData-like payload
-            let mut scene_map = serde_json::Map::new();
-
-            // entities array
-            let mut entities_arr: Vec<serde_json::Value> = Vec::new();
-            for (id, (adopted, transform)) in world.query::<(&AdoptedEntity, &Transform)>().iter() {
-                // try to get ModelProperties for this entity
-                let props_value = if let Ok(mut pq) = world.query_one::<&ModelProperties>(id) {
-                    if let Some(p) = pq.get() {
-                        serde_json::to_value(p)?
-                    } else {
-                        serde_json::Value::Object(serde_json::Map::new())
-                    }
-                } else {
-                    serde_json::Value::Object(serde_json::Map::new())
-                };
-
-                let mut ent_obj = serde_json::Map::new();
-                ent_obj.insert("label".to_string(), serde_json::Value::String(adopted.label().clone()));
-
-                // properties object expected shape: { custom_properties: Record<string, any> } or full ModelProperties
-                ent_obj.insert("properties".to_string(), props_value);
-
-                ent_obj.insert("transform".to_string(), serde_json::to_value(transform)?);
-
-                entities_arr.push(serde_json::Value::Object(ent_obj));
-            }
-            scene_map.insert("entities".to_string(), serde_json::Value::Array(entities_arr));
-
-            // cameras snapshot (keep minimal fields)
-            let mut cameras_arr: Vec<serde_json::Value> = Vec::new();
-            for (_id, (cam, _comp)) in world.query::<(&Camera, &CameraComponent)>().iter() {
-                cameras_arr.push(serde_json::json!({
-                    "label": cam.label,
-                    "data": {
-                        "eye": cam.eye.to_array(),
-                        "target": cam.target.to_array(),
-                        "up": cam.up.to_array(),
-                        "aspect": cam.aspect,
-                        "fov": cam.fov_y,
-                        "near": cam.znear,
-                        "far": cam.zfar,
-                        "yaw": cam.yaw,
-                        "pitch": cam.pitch,
-                        "speed": cam.speed,
-                        "sensitivity": cam.sensitivity
-                    }
-                }));
-            }
-            scene_map.insert("cameras".to_string(), serde_json::Value::Array(cameras_arr));
-
-            // lights snapshot (label only)
-            let mut lights_arr: Vec<serde_json::Value> = Vec::new();
-            for (_id, (_transform, _light_comp, light)) in world.query::<(&Transform, &LightComponent, &Light)>().iter() {
-                lights_arr.push(serde_json::json!({
-                    "label": light.label(),
-                }));
-            }
-            scene_map.insert("lights".to_string(), serde_json::Value::Array(lights_arr));
-
-            // current entity label (if available)
-            if let Ok(mut q) = world.query_one::<&AdoptedEntity>(entity_id) {
-                if let Some(adopted) = q.get() {
-                    scene_map.insert("current_entity".to_string(), serde_json::Value::String(adopted.label().clone()));
-                }
-            }
-
-            // input state
-            let serializable_input = input::SerializableInputState::from(input_state);
-            let mut payload = serde_json::Map::new();
-            payload.insert("scene".to_string(), serde_json::Value::Object(scene_map));
-            payload.insert("input".to_string(), serde_json::to_value(&serializable_input)?);
-
-            let script_data_value = serde_json::Value::Object(payload);
-
-            // call onLoad (module scripts call dropbear.start(s) and return dropbear.end())
-            match self.runtime.call_function::<serde_json::Value>(Some(&module), "onLoad", &vec![script_data_value.clone()]) {
-                Ok(result) => {
-                    log::debug!("Called onLoad for entity {:?}", entity_id);
-                    // apply whatever the script returned (expect Partial RawSceneData shape)
-                    let _ = self.apply_script_data_to_world(entity_id, &result, world);
-                    // store initial data (script-managed)
-                    self.entity_script_data.insert(entity_id, result);
-                }
-                Err(e) => {
-                    log::warn!("onLoad missing/failed for entity {:?}: {}", entity_id, e);
-                    // store the payload we sent so future updates have context
-                    self.entity_script_data.insert(entity_id, script_data_value);
-                }
-            }
-
-            Ok(())
-        } else {
-            Err(anyhow::anyhow!("Script '{}' not found", script_name))
-        }
-    }
-
-    pub fn update_entity_script(
-        &mut self,
-        entity_id: hecs::Entity,
-        script_name: &str,
-        world: &mut World,
-        input_state: &input::InputState,
-        dt: f32,
-    ) -> anyhow::Result<()> {
-        log_once::debug_once!("Update entity script name: {}", script_name);
-
-        if let Some(module) = self.compiled_scripts.get(script_name).cloned() {
-            // Build the same RawSceneData-like payload as init
-            let mut scene_map = serde_json::Map::new();
-
-            let mut entities_arr: Vec<serde_json::Value> = Vec::new();
-            for (id, (adopted, transform)) in world.query::<(&AdoptedEntity, &Transform)>().iter() {
-                let props_value = if let Ok(mut pq) = world.query_one::<&ModelProperties>(id) {
-                    if let Some(p) = pq.get() {
-                        serde_json::to_value(p)?
-                    } else {
-                        serde_json::Value::Object(serde_json::Map::new())
-                    }
-                } else {
-                    serde_json::Value::Object(serde_json::Map::new())
-                };
-
-                let mut ent_obj = serde_json::Map::new();
-                ent_obj.insert("label".to_string(), serde_json::Value::String(adopted.label().clone()));
-                ent_obj.insert("properties".to_string(), props_value);
-                ent_obj.insert("transform".to_string(), serde_json::to_value(transform)?);
-                entities_arr.push(serde_json::Value::Object(ent_obj));
-            }
-            scene_map.insert("entities".to_string(), serde_json::Value::Array(entities_arr));
-
-            // cameras
-            let mut cameras_arr: Vec<serde_json::Value> = Vec::new();
-            for (_id, (cam, _comp)) in world.query::<(&Camera, &CameraComponent)>().iter() {
-                cameras_arr.push(serde_json::json!({
-                    "label": cam.label,
-                    "data": {
-                        "eye": cam.eye.to_array(),
-                        "target": cam.target.to_array(),
-                        "up": cam.up.to_array(),
-                        "aspect": cam.aspect,
-                        "fov": cam.fov_y,
-                        "near": cam.znear,
-                        "far": cam.zfar,
-                        "yaw": cam.yaw,
-                        "pitch": cam.pitch,
-                        "speed": cam.speed,
-                        "sensitivity": cam.sensitivity
-                    }
-                }));
-            }
-            scene_map.insert("cameras".to_string(), serde_json::Value::Array(cameras_arr));
-
-            // lights
-            let mut lights_arr: Vec<serde_json::Value> = Vec::new();
-            for (_id, (_transform, _light_comp, light)) in world.query::<(&Transform, &LightComponent, &Light)>().iter() {
-                lights_arr.push(serde_json::json!({
-                    "label": light.label(),
-                }));
-            }
-            scene_map.insert("lights".to_string(), serde_json::Value::Array(lights_arr));
-
-            // current entity label
-            if let Ok(mut q) = world.query_one::<&AdoptedEntity>(entity_id) {
-                if let Some(adopted) = q.get() {
-                    scene_map.insert("current_entity".to_string(), serde_json::Value::String(adopted.label().clone()));
-                }
-            }
-
-            let serializable_input = input::SerializableInputState::from(input_state);
-            let mut payload = serde_json::Map::new();
-            payload.insert("scene".to_string(), serde_json::Value::Object(scene_map));
-            payload.insert("input".to_string(), serde_json::to_value(&serializable_input)?);
-
-            let script_data_value = serde_json::Value::Object(payload);
-            let dt_value = serde_json::Value::Number(serde_json::Number::from_f64(dt as f64).unwrap());
-
-            // call onUpdate(s, dt)
-            match self.runtime.call_function::<serde_json::Value>(Some(&module), "onUpdate", &vec![script_data_value.clone(), dt_value]) {
-                Ok(result) => {
-                    log::trace!("Called update for entity {:?}", entity_id);
-                    // delegate applying of returned data to the helper
-                    let _ = self.apply_script_data_to_world(entity_id, &result, world);
-                    // store latest returned data
-                    self.entity_script_data.insert(entity_id, result);
-                }
-                Err(e) => {
-                    log_once::error_once!("Script execution error for entity {:?}: {}", entity_id, e);
-                }
-            }
-        } else {
-            log_once::error_once!("Unable to fetch compiled scripts for entity {:?}. Script Name: {}", entity_id, script_name);
-        }
-        Ok(())
-    }
-
-    fn apply_script_data_to_world(
-        &self,
-        entity_id: hecs::Entity,
-        script_data: &serde_json::Value,
-        world: &mut World,
-    ) -> anyhow::Result<()> {
-        if let Some(obj) = script_data.as_object() {
-            if let Some(entities_val) = obj.get("entities").and_then(|v| v.as_array()) {
-                for ent in entities_val {
-                    if let Some(ent_obj) = ent.as_object() {
-                        if let Some(label) = ent_obj.get("label").and_then(|l| l.as_str()) {
-                            if let Some(target_entity) = world
-                                .query_mut::<(&AdoptedEntity,)>()
-                                .into_iter()
-                                .find_map(|(e, (adopted,))| {
-                                    if adopted.label() == label {
-                                        Some(e)
-                                    } else {
-                                        None
-                                    }
-                                }) {
-                                if let Some(transform_val) = ent_obj.get("transform") {
-                                    if let Ok(new_t) = serde_json::from_value::<Transform>(transform_val.clone()) {
-                                        if let Ok(mut tq) = world.query_one::<&mut Transform>(target_entity) {
-                                            if let Some(tref) = tq.get() {
-                                                *tref = new_t;
-                                                log::trace!("apply: updated transform for {:?}", target_entity);
-                                            }
-                                        }
-                                    } else {
-                                        log::trace!("apply: failed to deserialize transform for label '{}'", label);
-                                    }
-                                }
-                                if let Some(props_val) = ent_obj.get("properties") {
-                                    if let Ok(new_props) = serde_json::from_value::<ModelProperties>(props_val.clone()) {
-                                        if let Ok(mut pq) = world.query_one::<&mut ModelProperties>(target_entity) {
-                                            if let Some(p) = pq.get() {
-                                                *p = new_props;
-                                                log::trace!("apply: updated properties for {:?}", target_entity);
-                                            }
-                                        } else {
-                                            if let Err(e) = world.insert_one(target_entity, new_props) {
-                                                log::warn!("apply: failed to insert properties for {:?}: {}", target_entity, e);
-                                            }
-                                        }
-                                    } else {
-                                        log::trace!("apply: failed to deserialize properties for label '{}'", label);
-                                    }
-                                }
-                            } else {
-                                log::trace!("apply: no entity in world matching label '{}'", label);
-                            }
-                        }
-                    }
-                }
-            } else {
-                // No entities array: treat the object as direct payload for the single entity
-                // apply transform if present
-                if let Some(transform_value) = obj.get("transform") {
-                    if let Ok(updated_transform) = serde_json::from_value::<Transform>(transform_value.clone()) {
-                        if let Ok(mut transform_query) = world.query_one::<&mut Transform>(entity_id) {
-                            if let Some(transform) = transform_query.get() {
-                                *transform = updated_transform;
-                                log::trace!("apply: Updated transform for entity {:?}", entity_id);
-                            }
-                        }
-                    }
-                }
-                // apply properties if present (support "properties" or legacy "entity")
-                if let Some(props_value) = obj.get("properties").or_else(|| obj.get("entity")) {
-                    if let Ok(updated_properties) = serde_json::from_value::<ModelProperties>(props_value.clone()) {
-                        if let Ok(mut properties_query) = world.query_one::<&mut ModelProperties>(entity_id) {
-                            if let Some(properties) = properties_query.get() {
-                                *properties = updated_properties;
-                                log::trace!("apply: Updated properties for entity {:?}", entity_id);
-                            }
-                        } else {
-                            if let Err(e) = world.insert_one(entity_id, updated_properties) {
-                                log::warn!("apply: Failed to insert updated properties for entity {:?}: {}", entity_id, e);
-                            }
-                        }
-                    }
-                }
-
-                // cameras: if script returned camera array with full data, apply by label
-                if let Some(cameras_val) = obj.get("cameras").and_then(|v| v.as_array()) {
-                    for cam in cameras_val {
-                        if let Some(cam_obj) = cam.as_object() {
-                            if let Some(label) = cam_obj.get("label").and_then(|l| l.as_str()) {
-                                if let Some(data_obj) = cam_obj.get("data").and_then(|d| d.as_object()) {
-                                    // find camera entity by label
-                                    if let Some(cam_entity) = world
-                                        .query::<&Camera>()
-                                        .iter()
-                                        .find_map(|(e, camera)| if camera.label == label { Some(e) } else { None }) {
-                                        if let Ok(mut cam_q) = world.query_one::<&mut Camera>(cam_entity) {
-                                            if let Some(cam_struct) = cam_q.get() {
-                                                // update common camera fields if present
-                                                if let Some(eye_val) = data_obj.get("eye") {
-                                                    if let Ok(arr) = serde_json::from_value::<[f64; 3]>(eye_val.clone()) {
-                                                        cam_struct.eye = DVec3::from_array(arr);
-                                                    }
-                                                }
-                                                if let Some(target_val) = data_obj.get("target") {
-                                                    if let Ok(arr) = serde_json::from_value::<[f64; 3]>(target_val.clone()) {
-                                                        cam_struct.target = DVec3::from_array(arr);
-                                                    }
-                                                }
-                                                if let Some(up_val) = data_obj.get("up") {
-                                                    if let Ok(arr) = serde_json::from_value::<[f64; 3]>(up_val.clone()) {
-                                                        cam_struct.up = DVec3::from_array(arr);
-                                                    }
-                                                }
-                                                if let Some(aspect) = data_obj.get("aspect").and_then(|v| v.as_f64()) {
-                                                    cam_struct.aspect = aspect;
-                                                }
-                                                if let Some(fov) = data_obj.get("fov").and_then(|v| v.as_f64()) {
-                                                    cam_struct.fov_y = fov;
-                                                }
-                                                if let Some(near) = data_obj.get("near").and_then(|v| v.as_f64()) {
-                                                    cam_struct.znear = near;
-                                                }
-                                                if let Some(far) = data_obj.get("far").and_then(|v| v.as_f64()) {
-                                                    cam_struct.zfar = far;
-                                                }
-                                                if let Some(yaw) = data_obj.get("yaw").and_then(|v| v.as_f64()) {
-                                                    cam_struct.yaw = yaw;
-                                                }
-                                                if let Some(pitch) = data_obj.get("pitch").and_then(|v| v.as_f64()) {
-                                                    cam_struct.pitch = pitch;
-                                                }
-                                                if let Some(speed) = data_obj.get("speed").and_then(|v| v.as_f64()) {
-                                                    cam_struct.speed = speed;
-                                                }
-                                                if let Some(sensitivity) = data_obj.get("sensitivity").and_then(|v| v.as_f64()) {
-                                                    cam_struct.sensitivity = sensitivity;
-                                                }
-                                                log::trace!("apply: Updated camera '{}'", label);
-                                            }
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        } else {
-            log::trace!("apply: script_data not an object for entity {:?}", entity_id);
-        }
-
-        Ok(())
-    }
-
-    pub fn load_script_from_source(&mut self, script_name: &String, script_content: &String) -> anyhow::Result<String> {
-        let module = Module::new(&script_name, script_content);
-        
-        match self.runtime.load_module(&module) {
-            Ok(module) => {
-                self.compiled_scripts.insert(script_name.clone(), module);
-                log::info!("Loaded script: {}", script_name);
-                Ok(script_name.to_string())
-            }
-            Err(e) => {
-                log::error!("Compiling module for script [{}] returned an error: {}", script_name, e);
-                Err(e.into())
-            }
-        }
-    }
-
-    pub fn load_script(&mut self, script_path: &PathBuf) -> anyhow::Result<String> {
-        log::debug!("Reading script content");
-        let script_content = fs::read_to_string(script_path)?;
-        log::debug!("Fetching script name");
-        let script_name = script_path
-            .file_name()
-            .unwrap_or_default()
-            .to_string_lossy()
-            .to_string();
-        log::debug!("Script name: {}", script_name);
-
-        log::debug!("Creating module for typescript runtime");
-        let module = Module::new(&script_name, &script_content);
-        
-        log::debug!("Loading module");
-        match self.runtime.load_module(&module) {
-            Ok(module) => {
-                self.compiled_scripts.insert(script_name.clone(), module);
-                log::info!("Loaded script: {}", script_name);
-                Ok(script_name)
-            }
-            Err(e) => {
-                log::error!("Compiling module for script path [{}] returned an error: {}", script_path.display(), e);
-                Err(e.into())
-            }
-        }
-    }
-
-    pub fn remove_entity_script(&mut self, entity_id: hecs::Entity) {
-        self.entity_script_data.remove(&entity_id);
-    }
-
-    pub fn handle_camera_actions(&self, world: &mut hecs::World, active_camera: &mut Option<hecs::Entity>, actions: Vec<serde_json::Value>) -> anyhow::Result<()> {
-        use crate::camera::{CameraComponent, CameraType};
-        use dropbear_engine::camera::Camera;
-
-        for action_value in actions {
-            if let Ok(action) = serde_json::from_value::<crate::scripting::camera::CameraAction>(action_value) {
-                match action.action.as_str() {
-                    "switch_camera" => {
-                        if let Some(label) = action.label {
-                            let camera_entity = world
-                                .query::<&Camera>()
-                                .iter()
-                                .find(|(_, camera)| camera.label == label)
-                                .map(|(entity, _)| entity);
-
-                            if let Some(entity) = camera_entity {
-                                *active_camera = Some(entity);
-                                log::info!("Switched to camera with label: {}", label);
-                            } else {
-                                log::warn!("Camera with label '{}' not found", label);
-                            }
-                        }
-                    }
-                    "get_active_camera" => {
-                        if let Some(active_entity) = active_camera {
-                            if let Ok(camera) = world.get::<&Camera>(*active_entity) {
-                                log::info!("Active camera label: {}", camera.label);
-                            }
-                        }
-                    }
-                    "get_all_cameras" => {
-                        let labels: Vec<String> = world
-                            .query::<&Camera>()
-                            .iter()
-                            .map(|(_, camera)| camera.label.clone())
-                            .collect();
-                        log::info!("All camera labels: {:?}", labels);
-                    }
-                    "get_cameras_by_type" => {
-                        if let Some(type_str) = action.camera_type {
-                            let target_type = match type_str.as_str() {
-                                "Normal" => CameraType::Normal,
-                                "Debug" => CameraType::Debug,
-                                "Player" => CameraType::Player,
-                                _ => continue,
-                            };
-
-                            let labels: Vec<String> = world
-                                .query::<(&Camera, &CameraComponent)>()
-                                .iter()
-                                .filter_map(|(_, (camera, component))| {
-                                    if component.camera_type == target_type {
-                                        Some(camera.label.clone())
-                                    } else {
-                                        None
-                                    }
-                                })
-                                .collect();
-                            log::info!("Cameras of type {:?}: {:?}", target_type, labels);
-                        }
-                    }
-                    "manipulate_camera" => {
-                        if let (Some(label), Some(property), Some(value)) = (action.label, action.property, action.value) {
-                            let camera_entity = world
-                                .query::<&Camera>()
-                                .iter()
-                                .find(|(_, camera)| camera.label == label)
-                                .map(|(entity, _)| entity);
-
-                            if let Some(entity) = camera_entity {
-                                match property.as_str() {
-                                    "position" => {
-                                        if let Ok(pos_array) = serde_json::from_value::<[f64; 3]>(value) {
-                                            if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
-                                                camera.eye = DVec3::from_array(pos_array);
-                                                log::info!("Updated camera '{}' position to {:?}", label, pos_array);
-                                            }
-                                        }
-                                    }
-                                    "target" => {
-                                        if let Ok(target_array) = serde_json::from_value::<[f64; 3]>(value) {
-                                            if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
-                                                camera.target = DVec3::from_array(target_array);
-                                                log::info!("Updated camera '{}' target to {:?}", label, target_array);
-                                            }
-                                        }
-                                    }
-                                    "speed" => {
-                                        if let Some(speed) = value.as_f64() {
-                                            if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
-                                                camera.speed = speed;
-                                            }
-                                            if let Ok(mut component) = world.get::<&mut CameraComponent>(entity) {
-                                                component.speed = speed;
-                                            }
-                                            log::info!("Updated camera '{}' speed to {}", label, speed);
-                                        }
-                                    }
-                                    "sensitivity" => {
-                                        if let Some(sensitivity) = value.as_f64() {
-                                            if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
-                                                camera.sensitivity = sensitivity;
-                                            }
-                                            if let Ok(mut component) = world.get::<&mut CameraComponent>(entity) {
-                                                component.sensitivity = sensitivity;
-                                            }
-                                            log::info!("Updated camera '{}' sensitivity to {}", label, sensitivity);
-                                        }
-                                    }
-                                    "fov" => {
-                                        if let Some(fov) = value.as_f64() {
-                                            if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
-                                                camera.fov_y = fov;
-                                            }
-                                            if let Ok(mut component) = world.get::<&mut CameraComponent>(entity) {
-                                                component.fov_y = fov;
-                                            }
-                                            log::info!("Updated camera '{}' FOV to {}", label, fov);
-                                        }
-                                    }
-                                    "yaw" => {
-                                        if let Some(yaw) = value.as_f64() {
-                                            if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
-                                                camera.yaw = yaw;
-                                            }
-                                            log::info!("Updated camera '{}' yaw to {}", label, yaw);
-                                        }
-                                    }
-                                    "pitch" => {
-                                        if let Some(pitch) = value.as_f64() {
-                                            if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
-                                                camera.pitch = pitch;
-                                            }
-                                            log::info!("Updated camera '{}' pitch to {}", label, pitch);
-                                        }
-                                    }
-                                    _ => {
-                                        log::warn!("Unknown camera property: {}", property);
-                                    }
-                                }
-                            } else {
-                                log::warn!("Camera with label '{}' not found for manipulation", label);
-                            }
-                        }
-                    }
-                    "get_camera_by_label" => {
-                        if let Some(label) = action.label {
-                            log::info!("Getting camera data for label: {}", label);
-                        }
-                    }
-                    "set_camera_by_label" => {
-                        if let (Some(label), Some(camera_data)) = (action.label, action.camera_data) {
-                            // Find and update the entire camera
-                            let camera_entity = world
-                                .query::<&Camera>()
-                                .iter()
-                                .find(|(_, camera)| camera.label == label)
-                                .map(|(entity, _)| entity);
-
-                            if let Some(entity) = camera_entity {
-                                if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
-                                    camera.eye = camera_data.eye;
-                                    camera.target = camera_data.target;
-                                    camera.up = camera_data.up;
-                                    camera.aspect = camera_data.aspect;
-                                    camera.fov_y = camera_data.fov;
-                                    camera.znear = camera_data.near;
-                                    camera.zfar = camera_data.far;
-                                    camera.yaw = camera_data.yaw;
-                                    camera.pitch = camera_data.pitch;
-                                    camera.speed = camera_data.speed;
-                                    camera.sensitivity = camera_data.sensitivity;
-                                    log::info!("Updated complete camera data for label: {}", label);
-                                }
-                            }
-                        }
-                    }
-                    _ => {
-                        log::warn!("Unknown camera action: {}", action.action);
-                    }
-                }
-            }
-        }
-        Ok(())
-    }
-}
\ No newline at end of file
diff --git a/eucalyptus/src/shader.wgsl b/eucalyptus/src/shader.wgsl
deleted file mode 100644
index 37903ae..0000000
--- a/eucalyptus/src/shader.wgsl
+++ /dev/null
@@ -1,206 +0,0 @@
-// Main shader for standard objects.
-const MAX_LIGHTS: u32 = 8;
-
-struct CameraUniform {
-    view_pos: vec4<f32>,
-    view_proj: mat4x4<f32>,
-};
-@group(1) @binding(0)
-var<uniform> camera: CameraUniform;
-
-struct Light {
-    position: vec4<f32>, // x, y, z, unused
-    direction: vec4<f32>, // x, y, z, unused
-    color: vec4<f32>, // r, g, b, light_type (0, 1, 2)
-    constant: f32,
-    lin: f32, // linear
-    quadratic: f32,
-    cutoff: f32,
-}
-
-struct LightArray {
-    lights: array<Light, MAX_LIGHTS>,
-    light_count: u32,
-    ambient_strength: f32,
-}
-
-@group(2) @binding(0)
-var<uniform> light_array: LightArray;
-
-struct InstanceInput {
-    @location(5) model_matrix_0: vec4<f32>,
-    @location(6) model_matrix_1: vec4<f32>,
-    @location(7) model_matrix_2: vec4<f32>,
-    @location(8) model_matrix_3: vec4<f32>,
-
-    @location(9) normal_matrix_0: vec3<f32>,
-    @location(10) normal_matrix_1: vec3<f32>,
-    @location(11) normal_matrix_2: vec3<f32>,
-};
-
-struct VertexInput {
-    @location(0) position: vec3<f32>,
-    @location(1) tex_coords: vec2<f32>,
-    @location(2) normal: vec3<f32>,
-};
-
-struct VertexOutput {
-    @builtin(position) clip_position: vec4<f32>,
-    @location(0) tex_coords: vec2<f32>,
-    @location(1) world_normal: vec3<f32>,
-    @location(2) world_position: vec3<f32>,
-};
-
-@vertex
-fn vs_main(
-    model: VertexInput,
-    instance: InstanceInput,
-) -> VertexOutput {
-    let model_matrix = mat4x4<f32>(
-        instance.model_matrix_0,
-        instance.model_matrix_1,
-        instance.model_matrix_2,
-        instance.model_matrix_3,
-    );
-    let normal_matrix = mat3x3<f32>(
-        instance.normal_matrix_0,
-        instance.normal_matrix_1,
-        instance.normal_matrix_2,
-    );
-    var out: VertexOutput;
-    out.tex_coords = model.tex_coords;
-    out.world_normal = normal_matrix * model.normal;
-    var world_position: vec4<f32> = model_matrix * vec4<f32>(model.position, 1.0);
-    out.world_position = world_position.xyz;
-    out.clip_position = camera.view_proj * world_position;
-    return out;
-}
-
-@group(0) @binding(0)
-var t_diffuse: texture_2d<f32>;
-@group(0) @binding(1)
-var s_diffuse: sampler;
-
-fn calculate_light(light: Light, world_pos: vec3<f32>, world_normal: vec3<f32>, view_dir: vec3<f32>) -> vec3<f32> {
-    let light_dir = normalize(light.position.xyz - world_pos);
-    
-    // dihfuse
-    let diffuse_strength = max(dot(world_normal, light_dir), 0.0);
-    let diffuse_color = light.color.xyz * diffuse_strength;
-    
-    // specular
-    let half_dir = normalize(view_dir + light_dir);
-    let specular_strength = pow(max(dot(world_normal, half_dir), 0.0), 32.0);
-    let specular_color = specular_strength * light.color.xyz;
-    
-    return diffuse_color + specular_color;
-}
-
-fn directional_light(
-    light: Light,
-    world_normal: vec3<f32>,
-    view_dir: vec3<f32>,
-    tex_color: vec3<f32>
-) -> vec3<f32> {
-    let light_dir = normalize(-light.direction.xyz);
-
-    let ambient = light.color.xyz * light_array.ambient_strength * tex_color;
-
-    let diff = max(dot(world_normal, light_dir), 0.0);
-    let diffuse = light.color.xyz * diff * tex_color;
-
-    let reflect_dir = reflect(-light_dir, world_normal);
-    let spec = pow(max(dot(view_dir, reflect_dir), 0.0), 32.0);
-    let specular = light.color.xyz * spec * tex_color;
-
-    return ambient + diffuse + specular;
-}
-
-// https://learnopengl.com/code_viewer_gh.php?code=src/2.lighting/5.2.light_casters_point/5.2.light_casters.fs
-// deal with later. current issue: it is showing only yellow and white in point light (weird...)
-// note: fixed, forgot to push attenuation values to gpu lol
-fn point_light(light: Light, world_pos: vec3<f32>, world_normal: vec3<f32>, view_dir: vec3<f32>, tex_color: vec3<f32>) -> vec3<f32> {
-    let norm = normalize(world_normal);
-    let light_dir = normalize(light.position.xyz - world_pos);
-    let diff = max(dot(norm, light_dir), 0.0);
-    let diffuse = light.color.xyz * diff * tex_color;
-
-    let shininess = 32.0;
-    let reflect_dir = reflect(-light_dir, norm);
-    let spec = pow(max(dot(view_dir, reflect_dir), 0.0), shininess);
-    let specular = light.color.xyz * spec * tex_color;
-
-    let distance = length(light.position.xyz - world_pos);
-    let attenuation = 1.0 / (light.constant + (light.lin * distance) + (light.quadratic * (distance * distance)));
-
-    return (diffuse + specular) * attenuation;
-}
-
-fn spot_light(light: Light, world_pos: vec3<f32>, world_normal: vec3<f32>, view_dir: vec3<f32>, tex_color: vec3<f32>) -> vec3<f32> {
-    let outer_cutoff = light.direction.w;
-    let ambient = light.color.xyz * light_array.ambient_strength * tex_color;
-
-    let norm = normalize(world_normal);
-    let light_dir = normalize(light.position.xyz - world_pos);
-    let diff = max(dot(norm, light_dir), 0.0);
-    var diffuse = light.color.xyz * diff * tex_color;
-
-    let shininess = 32.0;
-    let reflect_dir = reflect(-light_dir, norm);
-    let spec = pow(max(dot(view_dir, reflect_dir), 0.0), shininess);
-    var specular = light.color.xyz * spec * tex_color;
-
-    let theta = dot(light_dir, normalize(-light.direction.xyz));
-    let epsilon = light.cutoff - outer_cutoff;
-    let intensity = clamp((theta - outer_cutoff) / epsilon, 0.0, 1.0);
-
-    diffuse *= intensity;
-    specular *= intensity;
-
-    let distance = length(light.position.xyz - world_pos);
-    let attenuation = 1.0 / (light.constant + (light.lin * distance) + (light.quadratic * (distance * distance)));
-
-    let ambient_attenuated = ambient * attenuation;
-    let diffuse_attenuated = diffuse * attenuation;
-    let specular_attenuated = specular * attenuation;
-
-    return ambient_attenuated + diffuse_attenuated + specular_attenuated;
-}
-
-@fragment
-fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
-    var tex_color = textureSample(t_diffuse, s_diffuse, in.tex_coords);
-    if (tex_color.a < 0.1) {
-        discard;
-    }
-
-    let view_dir = normalize(camera.view_pos.xyz - in.world_position);
-    let world_normal = normalize(in.world_normal);
-
-    var final_color = vec3<f32>(0.0);
-
-    var total_ambient = vec3<f32>(0.0);
-    for (var i: u32 = 0u; i < light_array.light_count; i = i + 1u) {
-        let light = light_array.lights[i];
-        total_ambient += light.color.xyz * light_array.ambient_strength;
-    }
-
-    for (var i: u32 = 0u; i < light_array.light_count; i = i + 1u) {
-        let light = light_array.lights[i];
-
-        // light type is color.w
-        if light.color.w == 0.0 {
-            final_color += directional_light(light, world_normal, view_dir, tex_color.xyz);
-        } else if light.color.w == 1.0 {
-            // point
-            final_color += point_light(light, in.world_position, world_normal, view_dir, tex_color.xyz);
-        } else if light.color.w == 2.0 {
-            final_color += spot_light(light, in.world_position, world_normal, view_dir, tex_color.xyz);
-        }
-    }
-
-    // Combine ambient and lighting
-    final_color = (total_ambient * tex_color.xyz) + final_color;
-
-    return vec4<f32>(final_color, tex_color.a);
-}
\ No newline at end of file
diff --git a/eucalyptus/src/states.rs b/eucalyptus/src/states.rs
deleted file mode 100644
index 7ecec66..0000000
--- a/eucalyptus/src/states.rs
+++ /dev/null
@@ -1,1019 +0,0 @@
-//! In this module, it will describe all the different types for
-//! storing configuration files (.eucp for project and .eucc for config files for subdirectories).
-//!
-//! There is a singleton that is used for other crates to access,
-//! as well as public structs related to that config and docs (hopefully).
-
-use std::{
-    collections::HashMap,
-    fmt::{self, Display, Formatter},
-    fs,
-    path::PathBuf,
-    sync::RwLock,
-};
-
-use bincode::{Encode, Decode};
-use chrono::Utc;
-use dropbear_engine::{
-    camera::Camera,
-    entity::{AdoptedEntity, Transform},
-    graphics::Graphics, lighting::{Light, LightComponent},
-};
-
-#[cfg(feature = "editor")]
-use egui_dock_fork::DockState;
-
-use glam::DVec3;
-use hecs;
-use log;
-use once_cell::sync::Lazy;
-use ron::ser::PrettyConfig;
-use serde::{Deserialize, Serialize};
-use dropbear_engine::model::Model;
-use dropbear_engine::starter::plane::PlaneBuilder;
-use dropbear_engine::utils::{ResourceReference, ResourceReferenceType};
-use crate::camera::{CameraComponent, CameraFollowTarget, CameraType, DebugCamera};
-#[cfg(feature = "editor")]
-use crate::editor::EditorTab;
-use crate::utils::PROTO_TEXTURE;
-
-pub static PROJECT: Lazy<RwLock<ProjectConfig>> =
-    Lazy::new(|| RwLock::new(ProjectConfig::default()));
-
-pub static RESOURCES: Lazy<RwLock<ResourceConfig>> =
-    Lazy::new(|| RwLock::new(ResourceConfig::default()));
-
-pub static SOURCE: Lazy<RwLock<SourceConfig>> = Lazy::new(|| RwLock::new(SourceConfig::default()));
-
-pub static SCENES: Lazy<RwLock<Vec<SceneConfig>>> = Lazy::new(|| RwLock::new(Vec::new()));
-
-/// The root config file, responsible for building and other metadata.
-///
-/// # Location
-/// This file is {project_name}.eucp and is located at {project_dir}/
-#[derive(Debug, Deserialize, Serialize, Default)]
-#[cfg(feature = "editor")]
-pub struct ProjectConfig {
-    pub project_name: String,
-    pub project_path: PathBuf,
-    pub date_created: String,
-    pub date_last_accessed: String,
-    #[serde(default)]
-    pub dock_layout: Option<DockState<EditorTab>>,
-    #[serde(default)]
-    pub editor_settings: EditorSettings,
-}
-
-#[derive(Debug, Deserialize, Serialize, Default)]
-#[cfg(not(feature = "editor"))]
-pub struct ProjectConfig {
-    pub project_name: String,
-    pub project_path: PathBuf,
-    pub date_created: String,
-    pub date_last_accessed: String,
-    // #[serde(default)]
-    // pub dock_layout: Option<DockState<EditorTab>>,
-}
-
-impl ProjectConfig {
-    /// Creates a new instance of the ProjectConfig. This function is typically used when creating
-    /// a new project, with it creating new defaults for everything.
-    pub fn new(project_name: String, project_path: &PathBuf) -> Self {
-        let date_created = format!("{}", Utc::now().format("%Y-%m-%d %H:%M:%S"));
-        let date_last_accessed = format!("{}", Utc::now().format("%Y-%m-%d %H:%M:%S"));
-        #[cfg(not(feature = "editor"))]
-        {
-            let mut result = Self {
-                project_name,
-                project_path: project_path.to_path_buf(),
-                date_created,
-                date_last_accessed,
-            };
-            let _ = result.load_config_to_memory(); // TODO: Deal with later...
-            result
-        }
-
-        #[cfg(feature = "editor")]
-        {
-            let mut result = Self {
-                project_name,
-                project_path: project_path.to_path_buf(),
-                date_created,
-                date_last_accessed,
-                editor_settings: Default::default(),
-                dock_layout: None,
-            };
-            let _ = result.load_config_to_memory(); // TODO: Deal with later...
-            result
-        }
-    }
-
-    /// This function writes the [`ProjectConfig`] struct (and other PathBufs) to a file of the choice
-    /// under the PathBuf path parameter.
-    ///
-    /// # Parameters
-    /// * path - The root **folder** of the project.
-
-    pub fn write_to(&mut self, path: &PathBuf) -> anyhow::Result<()> {
-        self.load_config_to_memory()?;
-        self.date_last_accessed = format!("{}", Utc::now().format("%Y-%m-%d %H:%M:%S"));
-        // self.assets = Assets::walk(path);
-        let ron_str = ron::ser::to_string_pretty(&self, PrettyConfig::default())
-            .map_err(|e| anyhow::anyhow!("RON serialization error: {}", e))?;
-        let config_path = path.join(format!("{}.eucp", self.project_name.clone().to_lowercase()));
-        self.project_path = path.to_path_buf();
-
-        fs::write(&config_path, ron_str).map_err(|e| anyhow::anyhow!(e.to_string()))?;
-        Ok(())
-    }
-
-    /// This function reads from the RON and traverses down the different folders to add more information
-    /// to the ProjectConfig, such as Assets location and other stuff.
-    ///
-    /// # Parameters
-    /// * path - The root config **file** for the project
-    pub fn read_from(path: &PathBuf) -> anyhow::Result<Self> {
-        let ron_str = fs::read_to_string(path)?;
-        let mut config: ProjectConfig = ron::de::from_str(&ron_str.as_str())?;
-        config.project_path = path.parent().unwrap().to_path_buf();
-        log::info!("Loaded project!");
-        log::debug!("Loaded config info");
-        log::debug!("Updating with new content");
-        config.load_config_to_memory()?;
-        config.write_to_all()?;
-        log::debug!("Successfully updated!");
-        Ok(config)
-    }
-
-    /// This function loads a `source.eucc`, `resources.eucc` or a `{scene}.eucs` config file into memory, allowing
-    /// you to reference and load the nodes located inside them.
-    pub fn load_config_to_memory(&mut self) -> anyhow::Result<()> {
-        let project_root = PathBuf::from(&self.project_path);
-
-        // resource config
-        match ResourceConfig::read_from(&project_root) {
-            Ok(resources) => {
-                let mut cfg = RESOURCES.write().unwrap();
-                *cfg = resources;
-            }
-            Err(e) => {
-                if let Some(io_err) = e.downcast_ref::<std::io::Error>() {
-                    if io_err.kind() == std::io::ErrorKind::NotFound {
-                        log::warn!("resources.eucc not found, creating default.");
-                        let default = ResourceConfig {
-                            path: project_root.join("resources"),
-                            nodes: vec![],
-                        };
-                        default.write_to(&project_root)?;
-                        {
-                            let mut cfg = RESOURCES.write().unwrap();
-                            *cfg = default;
-                        }
-                    } else {
-                        log::warn!("Failed to load resources.eucc: {}", e);
-                    }
-                } else {
-                    log::warn!("Failed to load resources.eucc: {}", e);
-                }
-            }
-        }
-
-        // src config
-        let mut source_config = SOURCE.write().unwrap();
-        match SourceConfig::read_from(&project_root) {
-            Ok(source) => *source_config = source,
-            Err(e) => {
-                if let Some(io_err) = e.downcast_ref::<std::io::Error>() {
-                    if io_err.kind() == std::io::ErrorKind::NotFound {
-                        log::warn!("source.eucc not found, creating default.");
-                        let default = SourceConfig {
-                            path: project_root.join("src"),
-                            nodes: vec![],
-                        };
-                        default.write_to(&project_root)?;
-                        *source_config = default;
-                    } else {
-                        log::warn!("Failed to load source.eucc: {}", e);
-                    }
-                } else {
-                    log::warn!("Failed to load source.eucc: {}", e);
-                }
-            }
-        }
-
-        // scenes
-        let mut scene_configs = SCENES.write().unwrap();
-        scene_configs.clear();
-
-        // iterate through each scene file in the folder
-        let scene_folder = &project_root.join("scenes");
-
-        if !scene_folder.exists() {
-            fs::create_dir_all(scene_folder)?;
-        }
-
-        for scene_entry in fs::read_dir(scene_folder)? {
-            let scene_entry = scene_entry?;
-            let path = scene_entry.path();
-
-            if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("eucs") {
-                match SceneConfig::read_from(&path) {
-                    Ok(scene) => {
-                        log::debug!("Loaded scene: {}", scene.scene_name);
-                        scene_configs.push(scene);
-                    }
-                    Err(e) => {
-                        if let Some(io_err) = e.downcast_ref::<std::io::Error>() {
-                            if io_err.kind() == std::io::ErrorKind::NotFound {
-                                log::warn!("Scene file {:?} not found", path);
-                            } else {
-                                log::warn!("Failed to load scene file {:?}: {}", path, e);
-                            }
-                        } else {
-                            log::warn!("Failed to load scene file {:?}: {}", path, e);
-                        }
-                    }
-                }
-            }
-        }
-
-        if scene_configs.is_empty() {
-            log::info!("No scenes found, creating default scene");
-            let default_scene =
-                SceneConfig::new("Default".to_string(), scene_folder.join("default.eucs"));
-            default_scene.write_to(&project_root)?;
-            scene_configs.push(default_scene);
-        }
-
-        Ok(())
-    }
-
-    /// # Parameters
-    /// * path - The root folder of the project
-    pub fn write_to_all(&mut self) -> anyhow::Result<()> {
-        let path = PathBuf::from(self.project_path.clone());
-
-        {
-            let resources_config = RESOURCES.read().unwrap();
-            resources_config.write_to(&path)?;
-        }
-
-        {
-            let source_config = SOURCE.read().unwrap();
-            source_config.write_to(&path)?;
-        }
-
-        {
-            let scene_configs = SCENES.read().unwrap();
-            for scene in scene_configs.iter() {
-                scene.write_to(&path)?;
-            }
-        }
-
-        self.write_to(&path)?;
-        Ok(())
-    }
-}
-
-#[allow(dead_code)]
-pub(crate) fn path_contains_folder(path: &PathBuf, folder: &str) -> bool {
-    path.components().any(|comp| comp.as_os_str() == folder)
-}
-
-#[derive(Debug, Serialize, Deserialize, Clone)]
-pub enum Node {
-    File(File),
-    Folder(Folder),
-}
-
-#[derive(Default, Debug, Serialize, Deserialize, Clone)]
-pub struct File {
-    pub name: String,
-    pub path: PathBuf,
-    pub resource_type: Option<ResourceType>,
-}
-
-#[derive(Default, Debug, Serialize, Deserialize, Clone)]
-pub struct Folder {
-    pub name: String,
-    pub path: PathBuf,
-    pub nodes: Vec<Node>,
-}
-
-/// The type of resource
-#[derive(Debug, Serialize, Deserialize, Clone, Hash)]
-pub enum ResourceType {
-    Unknown,
-    Model,
-    Thumbnail,
-    Texture,
-    Shader,
-}
-
-impl Display for ResourceType {
-    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
-        let str = match self {
-            ResourceType::Unknown => "unknown",
-            ResourceType::Model => "model",
-            ResourceType::Texture => "texture",
-            ResourceType::Shader => "shader",
-            ResourceType::Thumbnail => "thumbnail",
-        };
-        write!(f, "{}", str)
-    }
-}
-
-/// This is the resource config.
-#[derive(Default, Debug, Serialize, Deserialize)]
-pub struct ResourceConfig {
-    /// The path to the resource folder.
-    pub path: PathBuf,
-    /// The files and folders of the assets
-    pub nodes: Vec<Node>,
-}
-
-impl ResourceConfig {
-    /// Builds a resource path from the ProjectConfiguration's project_path (or a string)
-    #[allow(dead_code)]
-    pub fn build_path(project_path: String) -> PathBuf {
-        PathBuf::from(project_path).join("resources/resources.eucc")
-    }
-
-    /// # Parameters
-    /// - path: The root **folder** of the project
-    pub fn write_to(&self, path: &PathBuf) -> anyhow::Result<()> {
-        let resource_dir = path.join("resources");
-        let updated_config = ResourceConfig {
-            path: resource_dir.clone(),
-            nodes: collect_nodes(&resource_dir, path, vec!["thumbnails"].as_slice()),
-        };
-        let ron_str = ron::ser::to_string_pretty(&updated_config, PrettyConfig::default())
-            .map_err(|e| anyhow::anyhow!("RON serialization error: {}", e))?;
-        let config_path = path.join("resources").join("resources.eucc");
-        fs::create_dir_all(config_path.parent().unwrap())?;
-        fs::write(&config_path, ron_str).map_err(|e| anyhow::anyhow!(e.to_string()))?;
-        Ok(())
-    }
-
-    /// Updates the in-memory ResourceConfig by re-scanning the resource directory.
-    pub fn update_mem(&mut self) -> anyhow::Result<ResourceConfig> {
-        let resource_dir = self.path.clone();
-        let project_path = resource_dir.parent().unwrap_or(&resource_dir).to_path_buf();
-        let updated_config = ResourceConfig {
-            path: resource_dir.clone(),
-            nodes: collect_nodes(&resource_dir, &project_path, vec!["thumbnails"].as_slice()),
-        };
-        Ok(updated_config)
-    }
-
-    /// # Parameters
-    /// - path: The location to the **resources.eucc** file
-    pub fn read_from(path: &PathBuf) -> anyhow::Result<Self> {
-        let config_path = path.join("resources").join("resources.eucc");
-        let ron_str = fs::read_to_string(&config_path)?;
-        let config: ResourceConfig = ron::de::from_str(&ron_str)
-            .map_err(|e| anyhow::anyhow!("RON deserialization error: {}", e))?;
-        Ok(config)
-    }
-}
-
-#[derive(Default, Debug, Serialize, Deserialize, Clone)]
-pub struct SourceConfig {
-    /// The path to the resource folder.
-    pub path: PathBuf,
-    /// The files and folders of the assets
-    pub nodes: Vec<Node>,
-}
-
-impl SourceConfig {
-    /// Builds a source path from the ProjectConfiguration's project_path (or a string)
-    #[allow(dead_code)]
-    pub fn build_path(project_path: String) -> PathBuf {
-        PathBuf::from(project_path).join("src/source.eucc")
-    }
-
-    /// # Parameters
-    /// - path: The root **folder** of the project
-    pub fn write_to(&self, path: &PathBuf) -> anyhow::Result<()> {
-        let resource_dir = path.join("src");
-        let updated_config = SourceConfig {
-            path: resource_dir.clone(),
-            nodes: collect_nodes(&resource_dir, path, vec!["scripts"].as_slice()),
-        };
-
-        let ron_str = ron::ser::to_string_pretty(&updated_config, PrettyConfig::default())
-            .map_err(|e| anyhow::anyhow!("RON serialisation error: {}", e))?;
-        let config_path = path.join("src").join("source.eucc");
-        fs::create_dir_all(config_path.parent().unwrap())?;
-        fs::write(&config_path, ron_str).map_err(|e| anyhow::anyhow!(e.to_string()))?;
-        Ok(())
-    }
-
-    /// # Parameters
-    /// - path: The location to the **source.eucc** file
-    pub fn read_from(path: &PathBuf) -> anyhow::Result<Self> {
-        let config_path = path.join("src").join("source.eucc");
-        let ron_str = fs::read_to_string(&config_path)?;
-        let config: SourceConfig = ron::de::from_str(&ron_str)
-            .map_err(|e| anyhow::anyhow!("RON deserialization error: {}", e))?;
-        Ok(config)
-    }
-}
-
-fn collect_nodes(dir: &PathBuf, project_path: &PathBuf, exclude_list: &[&str]) -> Vec<Node> {
-    let mut nodes = Vec::new();
-    if let Ok(entries) = fs::read_dir(dir) {
-        for entry in entries.flatten() {
-            let entry_path = entry.path();
-            let name = entry_path
-                .file_name()
-                .unwrap_or_default()
-                .to_string_lossy()
-                .to_string();
-
-            if entry_path.is_dir() && exclude_list.iter().any(|ex| &ex.to_string() == &name) {
-                log::debug!("Skipped past folder {:?}", name);
-                continue;
-            }
-
-            if entry_path.is_dir() {
-                let folder_nodes = collect_nodes(&entry_path, project_path, exclude_list);
-                nodes.push(Node::Folder(Folder {
-                    name,
-                    path: entry_path.clone(),
-                    nodes: folder_nodes,
-                }));
-            } else {
-                let parent_folder = entry_path
-                    .parent()
-                    .and_then(|p| p.file_name())
-                    .map(|n| n.to_string_lossy().to_lowercase())
-                    .unwrap_or_default();
-
-                let resource_type = if parent_folder.contains("model") {
-                    Some(ResourceType::Model)
-                } else if parent_folder.contains("texture") {
-                    Some(ResourceType::Texture)
-                } else if parent_folder.contains("shader") {
-                    Some(ResourceType::Shader)
-                } else {
-                    Some(ResourceType::Unknown)
-                };
-
-                nodes.push(Node::File(File {
-                    name,
-                    path: entry_path.clone(),
-                    resource_type,
-                }));
-            }
-        }
-    }
-    nodes
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Hash)]
-pub enum EntityNode {
-    Entity {
-        id: hecs::Entity,
-        name: String,
-    },
-    Script {
-        name: String,
-        path: PathBuf,
-    },
-    Light {
-        id: hecs::Entity,
-        name: String,
-    },
-    Camera {
-        id: hecs::Entity,
-        name: String,
-        camera_type: CameraType,
-    },
-    Group {
-        name: String,
-        children: Vec<EntityNode>,
-        collapsed: bool,
-    },
-}
-
-#[derive(Default, Debug, Serialize, Deserialize, Clone)]
-pub struct ScriptComponent {
-    pub name: String,
-    pub path: PathBuf,
-}
-
-impl EntityNode {
-    pub fn from_world(world: &hecs::World) -> Vec<Self> {
-        let mut nodes = Vec::new();
-        let mut handled = std::collections::HashSet::new();
-
-        for (id, (script, _transform, adopted)) in world
-            .query::<(
-                &ScriptComponent,
-                &dropbear_engine::entity::Transform,
-                &dropbear_engine::entity::AdoptedEntity,
-            )>()
-            .iter()
-        {
-            let name = adopted.model().label.clone();
-
-            // grouped entity (entity + script)
-            nodes.push(EntityNode::Group {
-                name: name.clone(),
-                children: vec![
-                    EntityNode::Entity {
-                        id,
-                        name: name.clone(),
-                    },
-                    EntityNode::Script {
-                        name: script.name.clone(),
-                        path: script.path.clone(),
-                    },
-                ],
-                collapsed: false,
-            });
-            handled.insert(id);
-        }
-
-        // single entity
-        for (id, (_, adopted)) in world
-            .query::<(
-                &dropbear_engine::entity::Transform,
-                &dropbear_engine::entity::AdoptedEntity,
-            )>()
-            .iter()
-        {
-            if handled.contains(&id) {
-                continue;
-            }
-            let name = adopted.model().label.clone();
-
-            nodes.push(EntityNode::Entity { id, name });
-        }
-
-        // lights
-        for (id, (_transform, _light_comp, light)) in world
-            .query::<(&dropbear_engine::entity::Transform, &LightComponent, &Light)>()
-            .iter()
-        {
-            if handled.contains(&id) {
-                continue;
-            }
-            nodes.push(EntityNode::Light {
-                id,
-                name: light.label().to_string(),
-            });
-            handled.insert(id);
-        }
-
-        for (entity, (camera, component)) in world
-            .query::<(&Camera, &CameraComponent)>()
-            .iter()
-        {
-            if world.get::<&AdoptedEntity>(entity).is_err() {
-                nodes.push(EntityNode::Camera {
-                    id: entity,
-                    name: camera.label.clone(),
-                    camera_type: component.camera_type,
-                });
-            }
-        }
-
-        nodes
-    }
-}
-
-#[derive(Debug, Serialize, Deserialize, Clone)]
-pub struct CameraConfig {
-    pub label: String,
-    pub camera_type: CameraType,
-
-    pub position: [f64; 3],
-    pub target: [f64; 3],
-    pub up: [f64; 3],
-    pub aspect: f64,
-    pub fov: f32,
-    pub near: f32,
-    pub far: f32,
-
-    pub speed: f32,
-    pub sensitivity: f32,
-
-    pub follow_target_entity_label: Option<String>,
-    pub follow_offset: Option<[f64; 3]>,
-}
-
-impl Default for CameraConfig {
-    fn default() -> Self {
-        let default = CameraComponent::new();
-        Self {
-            position: [0.0, 1.0, 2.0],
-            target: [0.0, 0.0, 0.0],
-            up: [0.0, 1.0, 0.0],
-            aspect: 16.0 / 9.0,
-            fov: 45.0,
-            near: 0.1,
-            far: 100.0,
-            follow_target_entity_label: None,
-            follow_offset: None,
-            label: String::new(),
-            camera_type: CameraType::Normal,
-            speed: default.speed as f32,
-            sensitivity: default.sensitivity as f32,
-        }
-    }
-}
-
-impl CameraConfig {
-    pub fn from_ecs_camera(camera: &Camera, component: &CameraComponent, follow_target: Option<&CameraFollowTarget>) -> Self {
-        Self {
-            position: camera.position().to_array(),
-            target: camera.target.to_array(),
-            label: camera.label.clone(),
-            camera_type: component.camera_type,
-            up: camera.up.to_array(),
-            aspect: camera.aspect,
-            fov: camera.fov_y as f32,
-            near: camera.znear as f32,
-            far: camera.zfar as f32,
-            speed: component.speed as f32,
-            sensitivity: component.sensitivity as f32,
-            follow_target_entity_label: if let Some(target) = follow_target { Some(target.follow_target.clone()) } else { None },
-            follow_offset: if let Some(target) = follow_target { Some(target.offset.to_array()) } else { None },
-        }
-    }
-}
-
-#[derive(Debug, Serialize, Deserialize, Clone, Default)]
-pub struct SceneEntity {
-    pub model_path: ResourceReference,
-    pub label: String,
-    pub transform: Transform,
-    pub properties: ModelProperties,
-    pub script: Option<ScriptComponent>,
-
-    #[serde(skip)]
-    #[allow(dead_code)]
-    pub entity_id: Option<hecs::Entity>,
-}
-
-#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
-pub struct ModelProperties {
-    pub custom_properties: HashMap<String, PropertyValue>,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-pub enum PropertyValue {
-    String(String),
-    Int(i64),
-    Float(f64),
-    Bool(bool),
-    Vec3([f32; 3]),
-}
-
-impl ModelProperties {
-    pub fn new() -> Self {
-        Self {
-            custom_properties: HashMap::new(),
-        }
-    }
-
-    pub fn set_property(&mut self, key: String, value: PropertyValue) {
-        self.custom_properties.insert(key, value);
-    }
-
-    pub fn get_property(&self, key: &str) -> Option<&PropertyValue> {
-        self.custom_properties.get(key)
-    }
-}
-
-impl Default for ModelProperties {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-#[derive(Default, Debug, Serialize, Deserialize, Clone)]
-pub struct SceneConfig {
-    pub scene_name: String,
-    pub entities: Vec<SceneEntity>,
-    pub cameras: Vec<CameraConfig>,
-    pub lights: Vec<LightConfig>,
-    // todo later
-    // pub settings: SceneSettings,
-    #[serde(skip)]
-    pub path: PathBuf,
-}
-
-impl SceneConfig {
-    /// Creates a new instance of the scene config
-    pub fn new(scene_name: String, path: PathBuf) -> Self {
-        Self {
-            scene_name,
-            path,
-            entities: Vec::new(),
-            cameras: Vec::new(),
-            lights: Vec::new(),
-        }
-    }
-
-    /// Write the scene config to a .eucs file
-    pub fn write_to(&self, project_path: &PathBuf) -> anyhow::Result<()> {
-        let ron_str = ron::ser::to_string_pretty(&self, PrettyConfig::default())
-            .map_err(|e| anyhow::anyhow!("RON serialization error: {}", e))?;
-
-        let scenes_dir = project_path.join("scenes");
-        fs::create_dir_all(&scenes_dir)?;
-
-        let config_path = scenes_dir.join(format!("{}.eucs", self.scene_name));
-        fs::write(&config_path, ron_str).map_err(|e| anyhow::anyhow!(e.to_string()))?;
-        Ok(())
-    }
-
-    /// Read a scene config from a .eucs file
-    pub fn read_from(scene_path: &PathBuf) -> anyhow::Result<Self> {
-        let ron_str = fs::read_to_string(scene_path)?;
-        let mut config: SceneConfig = ron::de::from_str(&ron_str)
-            .map_err(|e| anyhow::anyhow!("RON deserialization error: {}", e))?;
-
-        config.path = scene_path.clone();
-        Ok(config)
-    }
-
-    pub fn load_into_world(
-        &self,
-        world: &mut hecs::World,
-        graphics: &Graphics,
-    ) -> anyhow::Result<hecs::Entity> {
-        log::info!(
-            "Loading scene [{}], clearing world with {} entities",
-            self.scene_name,
-            world.len()
-        );
-        world.clear();
-
-        #[allow(unused_variables)]
-        let project_config = if cfg!(feature = "data-only") {
-            if let Ok(cfg) = PROJECT.read() {
-                cfg.project_path.clone()
-            } else {
-                log::warn!("Unable to retrieve a lock from the PROJECT config");
-                PathBuf::new()
-            }
-        } else {
-            log::warn!("Feature is data only, no need for project config");
-            PathBuf::new()
-        };
-
-        log::info!("World cleared, now has {} entities", world.len());
-
-        for entity_config in &self.entities {
-            log::debug!("Loading entity: {}", entity_config.label);
-            match &entity_config.model_path.ref_type {
-                ResourceReferenceType::File(reference) => {
-                    let path: PathBuf = {
-                        if cfg!(feature = "data-only") {
-                            log::debug!("Using feature data-only");
-                            entity_config.model_path.to_executable_path()?
-                        } else if cfg!(feature = "editor") {
-                            log::debug!("Using feature editor");
-                            entity_config.model_path.to_project_path(project_config.clone())
-                                .ok_or_else(|| anyhow::anyhow!("Unable to convert resource reference [{}] to project path", reference))?
-                        } else {
-                            log::debug!("Fuck you");
-                            panic!("Not using either the data-only feature or the editor feature, which resolve the path of the ResourceReference");
-                        }
-                    };
-                    log::debug!("Path for entity {} is {} from reference {}", entity_config.label, path.display(), reference);
-                    let adopted = AdoptedEntity::new(
-                        graphics,
-                        &path,
-                        Some(&entity_config.label),
-                    )?;
-
-                    let transform = entity_config.transform;
-
-                    if let Some(script_config) = &entity_config.script {
-                        let script = ScriptComponent {
-                            name: script_config.name.clone(),
-                            path: script_config.path.clone(),
-                        };
-                        world.spawn((adopted, transform, script, entity_config.properties.clone()));
-                    } else {
-                        world.spawn((adopted, transform, entity_config.properties.clone()));
-                    }
-                    log::debug!("Loaded!");
-                }
-                ResourceReferenceType::Bytes(bytes) => {
-                    log::info!("Loading entity from bytes [Len: {}]", bytes.len());
-                    let bytes = bytes.to_owned();
-
-                    let model = Model::load_from_memory(graphics, bytes, Some(&entity_config.label))?;
-                    let transform = entity_config.transform;
-
-                    let adopted = AdoptedEntity::adopt(
-                        graphics,
-                        model,
-                        Some(&entity_config.label),
-                    );
-
-                    if let Some(script_config) = &entity_config.script {
-                        let script = ScriptComponent {
-                            name: script_config.name.clone(),
-                            path: script_config.path.clone(),
-                        };
-                        world.spawn((adopted, transform, script, entity_config.properties.clone()));
-                    } else {
-                        world.spawn((adopted, transform, entity_config.properties.clone()));
-                    }
-                    log::debug!("Loaded!");
-                }
-                ResourceReferenceType::Plane => {
-                    let width = entity_config.properties.custom_properties.get("width").ok_or_else(|| anyhow::anyhow!("Entity has no width property"))?;
-                    let width = match width {
-                        PropertyValue::Float(width) => width,
-                        _ => panic!("Entity has a width property that is not a float"),
-                    };
-                    let height = entity_config.properties.custom_properties.get("height").ok_or_else(|| anyhow::anyhow!("Entity has no height property"))?;
-                    let height = match height {
-                        PropertyValue::Float(height) => height,
-                        _ => panic!("Entity has a height property that is not a float"),
-                    };
-                    let tiles_x = entity_config.properties.custom_properties.get("tiles_x").ok_or_else(|| anyhow::anyhow!("Entity has no tiles_x property"))?;
-                    let tiles_x = match tiles_x {
-                        PropertyValue::Int(tiles_x) => tiles_x,
-                        _ => panic!("Entity has a tiles_x property that is not an int"),
-                    };
-                    let tiles_z = entity_config.properties.custom_properties.get("tiles_z").ok_or_else(|| anyhow::anyhow!("Entity has no tiles_z property"))?;
-                    let tiles_z = match tiles_z {
-                        PropertyValue::Int(tiles_z) => tiles_z,
-                        _ => panic!("Entity has a tiles_z property that is not an int"),
-                    };
-
-                    let plane = PlaneBuilder::new().with_size(*width as f32, *height as f32).with_tiles(*tiles_x as u32, *tiles_z as u32).build(graphics, PROTO_TEXTURE, Some(entity_config.label.clone().as_str()))?;
-                    let transform = entity_config.transform;
-
-                    if let Some(script_config) = &entity_config.script {
-                        let script = ScriptComponent {
-                            name: script_config.name.clone(),
-                            path: script_config.path.clone(),
-                        };
-                        world.spawn((plane, transform, script, entity_config.properties.clone()));
-                    } else {
-                        world.spawn((plane, transform, entity_config.properties.clone()));
-                    }
-                }
-                ResourceReferenceType::None => panic!("Entity has a resource reference of None, which cannot be loaded or referenced"),
-            }
-        }
-
-        for light_config in &self.lights {
-            log::debug!("Loading light: {}", light_config.label);
-
-            let light = Light::new(
-                graphics,
-                &light_config.light_component,
-                &light_config.transform,
-                Some(&light_config.label),
-            );
-
-            world.spawn((light_config.light_component.clone(), light_config.transform, light, ModelProperties::default()));
-        }
-
-        for camera_config in &self.cameras {
-            log::debug!("Loading camera {} of type {:?}", camera_config.label, camera_config.camera_type);
-
-            let camera = Camera::new(
-                graphics,
-                DVec3::from_array(camera_config.position),
-                DVec3::from_array(camera_config.target),
-                DVec3::from_array(camera_config.up),
-                camera_config.aspect,
-                camera_config.fov as f64,
-                camera_config.near as f64,
-                camera_config.far as f64,
-                camera_config.speed as f64,
-                camera_config.sensitivity as f64,
-                Some(&camera_config.label),
-            );
-
-            let component = CameraComponent {
-                speed: camera_config.speed as f64,
-                sensitivity: camera_config.sensitivity as f64,
-                fov_y: camera_config.fov as f64,
-                camera_type: camera_config.camera_type,
-            };
-
-            if let (Some(target_label), Some(offset)) = (&camera_config.follow_target_entity_label, &camera_config.follow_offset) {
-                let follow_target = CameraFollowTarget {
-                    follow_target: target_label.clone(),
-                    offset: DVec3::from_array(*offset),
-                };
-                world.spawn((camera, component, follow_target));
-            } else {
-                world.spawn((camera, component));
-            }
-        }
-
-        if world.query::<(&LightComponent, &Light)>().iter().next().is_none() {
-            log::info!("No lights in scene, spawning default light");
-            let default_transform = Transform {
-                position: glam::DVec3::new(2.0, 4.0, 2.0),
-                ..Default::default()
-            };
-            let default_component = LightComponent::directional(glam::DVec3::ONE, 1.0);
-            let default_light = Light::new(graphics, &default_component, &default_transform, Some("Default Light"));
-            world.spawn((default_component, default_transform, default_light, ModelProperties::default()));
-        }
-
-        log::info!("Loaded {} entities, {} lights and {} cameras", self.entities.len(), self.lights.len(), self.cameras.len());
-        #[cfg(feature = "editor")]
-        {
-            // Editor mode - look for debug camera, create one if none exists
-            let debug_camera = world
-                .query::<(&Camera, &CameraComponent)>()
-                .iter()
-                .find_map(|(entity, (_, component))| {
-                    if matches!(component.camera_type, CameraType::Debug) {
-                        Some(entity)
-                    } else {
-                        None
-                    }
-                });
-
-            if let Some(camera_entity) = debug_camera {
-                log::info!("Using existing debug camera for editor");
-                Ok(camera_entity)
-            } else {
-                log::info!("No debug camera found, creating viewport camera for editor");
-                let camera = Camera::predetermined(graphics, Some("Viewport Camera"));
-                let component = DebugCamera::new();
-                let camera_entity = world.spawn((camera, component));
-                Ok(camera_entity)
-            }
-        }
-
-        #[cfg(not(feature = "editor"))]
-        {
-            // Runtime mode - look for player camera, panic if none exists
-            let player_camera = world
-                .query::<(&Camera, &CameraComponent)>()
-                .iter()
-                .find_map(|(entity, (_, component))| {
-                    if matches!(component.camera_type, CameraType::Player) {
-                        Some(entity)
-                    } else {
-                        None
-                    }
-                });
-
-            if let Some(camera_entity) = player_camera {
-                log::info!("Using player camera for runtime");
-                Ok(camera_entity)
-            } else {
-                panic!("Runtime mode requires a player camera, but none was found in the scene!");
-            }
-        }
-    }
-}
-
-#[derive(Decode, Encode, serde::Serialize, serde::Deserialize, Debug)]
-pub struct RuntimeData {
-    #[bincode(with_serde)]
-    pub project_config: ProjectConfig,
-    #[bincode(with_serde)]
-    pub source_config: SourceConfig,
-    #[bincode(with_serde)]
-    pub scene_data: Vec<SceneConfig>,
-    #[bincode(with_serde)]
-    pub scripts: HashMap<String, String>, // name, script_content
-}
-
-#[derive(Debug, Serialize, Deserialize, Clone)]
-pub struct LightConfig {
-    pub label: String,
-    pub transform: Transform,
-    pub light_component: LightComponent,
-    pub enabled: bool,
-
-    #[serde(skip)]
-    pub entity_id: Option<hecs::Entity>,
-}
-
-impl Default for LightConfig {
-    fn default() -> Self {
-        Self {
-            label: "New Light".to_string(),
-            transform: Transform::default(),
-            light_component: LightComponent::default(),
-            enabled: true,
-            entity_id: None,
-        }
-    }
-}
-
-#[derive(Debug, Serialize, Deserialize, Clone, Default)]
-pub struct EditorSettings {
-    pub is_debug_menu_shown: bool,
-}
\ No newline at end of file
diff --git a/eucalyptus/src/template.ts b/eucalyptus/src/template.ts
deleted file mode 100644
index 552032a..0000000
--- a/eucalyptus/src/template.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-// dropbear-engine script template for eucalyptus
-import * as dropbear from "./dropbear";
-
-export function onLoad(s) {
-    dropbear.start(s);
-    // ------- Your own code here -------
-    console.log("I have been awoken");
-
-
-    // ----------------------------------
-    // Do not remove anything outside unless
-    // you know what you are doing.
-    return dropbear.end();
-}
-
-export function onUpdate(s, dt: number) {
-    dropbear.start(s);
-    // ------- Your own code here -------
-    console.log("I'm being updated!");
-    
-
-    // ----------------------------------
-    // Same thing over here!
-    return dropbear.end();
-}
\ No newline at end of file
diff --git a/eucalyptus/src/utils.rs b/eucalyptus/src/utils.rs
deleted file mode 100644
index e3b0c08..0000000
--- a/eucalyptus/src/utils.rs
+++ /dev/null
@@ -1,280 +0,0 @@
-#[cfg(feature = "editor")]
-use std::{
-    fs, sync::mpsc::{self, Receiver},
-};
-
-use std::path::PathBuf;
-
-#[cfg(feature = "editor")]
-use anyhow::anyhow;
-use dropbear_engine::{camera::Camera, entity::Transform};
-#[cfg(feature = "editor")]
-use dropbear_engine::scene::SceneCommand;
-#[cfg(feature = "editor")]
-use egui::Context;
-
-#[cfg(feature = "editor")]
-use egui_toast_fork::{Toast, ToastOptions, Toasts};
-#[cfg(feature = "editor")]
-use git2::Repository;
-
-use crate::states::{ModelProperties, Node};
-#[cfg(feature = "editor")]
-use crate::states::{PROJECT, ProjectConfig};
-
-pub const PROTO_TEXTURE: &[u8] = include_bytes!("../../resources/proto.png");
-
-pub fn search_nodes_recursively<'a, F>(nodes: &'a [Node], matcher: &F, results: &mut Vec<&'a Node>)
-where
-    F: Fn(&Node) -> bool,
-{
-    for node in nodes {
-        if matcher(node) {
-            results.push(node);
-        }
-        match node {
-            Node::File(_) => {}
-            Node::Folder(folder) => {
-                search_nodes_recursively(&folder.nodes, matcher, results);
-            }
-        }
-    }
-}
-
-/// Progress events for project creation
-pub enum ProjectProgress {
-    Step {
-        _progress: f32,
-        _message: String,
-    },
-    #[allow(dead_code)] // idk why its giving me this warning :(
-    Error(String),
-    Done,
-}
-
-/// Open a project file and update the global state.
-/// Returns Ok(Some(SceneCommand::SwitchScene)) on success, or an error string on failure.
-#[allow(dead_code)]
-#[cfg(feature = "editor")]
-pub fn open_project(
-    scene_command: &mut SceneCommand,
-    toast: &mut Toasts,
-) -> Result<Option<SceneCommand>, String> {
-    if let Some(path) = rfd::FileDialog::new()
-        .add_filter("Eucalyptus Project Configuration Files", &["eucp"])
-        .pick_file()
-    {
-        match ProjectConfig::read_from(&path) {
-            Ok(config) => {
-                let mut global = PROJECT.write().unwrap();
-                *global = config;
-                *scene_command = SceneCommand::SwitchScene("editor".to_string());
-                Ok(Some(SceneCommand::SwitchScene("editor".to_string())))
-            }
-            Err(e) => {
-                if e.to_string().contains("missing field") {
-                    toast.add(Toast {
-                        kind: egui_toast_fork::ToastKind::Error,
-                        text: "Project version is not up to date.".into(),
-                        options: ToastOptions::default()
-                            .duration_in_seconds(5.0)
-                            .show_progress(true),
-                        ..Default::default()
-                    });
-                }
-                Err(format!("Failed to load project: {e}"))
-            }
-        }
-    } else {
-        Err("File dialog returned None".to_string())
-    }
-}
-
-/// Start creating a new project in a background thread.
-/// Returns a Receiver for progress updates.
-/// 
-#[cfg(feature = "editor")]
-pub fn start_project_creation(
-    project_name: String,
-    project_path: Option<PathBuf>,
-) -> Option<Receiver<ProjectProgress>> {
-    let (tx, rx) = mpsc::channel();
-    let project_path = project_path.clone();
-
-    std::thread::spawn(move || {
-        let folders = [
-            ("git", 0.1, "Creating a git folder..."),
-            ("src", 0.2, "Creating src folder..."),
-            ("resources/models", 0.4, "Creating models folder..."),
-            ("resources/shaders", 0.6, "Creating shader folder..."),
-            ("resources/textures", 0.8, "Creating textures folder..."),
-            ("src2", 0.9, "Creating project config file..."),
-        ];
-
-        if let Some(path) = &project_path {
-            for (folder, progress, message) in folders {
-                tx.send(ProjectProgress::Step {
-                    _progress: progress,
-                    _message: message.to_string(),
-                })
-                .ok();
-
-                let full_path = path.join(folder);
-                let result: anyhow::Result<()> = if folder == "src" {
-                    if !full_path.exists() {
-                        fs::create_dir(&full_path)
-                            .map_err(|e| anyhow::anyhow!(e))
-                            .map(|_| ())
-                    } else {
-                        Ok(())
-                    }
-                } else if folder == "git" {
-                    match Repository::init(path) {
-                        Ok(_) => Ok(()),
-                        Err(e) => {
-                            if matches!(e.code(), git2::ErrorCode::Exists) {
-                                Ok(())
-                            } else {
-                                Err(anyhow!(e))
-                            }
-                        }
-                    }
-                } else if folder == "src2" {
-                    if let Some(path) = &project_path {
-                        let mut config = ProjectConfig::new(project_name.clone(), &path);
-                        let _ = config.write_to_all();
-                        let mut global = PROJECT.write().unwrap();
-                        *global = config;
-                        Ok(())
-                    } else {
-                        Err(anyhow!("Project path not found"))
-                    }
-                } else {
-                    if !full_path.exists() {
-                        fs::create_dir_all(&full_path)
-                            .map_err(|e| anyhow!(e))
-                            .map(|_| ())
-                    } else {
-                        Ok(())
-                    }
-                };
-                if let Err(e) = result {
-                    tx.send(ProjectProgress::Error(e.to_string())).ok();
-                }
-            }
-            tx.send(ProjectProgress::Step {
-                _progress: 1.0,
-                _message: "Project creation complete!".to_string(),
-            })
-            .ok();
-            tx.send(ProjectProgress::Done).ok();
-        }
-    });
-
-    Some(rx)
-}
-
-#[cfg(feature = "editor")]
-pub fn show_new_project_window<F>(
-    ctx: &Context,
-    show_new_project: &mut bool,
-    project_name: &mut String,
-    project_path: &mut Option<PathBuf>,
-    on_create: F,
-) where
-    F: FnOnce(&str, &PathBuf),
-{
-    let screen_size = egui::vec2(400.0, 220.0);
-
-    let mut open = *show_new_project;
-    egui::Window::new("Create new project")
-        .open(&mut open)
-        .resizable(true)
-        .collapsible(false)
-        .fixed_size(screen_size)
-        .show(ctx, |ui| {
-            ui.vertical(|ui| {
-                ui.heading("Project Name:");
-                ui.add_space(5.0);
-
-                ui.text_edit_singleline(project_name);
-                ui.add_space(10.0);
-
-                ui.heading("Project Location: ");
-                ui.add_space(5.0);
-
-                if let Some(path) = project_path {
-                    ui.label(format!("Chosen location: {}", path.display()));
-                    ui.add_space(5.0);
-                }
-
-                ui.add_space(5.0);
-                if ui.button("Choose Location").clicked() {
-                    if let Some(path) = rfd::FileDialog::new()
-                        .set_title("Save Project")
-                        .set_file_name(project_name.clone())
-                        .pick_folder()
-                    {
-                        *project_path = Some(path);
-                    }
-                }
-
-                let can_create = project_path.is_some() && !project_name.is_empty();
-                if ui
-                    .add_enabled(can_create, egui::Button::new("Create Project"))
-                    .clicked()
-                {
-                    if let Some(path) = project_path {
-                        on_create(project_name, path);
-                    }
-                    ui.ctx().request_repaint();
-                }
-            });
-        });
-    *show_new_project = open;
-}
-
-/// Converts a click on a screen (like a viewport) coordinate relative to the world
-#[allow(dead_code)]
-pub fn screen_to_world_coords(
-    camera: &Camera,
-    screen_pos: egui::Pos2,
-    viewport_rect: egui::Rect,
-) -> (glam::DVec3, glam::DVec3) {
-    let viewport_width = viewport_rect.width() as f64;
-    let viewport_height = viewport_rect.height() as f64;
-
-    let ndc_x = 2.0 * (screen_pos.x as f64 - viewport_rect.min.x as f64) / viewport_width - 1.0;
-    let ndc_y = 1.0 - 2.0 * (screen_pos.y as f64 - viewport_rect.min.y as f64) / viewport_height;
-
-    let inv_view = camera.view_mat.inverse();
-    let inv_proj = camera.proj_mat.inverse();
-
-    let clip_near = glam::DVec4::new(ndc_x, ndc_y, 0.0, 1.0);
-    let clip_far = glam::DVec4::new(ndc_x, ndc_y, 1.0, 1.0);
-
-    let view_near = inv_proj * clip_near;
-    let view_far = inv_proj * clip_far;
-
-    let world_near = inv_view * glam::DVec4::new(view_near.x, view_near.y, view_near.z, 1.0);
-    let world_far = inv_view * glam::DVec4::new(view_far.x, view_far.y, view_far.z, 1.0);
-
-    let world_near = world_near.truncate() / world_near.w;
-    let world_far = world_far.truncate() / world_far.w;
-
-    (world_near, world_far)
-}
-
-pub enum ViewportMode {
-    None,
-    CameraMove,
-    Gizmo,
-}
-
-#[derive(Clone, Debug)]
-pub struct PendingSpawn {
-    pub asset_path: PathBuf,
-    pub asset_name: String,
-    pub transform: Transform,
-    pub properties: ModelProperties,
-}
diff --git a/redback-runtime b/redback-runtime
index bec111e..5ff4de9 160000
--- a/redback-runtime
+++ b/redback-runtime
@@ -1 +1 @@
-Subproject commit bec111eb14ef956694099d6b7e9520b97814da09
+Subproject commit 5ff4de97ac7fbc6d6ffd92316eea52d27bec5e70
diff --git a/resources/dropbear.ts b/resources/dropbear.ts
new file mode 100644
index 0000000..1ffce98
--- /dev/null
+++ b/resources/dropbear.ts
@@ -0,0 +1,1115 @@
+// Modules for the dropbear-engine scripting component
+// Made by 4tkbytes
+// EDIT THIS IF YOU WISH, I RECOMMEND YOU NOT TOUCH IT
+
+/**
+ * A class describing the position, scale and rotation to be able to manipulate
+ * the entity's location. 
+ */
+export class Transform {
+    /**
+     * A {@link Vector3} describing the position of the entity
+     */
+    position: Vector3;
+    /**
+     * A {@link Quaternion} describing the rotation of the entity
+     */
+    rotation: Quaternion;
+    /**
+     * A {@link Vector3} describing the scale of the entity
+     */
+    scale: Vector3;
+
+    public constructor(position?: Vector3, rotation?: Quaternion, scale?: Vector3) {
+        this.position = position || Vector3.zero();
+        this.rotation = rotation || Quaternion.identity();
+        this.scale = scale || Vector3.one();
+    }
+
+    /**
+     * Translates/Offsets the position of the entity by a {@link Vector3}
+     * 
+     * # Example
+     * @example
+     * ```ts
+     * let transform = new Transform();
+     * transform.translate(new Vector3(1.0, 1.0, 1.0));
+     * ```
+     * 
+     * @param movement - A {@link Vector3} for position
+     */
+    public translate(movement: Vector3) {
+        this.position.x += movement.x;
+        this.position.y += movement.y;
+        this.position.z += movement.z;
+    }
+
+    /**
+     * Rotates the transformables rotation on the X axis
+     * 
+     * # Example
+     * @example
+     * ```ts
+     * let transform = new Transform();
+     * transform.rotateX(dbMath.degreesToRadians(180));
+     * ```
+     * 
+     * @param angle - The angle in radians
+     */
+    public rotateX(angle: number) {
+        const rotQuat = Quaternion.fromAxisAngle(new Vector3(1, 0, 0), angle);
+        this.rotation = Quaternion.multiply(this.rotation, rotQuat);
+        
+    }
+
+    /**
+     * Rotates the transformables rotation on the Y axis
+     * 
+     * # Example
+     * ```ts
+     * let transform = new Transform();
+     * transform.rotateY(dbMath.degreesToRadians(180))
+     * ```
+     * 
+     * @param angle - The angle in radians
+     */
+    public rotateY(angle: number) {
+        const rotQuat = Quaternion.fromAxisAngle(new Vector3(0, 1, 0), angle);
+        this.rotation = Quaternion.multiply(this.rotation, rotQuat);        
+    }
+
+    /**
+     * Rotates the transformables rotation on the Z axis
+     * 
+     * # Example
+     * ```ts
+     * let transform = new Transform();
+     * transform.rotateZ(dbMath.degreesToRadians(180))
+     * ```
+     * 
+     * @param angle - The angle in radians
+     */
+    public rotateZ(angle: number) {
+        const rotQuat = Quaternion.fromAxisAngle(new Vector3(0, 0, 1), angle);
+        this.rotation = Quaternion.multiply(this.rotation, rotQuat);
+    }
+
+    /**
+     * Uniformly scales the entity by a multiplier. 
+     * 
+     * # Example
+     * ```ts
+     * let transform = new Transform();
+     * transform.scaleUniform(2.0)
+     * ```
+     * 
+     * @param scale - A number that the scale multiplies by
+     */
+    public scaleUniform(scale: number) {
+        this.scale.x *= scale;
+        this.scale.y *= scale;
+        this.scale.z *= scale;
+    }
+
+    /**
+     * Individually scales the entity by a multiplier by using a {@link Vector3}
+     * 
+     * # Example
+     * ```ts
+     * let transform = new Transform();
+     * transform.scaleIndividual(new Vector3(1.0, 2.0, 1.5))
+     * ```
+     * 
+     * @param scale - A Vector3 representing the scale.x, scale.y and scale.z values
+     */
+    public scaleIndividual(scale: Vector3) {
+        this.scale.x *= scale.x;
+        this.scale.y *= scale.y;
+        this.scale.z *= scale.z;
+    }
+}
+
+/**
+ * Utilities for math functions that do not exist in the TypeScript Math module. 
+ */
+export const dbMath = {
+    /**
+     * Convert from degrees to radians
+     * 
+     * # Example
+     * @example
+     * ```ts
+     * console.log(dbMath.degreesToRadians(180)) // expect Math.PI
+     * ```
+     * 
+     * @param deg - The angle in degrees
+     * @returns The angle in radians
+     */
+    degreesToRadians:(deg: number):number => {
+        return deg * (Math.PI / 180.0);
+    },
+
+    /**
+     * Convert from radians to degrees
+     * 
+     * # Example
+     * @example
+     * ```ts
+     * console.log(dbMath.radiansToDegrees(Math.PI)) // expect 180
+     * ```
+     * 
+     * @param rad - The angle in radians
+     * @returns The angle in degrees
+     */
+    radiansToDegrees:(rad: number):number => {
+        return (180 * rad) / Math.PI;
+    },
+
+    /**
+     * Constrains a number to lie within a specified range. If value is less than min, returns min. 
+     * If value is greater than max, returns max. Otherwise, returns value.
+     * 
+     * @example
+     * ```ts
+     * dropbear.dbMath.clamp(5, 0, 10)     // → 5
+     * dropbear.dbMath.clamp(-3, 0, 10)    // → 0
+     * dropbear.dbMath.clamp(15, 0, 10)    // → 10
+     * ```
+     * 
+     * @param value - The input value to clamp
+     * @param min - The lower bound of the range
+     * @param max - The upper bound of the range
+     * @returns - The clamped value
+     */
+    clamp: (value: number, min: number, max: number): number => {
+        return Math.min(Math.max(value, min), max);
+    }
+}
+
+/**
+ * A Vector of 3 components: an X, Y and Z. 
+ */
+export class Vector3 {
+    /**
+     * The X value
+     */
+    public x: number;
+
+    /**
+     * The Y value
+     */
+    public y: number;
+    /**
+     * The Z value
+     */
+    public z: number;
+
+    public constructor(x: number, y: number, z: number) {
+        this.x = x;
+        this.y = y;
+        this.z = z;
+    }
+
+    /**
+     * Converts the {@link Vector3} class to a primitive number array
+     * 
+     * # Example
+     * @example
+     * ```ts
+     * let vec = new Vector3(1.0, 1.5, 2.0);
+     * console.log(vec.as_array()) // expect [1.0, 1.5, 2.0]
+     * ```
+     * 
+     * @returns A number array representing the x, y, z values
+     */
+    public as_array(): [number, number, number] {
+        return [this.x, this.y, this.z];
+    }
+
+    /**
+     * An alternative static constructor to create a {@link Vector3} with all values 
+     * set to 0.0
+     * 
+     * # Example
+     * ```ts
+     * let vec = Vector3.zero();
+     * console.log(vec); // expect [0.0, 0.0, 0.0]
+     * ```
+     * 
+     * @returns A new Vector3 instance with all values set to 0.0
+     */
+    public static zero(): Vector3 {
+        return new Vector3(0.0, 0.0, 0.0);
+    }
+
+    /**
+     * An alternative static constructor to create a {@link Vector3} with all values 
+     * set to 1.0
+     * 
+     * # Example
+     * ```ts
+     * let vec = Vector3.one();
+     * console.log(vec); // expect [1.0, 1.0, 1.0]
+     * ```
+     * 
+     * @returns A new Vector3 instance with all values set to 1.0
+     */
+    public static one(): Vector3 {
+        return new Vector3(1.0, 1.0, 1.0);
+    }
+
+    /**
+     * An alternative static constructor that creates a Vector3 from a number array.
+     * 
+     * # Example
+     * @example
+     * ```ts
+     * let arr = [1.0, 1.5, 2.0];
+     * let vec = Vector3.fromArray(arr);
+     * console.log(vec.to_array() === arr) // expect to print 'true'
+     * ```
+     *  
+     * @param arr - The number array to convert
+     * @returns - A new Vector3 instance
+     */
+    public static fromArray(arr: [number, number, number]): Vector3 {
+        return new Vector3(arr[0], arr[1], arr[2]);
+    }
+
+    /**
+     * Add another Vector3 to this vector and return the result as a new Vector3.
+     *
+     * @param rhs - Right-hand side vector to add.
+     * @returns A new Vector3 equal to (this + rhs).
+     *
+     * @example
+     * const a = new Vector3(1, 2, 3);
+     * const b = new Vector3(4, 5, 6);
+     * const c = a.add(b); // Vector3(5,7,9)
+     */
+    public add(rhs: Vector3): Vector3 {
+        return new Vector3(this.x + rhs.x, this.y + rhs.y, this.z + rhs.z);
+    }
+
+    /**
+     * Subtract another Vector3 from this vector and return the result as a new Vector3.
+     *
+     * @param rhs - Right-hand side vector to subtract.
+     * @returns A new Vector3 equal to (this - rhs).
+     *
+     * @example
+     * const a = new Vector3(5, 7, 9);
+     * const b = new Vector3(1, 2, 3);
+     * const c = a.subtract(b); // Vector3(4,5,6)
+     */
+    public subtract(rhs: Vector3): Vector3 {
+        return new Vector3(this.x - rhs.x, this.y - rhs.y, this.z - rhs.z);
+    }
+
+    /**
+     * Multiply this vector by a scalar and return the result as a new Vector3.
+     *
+     * @param rhs - Scalar multiplier.
+     * @returns A new Vector3 scaled by rhs.
+     *
+     * @example
+     * const v = new Vector3(1, 2, 3);
+     * const s = v.multiply(2); // Vector3(2,4,6)
+     */
+    public multiply(rhs: number): Vector3 {
+        return new Vector3(this.x * rhs, this.y * rhs, this.z * rhs);
+    }
+
+    /**
+     * Compute the Euclidean length (magnitude) of this vector.
+     *
+     * @returns The length sqrt(x*x + y*y + z*z).
+     *
+     * @example
+     * const v = new Vector3(1, 2, 2);
+     * console.log(v.length()); // 3
+     */
+    public length(): number {
+        return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
+    }
+
+    /**
+     * Return a new Vector3 representing the normalized (unit) direction of this vector.
+     * If the vector has zero length, returns Vector3.zero().
+     *
+     * @returns A normalized Vector3 or Vector3.zero() when length is 0.
+     *
+     * @example
+     * const v = new Vector3(0, 3, 4);
+     * const n = v.normalize(); // Vector3(0, 0.6, 0.8)
+     */
+    public normalize(): Vector3 {
+        const len = this.length();
+        if (len === 0) return Vector3.zero();
+        return new Vector3(this.x / len, this.y / len, this.z / len);
+    }
+}
+
+/**
+ * Quaternion representing a rotation in 3D space.
+ *
+ * Components are stored as (x, y, z, w) where w is the scalar part.
+ * Useful helpers are provided to construct quaternions from Euler angles,
+ * axis/angle, multiply them, and convert to/from arrays.
+ */
+export class Quaternion {
+    public x: number;
+    public y: number;
+    public z: number;
+    public w: number;
+
+    /**
+     * Create a new quaternion.
+     *
+     * @param x - X component (default 0)
+     * @param y - Y component (default 0)
+     * @param z - Z component (default 0)
+     * @param w - W (scalar) component (default 1)
+     *
+     * @example
+     * const q = new Quaternion(); // identity
+     * const q2 = new Quaternion(0.1, 0.2, 0.3, 0.9);
+     */
+    public constructor(x: number = 0, y: number = 0, z: number = 0, w: number = 1) {
+        this.x = x;
+        this.y = y;
+        this.z = z;
+        this.w = w;
+    }
+
+    /**
+     * Construct a quaternion from Euler angles (radians).
+     *
+     * The parameters represent rotations about the X, Y and Z axes respectively.
+     * Angles must be provided in radians.
+     *
+     * @param x - rotation about the X axis in radians
+     * @param y - rotation about the Y axis in radians
+     * @param z - rotation about the Z axis in radians
+     * @returns A new Quaternion representing the composite rotation
+     *
+     * @example
+     * const q = Quaternion.fromEuler(Math.PI/2, 0, 0); // 90° around X
+     */
+    public static fromEuler(x: number, y: number, z: number): Quaternion {
+        // Convert euler angles to quaternion
+        const cx = Math.cos(x * 0.5);
+        const sx = Math.sin(x * 0.5);
+        const cy = Math.cos(y * 0.5);
+        const sy = Math.sin(y * 0.5);
+        const cz = Math.cos(z * 0.5);
+        const sz = Math.sin(z * 0.5);
+
+        return new Quaternion(
+            sx * cy * cz - cx * sy * sz,
+            cx * sy * cz + sx * cy * sz,
+            cx * cy * sz - sx * sy * cz,
+            cx * cy * cz + sx * sy * sz
+        );
+    }
+
+    /**
+     * Construct a quaternion representing a rotation around an axis.
+     *
+     * The axis will be normalized internally. Angle is in radians.
+     *
+     * @param axis - Rotation axis as a Vector3
+     * @param angle - Rotation angle in radians
+     * @returns A new Quaternion representing the axis-angle rotation
+     *
+     * @example
+     * const q = Quaternion.fromAxisAngle(new Vector3(0,1,0), Math.PI); // 180° around Y
+     */
+    public static fromAxisAngle(axis: Vector3, angle: number): Quaternion {
+        const halfAngle = angle * 0.5;
+        const sin = Math.sin(halfAngle);
+        const normalizedAxis = axis.normalize();
+        
+        return new Quaternion(
+            normalizedAxis.x * sin,
+            normalizedAxis.y * sin,
+            normalizedAxis.z * sin,
+            Math.cos(halfAngle)
+        );
+    }
+
+    /**
+     * Multiply two quaternions.
+     *
+     * The result corresponds to the composition a * b (apply b, then a) using
+     * the multiplication implemented here.
+     *
+     * @param a - Left quaternion
+     * @param b - Right quaternion
+     * @returns The product quaternion
+     *
+     * @example
+     * const r = Quaternion.multiply(q1, q2);
+     */
+    public static multiply(a: Quaternion, b: Quaternion): Quaternion {
+        return new Quaternion(
+            a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
+            a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
+            a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,
+            a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z
+        );
+    }
+
+    /**
+     * Create a quaternion from a numeric array [x, y, z, w].
+     *
+     * @param arr - Array containing quaternion components in order [x, y, z, w]
+     * @returns A new Quaternion with components taken from the array
+     *
+     * @example
+     * const q = Quaternion.fromArray([0, 0, 0, 1]);
+     */
+    public static fromArray(arr: [number, number, number, number]): Quaternion {
+        return new Quaternion(arr[0], arr[1], arr[2], arr[3]);
+    }
+
+    /**
+     * Return the identity quaternion (no rotation).
+     *
+     * @returns Quaternion equal to (0, 0, 0, 1)
+     *
+     * @example
+     * const id = Quaternion.identity();
+     */
+    public static identity(): Quaternion {
+        return new Quaternion(0.0, 0.0, 0.0, 1.0);
+    }
+
+    /**
+     * Convert this quaternion to a numeric array [x, y, z, w].
+     *
+     * @returns An array containing the quaternion components
+     *
+     * @example
+     * const arr = q.as_array(); // [x, y, z, w]
+     */
+    public as_array(): [number, number, number, number] {
+        return [this.x, this.y, this.z, this.w];
+    }
+}
+
+/**
+ * Values of the Key codes. 
+ */
+export const Keys = {
+    KeyA: "KeyA", KeyB: "KeyB", KeyC: "KeyC", KeyD: "KeyD", KeyE: "KeyE", KeyF: "KeyF", 
+    KeyG: "KeyG", KeyH: "KeyH", KeyI: "KeyI", KeyJ: "KeyJ", KeyK: "KeyK", KeyL: "KeyL", 
+    KeyM: "KeyM", KeyN: "KeyN", KeyO: "KeyO", KeyP: "KeyP", KeyQ: "KeyQ", KeyR: "KeyR", 
+    KeyS: "KeyS", KeyT: "KeyT", KeyU: "KeyU", KeyV: "KeyV", KeyW: "KeyW", KeyX: "KeyX", 
+    KeyY: "KeyY", KeyZ: "KeyZ",
+    Digit0: "Digit0", Digit1: "Digit1", Digit2: "Digit2", Digit3: "Digit3", Digit4: "Digit4",
+    Digit5: "Digit5", Digit6: "Digit6", Digit7: "Digit7", Digit8: "Digit8", Digit9: "Digit9",
+    Space: "Space",
+    ShiftLeft: "ShiftLeft", ShiftRight: "ShiftRight",
+    ControlLeft: "ControlLeft", ControlRight: "ControlRight",
+    AltLeft: "AltLeft", AltRight: "AltRight",
+    Escape: "Escape", Enter: "Enter", Tab: "Tab",
+    ArrowUp: "ArrowUp", ArrowDown: "ArrowDown", ArrowLeft: "ArrowLeft", ArrowRight: "ArrowRight",
+    F1: "F1", F2: "F2", F3: "F3", F4: "F4", F5: "F5", F6: "F6",
+    F7: "F7", F8: "F8", F9: "F9", F10: "F10", F11: "F11", F12: "F12",
+} as const;
+
+export type KeyCode = typeof Keys[keyof typeof Keys];
+
+/**
+ * The properties of the entity. 
+ * From the eucalyptus editor, you are able to make custom 
+ * properties such as 'speed' or 'health'. This class allows 
+ * you to create and edit new value
+ * during the runtime of the script. 
+ * 
+ * Usage:
+ * ```ts
+ * props.setNumber("hp", 100);
+ * const hp = props.getNumber("hp"); // 100
+ * ```
+ */
+export class EntityProperties {
+    private data: Record<string, any>;
+
+    /**
+     * Create an EntityProperties wrapper.
+     *
+     * @param data - Optional initial properties object. A shallow reference is kept.
+     */
+    public constructor(data?: Record<string, any>) {
+        this.data = data || {};
+    }
+
+    /**
+     * Get the raw underlying properties object.
+     *
+     * Use this to serialize properties back to the host or perform bulk operations.
+     *
+     * @returns The raw Record<string, any> used to store properties.
+     *
+     * @example
+     * const raw = props.getRawProperties();
+     */
+    public getRawProperties(): Record<string, any> {
+        return this.data;
+    }
+
+    /**
+     * Set a string property.
+     *
+     * @param key - Property key.
+     * @param value - String value to set.
+     *
+     * @example
+     * props.setString("tag", "friendly");
+     */
+    public setString(key: string, value: string): void {
+        this.data[key] = value;
+    }
+
+    /**
+     * Set a numeric property.
+     *
+     * @param key - Property key.
+     * @param value - Number value to set.
+     *
+     * @example
+     * props.setNumber("speed", 4.2);
+     */
+    public setNumber(key: string, value: number): void {
+        this.data[key] = value;
+    }
+
+    /**
+     * Set a boolean property.
+     *
+     * @param key - Property key.
+     * @param value - Boolean value to set.
+     *
+     * @example
+     * props.setBool("isActive", true);
+     */
+    public setBool(key: string, value: boolean): void {
+        this.data[key] = value;
+    }
+
+    /**
+     * Get a string property.
+     *
+     * If the property is missing or falsy, an empty string ("") is returned.
+     *
+     * @param key - Property key.
+     * @returns The string value or "" when missing.
+     *
+     * @example
+     * const name = props.getString("name");
+     */
+    public getString(key: string): string {
+        return this.data[key] || "";
+    }
+
+    /**
+     * Get a numeric property.
+     *
+     * If the property is missing or falsy, 0 is returned.
+     *
+     * @param key - Property key.
+     * @returns The number value or 0 when missing.
+     *
+     * @example
+     * const speed = props.getNumber("speed");
+     */
+    public getNumber(key: string): number {
+        return this.data[key] || 0;
+    }
+
+    /**
+     * Get a boolean property.
+     *
+     * If the property is missing or falsy, false is returned.
+     *
+     * @param key - Property key.
+     * @returns The boolean value or false when missing.
+     *
+     * @example
+     * const active = props.getBool("isActive");
+     */
+    public getBool(key: string): boolean {
+        return this.data[key] || false;
+    }
+
+    /**
+     * Checks if the entity has a specific property as per a key. 
+     * 
+     * If the property exists, it will return true. If not, it will return false
+     * @param key - Property key.
+     * @returns - True is the value exists, false if not
+     * 
+     * @example
+     * ```ts
+     * if props.hasProperty("speed") {
+     *      let speed = props.getNumber("speed");
+     *      speed = speed+10;
+     * } else {
+     *      console.log("No value as speed exists");
+     * }
+     * ```
+     */
+    public hasProperty(key: string): boolean {
+        return key in this.data;
+    }
+}
+
+export class Entity {
+    public label: string;
+    public transform: Transform;
+    public properties: EntityProperties;
+
+    /**
+     * Creates a new instance of entity
+     * @param entityData - The entty data, typically parsed as an argument in the load or update functions
+     */
+    constructor(label: string, properties: EntityData, transform: TransformData) {
+        this.label = label;
+        this.transform = createTransformFromData(transform);
+        this.properties = new EntityProperties(properties);
+    }
+
+    /**
+     * Moves the player forward on the Z axis
+     * @param distance - The distance (as a number) it moves forward by
+     */
+    moveForward(distance: number): void {
+        const movement = new Vector3(0, 0, -distance);
+        this.transform.translate(movement);
+    }
+
+    /**
+     * Moves the player back on the Z axis
+     * @param distance - The distance (as a number) it moves back by
+     */
+    moveBack(distance: number): void {
+        const movement = new Vector3(0, 0, distance);
+        this.transform.translate(movement);
+    }
+
+    /**
+     * Moves the player left on the X axis
+     * @param distance - The distance (as a number) it moves left by
+     */
+    moveLeft(distance: number): void {
+        const movement = new Vector3(-distance, 0, 0);
+        this.transform.translate(movement);
+    }
+
+    /**
+     * Moves the player right on the X axis
+     * @param distance - The distance (as a number) it moves right by
+     */
+    moveRight(distance: number): void {
+        const movement = new Vector3(distance, 0, 0);
+        this.transform.translate(movement);
+    }
+
+    /**
+     * Moves the player up on the Y axis
+     * @param distance - The distance (as a number) it moves up by
+     */
+    moveUp(distance: number): void {
+        const movement = new Vector3(0, distance, 0);
+        this.transform.translate(movement);
+    }
+
+    /**
+     * Moves the player down on the Y axis
+     * @param distance - The distance (as a number) it moves down by
+     */
+    moveDown(distance: number): void {
+        const movement = new Vector3(0, -distance, 0);
+        this.transform.translate(movement);
+    }
+}
+
+
+/**
+ * Helper function that creates a new {@link Transform} from 
+ * a {@link TransformData}
+ * @param data - The raw transformable ({@link TransformData})data
+ * @returns - An instance of a {@link Transform}
+ */
+function createTransformFromData(data: TransformData): Transform {
+    const position = Vector3.fromArray(data.position);
+    const rotation = Quaternion.fromArray(data.rotation);
+    const scale = Vector3.fromArray(data.scale);
+    return new Transform(position, rotation, scale);
+}
+
+/**
+ * Camera class for controlling and manipulating cameras in the scene.
+ * Provides functionality for camera movement, switching, and property manipulation.
+ */
+export class Camera {
+    public label: string;
+
+    public eye: Vector3;
+    public target: Vector3;
+    public up: Vector3;
+    public aspect: number;
+    public fov: number;
+    public near: number;
+    public far: number;
+    public yaw: number;
+    public pitch: number;
+    public speed: number;
+    public sensitivity: number;
+    public camera_type: string;
+
+
+    /**
+     * Create a new Camera instance.
+     * 
+     * @param data - Optional camera data to initialize from
+     */
+    constructor(label: string, data: CameraData) {
+        this.eye = Vector3.fromArray(data.eye);
+        this.target = Vector3.fromArray(data.target);
+        this.up = Vector3.fromArray(data.up);
+        this.aspect = data.aspect;
+        this.fov = data.fov;
+        this.near = data.near;
+        this.far = data.far;
+        this.yaw = data.yaw;
+        this.pitch = data.pitch;
+        this.speed = data.speed;
+        this.sensitivity = data.sensitivity;
+        this.camera_type = data.camera_type;
+        this.label = label;
+    }
+
+    /**
+     * Track mouse movement for camera look controls.
+     * 
+     * @param delta - Mouse delta X and Y
+     * 
+     * @example
+     * ```ts
+     * let delta = entity.getMouseDelta();
+     * camera.track
+     * ```
+     */
+    trackMouseDelta(delta: [number, number]): void {
+        this.yaw += delta[0] * this.sensitivity;
+        this.pitch += delta[1] * this.sensitivity;
+        
+        this.pitch = dbMath.clamp(this.pitch, dbMath.degreesToRadians(-89.0), dbMath.degreesToRadians(89.0));
+
+        
+        let direction = new Vector3(
+            Math.cos(this.yaw) * Math.cos(this.pitch),
+            Math.sin(this.pitch),
+            Math.sin(this.yaw) * Math.cos(this.pitch),
+        );
+        this.target = this.eye.add(direction);
+    }
+}
+
+export class Light {
+
+}
+
+/**
+ * A raw format for storing the transform data, typically used as a
+ * FFI by the engine
+ */
+export interface TransformData {
+    position: [number, number, number];
+    rotation: [number, number, number, number]; // quaternion [x, y, z, w]
+    scale: [number, number, number];
+}
+
+/**
+ * A raw format for storing the custom entity properties from the engines
+ * raw FFI
+ */
+export interface EntityData {
+    custom_properties: Record<string, any>;
+}
+
+/**
+ * A raw format for storing the input data
+ */
+export interface InputData {
+    mouse_pos: [number, number];
+    pressed_keys: string[];
+    mouse_delta: [number, number] | null;
+    is_cursor_locked: boolean;
+}
+
+/**
+ * A raw format for storing the camera data
+ */
+export interface CameraData {
+    eye: [number, number, number];
+    target: [number, number, number];
+    up: [number, number, number];
+    aspect: number;
+    fov: number;
+    near: number;
+    far: number;
+    yaw: number;
+    pitch: number;
+    speed: number;
+    sensitivity: number;
+    camera_type: string;
+}
+
+/**
+ * A raw format for storing the light data
+ */
+export interface LightData {
+
+}
+
+/**
+ * A raw format for storing the scene data
+ */
+export interface RawSceneData {
+    entities: [{
+        label: string,
+        properties: EntityData, 
+        transform: TransformData
+    }];
+    cameras: [{
+        label: string,
+        data: CameraData
+    }];
+    lights: [{
+        label: string,
+        data: LightData
+    }];
+    input: InputData;
+}
+
+/**
+ * A wrapper class aimed to aid with input data
+ */
+export class Input {
+    public inputData?: InputData
+
+    // constructor(data: InputData) {
+    //     this.inputData = data;
+    // }
+
+    // empty because global variable
+    constructor() {
+
+    }
+
+    /**
+     * Checks if a key is pressed
+     * @param key - The specific keycode
+     * @returns - True is pressed, false if not
+     * 
+     * @example
+     * ```ts
+     * if entity.isKeyPressed(Keys::KeyW) {
+     *      console.log("The W key is pressed");
+     * }
+     * ```
+     */
+    isKeyPressed(key: KeyCode): boolean {
+        if (!this.inputData) return false;
+        return this.inputData.pressed_keys.indexOf(key) !== -1
+    }
+
+    /**
+     * Fetches the mouse position
+     * @returns - The x,y position of the mouse
+     */
+    getMousePosition(): [number, number] {
+        return this.inputData?.mouse_pos || [0, 0];
+    }
+
+    /**
+     * Fetches the change in the mouse position from the center (as it gets reset each frame)
+     * @returns - The dx,dy position of the mouse
+     */
+    getMouseDelta(): [number, number] {
+        return this.inputData?.mouse_delta || [0.0, 0.0];
+    }
+}
+
+/**
+ * The class containing all the different entities in this scene. 
+ */
+export class Scene {
+    // ensure none of these are public
+    current_entity?: string;
+    entities?: Entity[];
+    cameras?: Camera[];
+    lights?: Light[];
+
+    // Sets everything to default
+    constructor() {
+        this.cameras = [];
+        this.entities = [];
+        this.lights = [];
+    }
+
+    /**
+     * Returns a **reference** to the camera in the scene
+     * @param label - The label of the camera as set by you from the editor
+     */
+    public getCamera(label: string): Camera | undefined {
+        return this.cameras?.find(c => c.label === label);
+    }
+
+    /**
+     * Returns a **reference** to the light in the scene
+     * @param label - The label of the light as set by you from the editor
+     */
+    public getLight(label: string): Light | undefined {
+        return this.lights?.find(l => (l as any).label === label);
+    }
+
+    /**
+     * Fetches an entity as per its label. Returns the actual Entity instance (mutable reference).
+     * @param label - The label of the entity
+     */
+    public getEntity(label: string): Entity | undefined {
+        return this.entities?.find(e => e.label === label);
+    }
+
+    /**
+     * Fetches the current entity this script is attached to (returns mutable reference).
+     */
+    public getCurrentEntity(): Entity | undefined {
+        if (!this.current_entity) {
+            console.error("Unable to get entity: Have you added dropbear.start(s) yet?");
+            return undefined;
+        }
+        const ent = this.getEntity(this.current_entity);
+        if (!ent) {
+            console.error(`Unable to get entity: no entity with label "${this.current_entity}" found`);
+        }
+        return ent;
+    }
+}
+
+/**
+ * Starts the specific function by filling the scene data. 
+ * @param data 
+ */
+export function start(data: RawSceneData) {
+    data.cameras.forEach(camera => {
+        scene.cameras?.push(new Camera(camera.label, camera.data))
+    });
+    data.entities.forEach(entity => {
+        scene.entities?.push(new Entity(entity.label, entity.properties, entity.transform))
+    });
+    data.lights.forEach(light => {
+        scene.lights?.push(light)
+    });
+    input.inputData = data.input;
+}
+
+/**
+ * Ends the scripting function by returning a Partial RawSceneData for the
+ * rust client to take. 
+ */
+export function end(): Partial<RawSceneData> {
+    const out: Partial<RawSceneData> = {};
+
+    if (scene.entities && scene.entities.length) {
+        out.entities = scene.entities.map(e => {
+            return {
+                label: e.label,
+                properties: {
+                    custom_properties: e.properties.getRawProperties()
+                } as EntityData,
+                transform: {
+                    position: e.transform.position.as_array(),
+                    rotation: e.transform.rotation.as_array(),
+                    scale: e.transform.scale.as_array()
+                } as TransformData
+            };
+        }) as any;
+    }
+
+    if (scene.cameras && scene.cameras.length) {
+        out.cameras = scene.cameras.map(c => {
+            return {
+                label: c.label,
+                data: {
+                    eye: c.eye.as_array(),
+                    target: c.target.as_array(),
+                    up: c.up.as_array(),
+                    aspect: c.aspect,
+                    fov: c.fov,
+                    near: c.near,
+                    far: c.far,
+                    yaw: c.yaw,
+                    pitch: c.pitch,
+                    speed: c.speed,
+                    sensitivity: c.sensitivity,
+                    camera_type: c.camera_type
+                } as CameraData
+            };
+        }) as any;
+    }
+
+    if (scene.lights && scene.lights.length) {
+        out.lights = scene.lights.map(l => {
+            return {
+                label: (l as any).label || "",
+                data: {} as LightData
+            };
+        }) as any;
+    }
+
+    if (input.inputData) {
+        out.input = input.inputData;
+    }
+
+    return out;
+}
+
+// global variables
+/**
+ * A global variable that contains all the information about the scene. 
+ * 
+ * To use the variable, you need to run {@link start()} at the start of 
+ * the function and return {@link end()} at the end to send to the engine.  
+ * 
+ * @example
+ * ```ts
+ * export function onUpdate(s, dt: number) {
+ *    dropbear.start(s);
+ *    console.log("I'm being updated!");
+ *    return dropbear.end();
+ * }
+ * ```
+ */
+export const scene = new Scene();
+/**
+ * A global variable that contains all the information about the inputs
+ * such as the keyboard and the mouse.
+ * 
+ * To use the variable, you need to run {@link start()} at the start of 
+ * the function and return {@link end()} at the end to send to the engine.  
+ * 
+ * @example
+ * ```ts
+ * export function onUpdate(s, dt: number) {
+ *    dropbear.start(s);
+ *    console.log("I'm being updated!");
+ *    return dropbear.end();
+ * }
+ */
+export const input = new Input();
\ No newline at end of file
diff --git a/resources/shaders/light.wgsl b/resources/shaders/light.wgsl
new file mode 100644
index 0000000..57bedfa
--- /dev/null
+++ b/resources/shaders/light.wgsl
@@ -0,0 +1,61 @@
+// Shader for rendering the white cube for lighting (or diff depending on colour)
+
+struct Camera {
+    view_pos: vec4<f32>,
+    view_proj: mat4x4<f32>,
+}
+@group(0) @binding(0)
+var<uniform> camera: Camera;
+
+struct Light {
+    position: vec4<f32>,
+    direction: vec4<f32>, // x, y, z, outer_cutoff_angle
+    color: vec4<f32>, // r, g, b, light_type (0, 1, 2)
+    constant: f32,
+    lin: f32,
+    quadratic: f32,
+    cutoff: f32,
+}
+
+@group(1) @binding(0)
+var<uniform> light: Light;
+
+struct InstanceInput {
+    @location(5) model_matrix_0: vec4<f32>,
+    @location(6) model_matrix_1: vec4<f32>,
+    @location(7) model_matrix_2: vec4<f32>,
+    @location(8) model_matrix_3: vec4<f32>,
+}
+
+struct VertexInput {
+    @location(0) position: vec3<f32>,
+};
+
+struct VertexOutput {
+    @builtin(position) clip_position: vec4<f32>,
+    @location(0) color: vec3<f32>,
+};
+
+@vertex
+fn vs_main(
+    model: VertexInput,
+    instance: InstanceInput,
+) -> VertexOutput {
+    let model_matrix = mat4x4<f32>(
+        instance.model_matrix_0,
+        instance.model_matrix_1,
+        instance.model_matrix_2,
+        instance.model_matrix_3,
+    );
+    var out: VertexOutput;
+    out.clip_position = camera.view_proj * model_matrix * vec4<f32>(model.position, 1.0);
+    out.color = light.color.xyz;
+    return out;
+}
+
+// Fragment shader
+
+@fragment
+fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
+    return vec4<f32>(in.color, 1.0);
+}
\ No newline at end of file
diff --git a/resources/shaders/shader.wgsl b/resources/shaders/shader.wgsl
new file mode 100644
index 0000000..37903ae
--- /dev/null
+++ b/resources/shaders/shader.wgsl
@@ -0,0 +1,206 @@
+// Main shader for standard objects.
+const MAX_LIGHTS: u32 = 8;
+
+struct CameraUniform {
+    view_pos: vec4<f32>,
+    view_proj: mat4x4<f32>,
+};
+@group(1) @binding(0)
+var<uniform> camera: CameraUniform;
+
+struct Light {
+    position: vec4<f32>, // x, y, z, unused
+    direction: vec4<f32>, // x, y, z, unused
+    color: vec4<f32>, // r, g, b, light_type (0, 1, 2)
+    constant: f32,
+    lin: f32, // linear
+    quadratic: f32,
+    cutoff: f32,
+}
+
+struct LightArray {
+    lights: array<Light, MAX_LIGHTS>,
+    light_count: u32,
+    ambient_strength: f32,
+}
+
+@group(2) @binding(0)
+var<uniform> light_array: LightArray;
+
+struct InstanceInput {
+    @location(5) model_matrix_0: vec4<f32>,
+    @location(6) model_matrix_1: vec4<f32>,
+    @location(7) model_matrix_2: vec4<f32>,
+    @location(8) model_matrix_3: vec4<f32>,
+
+    @location(9) normal_matrix_0: vec3<f32>,
+    @location(10) normal_matrix_1: vec3<f32>,
+    @location(11) normal_matrix_2: vec3<f32>,
+};
+
+struct VertexInput {
+    @location(0) position: vec3<f32>,
+    @location(1) tex_coords: vec2<f32>,
+    @location(2) normal: vec3<f32>,
+};
+
+struct VertexOutput {
+    @builtin(position) clip_position: vec4<f32>,
+    @location(0) tex_coords: vec2<f32>,
+    @location(1) world_normal: vec3<f32>,
+    @location(2) world_position: vec3<f32>,
+};
+
+@vertex
+fn vs_main(
+    model: VertexInput,
+    instance: InstanceInput,
+) -> VertexOutput {
+    let model_matrix = mat4x4<f32>(
+        instance.model_matrix_0,
+        instance.model_matrix_1,
+        instance.model_matrix_2,
+        instance.model_matrix_3,
+    );
+    let normal_matrix = mat3x3<f32>(
+        instance.normal_matrix_0,
+        instance.normal_matrix_1,
+        instance.normal_matrix_2,
+    );
+    var out: VertexOutput;
+    out.tex_coords = model.tex_coords;
+    out.world_normal = normal_matrix * model.normal;
+    var world_position: vec4<f32> = model_matrix * vec4<f32>(model.position, 1.0);
+    out.world_position = world_position.xyz;
+    out.clip_position = camera.view_proj * world_position;
+    return out;
+}
+
+@group(0) @binding(0)
+var t_diffuse: texture_2d<f32>;
+@group(0) @binding(1)
+var s_diffuse: sampler;
+
+fn calculate_light(light: Light, world_pos: vec3<f32>, world_normal: vec3<f32>, view_dir: vec3<f32>) -> vec3<f32> {
+    let light_dir = normalize(light.position.xyz - world_pos);
+    
+    // dihfuse
+    let diffuse_strength = max(dot(world_normal, light_dir), 0.0);
+    let diffuse_color = light.color.xyz * diffuse_strength;
+    
+    // specular
+    let half_dir = normalize(view_dir + light_dir);
+    let specular_strength = pow(max(dot(world_normal, half_dir), 0.0), 32.0);
+    let specular_color = specular_strength * light.color.xyz;
+    
+    return diffuse_color + specular_color;
+}
+
+fn directional_light(
+    light: Light,
+    world_normal: vec3<f32>,
+    view_dir: vec3<f32>,
+    tex_color: vec3<f32>
+) -> vec3<f32> {
+    let light_dir = normalize(-light.direction.xyz);
+
+    let ambient = light.color.xyz * light_array.ambient_strength * tex_color;
+
+    let diff = max(dot(world_normal, light_dir), 0.0);
+    let diffuse = light.color.xyz * diff * tex_color;
+
+    let reflect_dir = reflect(-light_dir, world_normal);
+    let spec = pow(max(dot(view_dir, reflect_dir), 0.0), 32.0);
+    let specular = light.color.xyz * spec * tex_color;
+
+    return ambient + diffuse + specular;
+}
+
+// https://learnopengl.com/code_viewer_gh.php?code=src/2.lighting/5.2.light_casters_point/5.2.light_casters.fs
+// deal with later. current issue: it is showing only yellow and white in point light (weird...)
+// note: fixed, forgot to push attenuation values to gpu lol
+fn point_light(light: Light, world_pos: vec3<f32>, world_normal: vec3<f32>, view_dir: vec3<f32>, tex_color: vec3<f32>) -> vec3<f32> {
+    let norm = normalize(world_normal);
+    let light_dir = normalize(light.position.xyz - world_pos);
+    let diff = max(dot(norm, light_dir), 0.0);
+    let diffuse = light.color.xyz * diff * tex_color;
+
+    let shininess = 32.0;
+    let reflect_dir = reflect(-light_dir, norm);
+    let spec = pow(max(dot(view_dir, reflect_dir), 0.0), shininess);
+    let specular = light.color.xyz * spec * tex_color;
+
+    let distance = length(light.position.xyz - world_pos);
+    let attenuation = 1.0 / (light.constant + (light.lin * distance) + (light.quadratic * (distance * distance)));
+
+    return (diffuse + specular) * attenuation;
+}
+
+fn spot_light(light: Light, world_pos: vec3<f32>, world_normal: vec3<f32>, view_dir: vec3<f32>, tex_color: vec3<f32>) -> vec3<f32> {
+    let outer_cutoff = light.direction.w;
+    let ambient = light.color.xyz * light_array.ambient_strength * tex_color;
+
+    let norm = normalize(world_normal);
+    let light_dir = normalize(light.position.xyz - world_pos);
+    let diff = max(dot(norm, light_dir), 0.0);
+    var diffuse = light.color.xyz * diff * tex_color;
+
+    let shininess = 32.0;
+    let reflect_dir = reflect(-light_dir, norm);
+    let spec = pow(max(dot(view_dir, reflect_dir), 0.0), shininess);
+    var specular = light.color.xyz * spec * tex_color;
+
+    let theta = dot(light_dir, normalize(-light.direction.xyz));
+    let epsilon = light.cutoff - outer_cutoff;
+    let intensity = clamp((theta - outer_cutoff) / epsilon, 0.0, 1.0);
+
+    diffuse *= intensity;
+    specular *= intensity;
+
+    let distance = length(light.position.xyz - world_pos);
+    let attenuation = 1.0 / (light.constant + (light.lin * distance) + (light.quadratic * (distance * distance)));
+
+    let ambient_attenuated = ambient * attenuation;
+    let diffuse_attenuated = diffuse * attenuation;
+    let specular_attenuated = specular * attenuation;
+
+    return ambient_attenuated + diffuse_attenuated + specular_attenuated;
+}
+
+@fragment
+fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
+    var tex_color = textureSample(t_diffuse, s_diffuse, in.tex_coords);
+    if (tex_color.a < 0.1) {
+        discard;
+    }
+
+    let view_dir = normalize(camera.view_pos.xyz - in.world_position);
+    let world_normal = normalize(in.world_normal);
+
+    var final_color = vec3<f32>(0.0);
+
+    var total_ambient = vec3<f32>(0.0);
+    for (var i: u32 = 0u; i < light_array.light_count; i = i + 1u) {
+        let light = light_array.lights[i];
+        total_ambient += light.color.xyz * light_array.ambient_strength;
+    }
+
+    for (var i: u32 = 0u; i < light_array.light_count; i = i + 1u) {
+        let light = light_array.lights[i];
+
+        // light type is color.w
+        if light.color.w == 0.0 {
+            final_color += directional_light(light, world_normal, view_dir, tex_color.xyz);
+        } else if light.color.w == 1.0 {
+            // point
+            final_color += point_light(light, in.world_position, world_normal, view_dir, tex_color.xyz);
+        } else if light.color.w == 2.0 {
+            final_color += spot_light(light, in.world_position, world_normal, view_dir, tex_color.xyz);
+        }
+    }
+
+    // Combine ambient and lighting
+    final_color = (total_ambient * tex_color.xyz) + final_color;
+
+    return vec4<f32>(final_color, tex_color.a);
+}
\ No newline at end of file
diff --git a/resources/template.ts b/resources/template.ts
new file mode 100644
index 0000000..552032a
--- /dev/null
+++ b/resources/template.ts
@@ -0,0 +1,25 @@
+// dropbear-engine script template for eucalyptus
+import * as dropbear from "./dropbear";
+
+export function onLoad(s) {
+    dropbear.start(s);
+    // ------- Your own code here -------
+    console.log("I have been awoken");
+
+
+    // ----------------------------------
+    // Do not remove anything outside unless
+    // you know what you are doing.
+    return dropbear.end();
+}
+
+export function onUpdate(s, dt: number) {
+    dropbear.start(s);
+    // ------- Your own code here -------
+    console.log("I'm being updated!");
+    
+
+    // ----------------------------------
+    // Same thing over here!
+    return dropbear.end();
+}
\ No newline at end of file