kitgit

tirbofish/dropbear · commit

726ca434d30ebb0bbbdf623d605d87988532e7e7

preparing for wasm scripting. this is so tedious :(

Unverified

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

view full diff

diff --git a/Cargo.toml b/Cargo.toml
index 81b26bc..e4c2e8e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -50,7 +50,7 @@ wgpu = "26"
 winit = { version = "0.30", features = [] }
 zip = "4.6"
 walkdir = "2.3"
-wasmtime = "36.0"
+wasmer = { version = "6.0" }
 
 [workspace.dependencies.image]
 version = "0.25"
diff --git a/dropbear-engine/Cargo.toml b/dropbear-engine/Cargo.toml
index 7d184cf..f59464d 100644
--- a/dropbear-engine/Cargo.toml
+++ b/dropbear-engine/Cargo.toml
@@ -32,7 +32,6 @@ hecs.workspace = true
 once_cell.workspace = true
 parking_lot.workspace = true
 lazy_static.workspace = true
-tokio.workspace = true
 
 [target.'cfg(not(target_os = "android"))'.dependencies]
 rfd.workspace = true
diff --git a/dropbear-engine/src/lib.rs b/dropbear-engine/src/lib.rs
index cb37edd..36e5a2f 100644
--- a/dropbear-engine/src/lib.rs
+++ b/dropbear-engine/src/lib.rs
@@ -17,9 +17,9 @@ use std::io::Write;
 use chrono::Local;
 use egui::TextureId;
 use egui_wgpu_backend::ScreenDescriptor;
+use futures::executor::block_on;
 use gilrs::{Gilrs, GilrsBuilder};
 use spin_sleep::SpinSleeper;
-use tokio::task::LocalSet;
 use std::{
     fmt::{self, Display, Formatter},
     sync::Arc,
@@ -404,20 +404,10 @@ impl App {
     /// - setup: A closure that can initialise the first scenes, such as a menu or the game itself.
     /// It takes an input of a scene manager and an input manager, and expects you to return back the changed
     /// managers.
-    pub async fn run<F>(config: WindowConfiguration, app_name: &str, setup: F) -> anyhow::Result<()>
+    pub fn run<F>(config: WindowConfiguration, app_name: &str, setup: F) -> anyhow::Result<()>
     where
         F: FnOnce(scene::Manager, input::Manager) -> (scene::Manager, input::Manager),
     {
-        // if cfg!(debug_assertions) {
-        //     log::info!("Running in dev mode");
-        //     let app_target = app_name.replace('-', "_");
-        //     let log_config = format!("dropbear_engine=trace,{}=debug,warn", app_target);
-        //     unsafe { std::env::set_var("RUST_LOG", log_config) };
-        // }
-        //
-        // #[cfg(not(target_os = "android"))]
-        // let _ = env_logger::try_init();
-
         let log_dir = app_dirs2::app_root(AppDataType::UserData, &config.app_info)
             .expect("Failed to get app data directory")
             .join("logs");
@@ -542,7 +532,7 @@ impl ApplicationHandler for App {
 
         let window = Arc::new(event_loop.create_window(window_attributes).unwrap());
 
-        self.state = Some(tokio::runtime::Handle::current().block_on(State::new(window)).unwrap());
+        self.state = Some(block_on(State::new(window)).unwrap());
 
         if let Some(state) = &mut self.state {
             let size = state.window.inner_size();
@@ -579,10 +569,7 @@ impl ApplicationHandler for App {
 
                 self.input_manager.update(&mut self.gilrs);
                 
-                let ls = LocalSet::new();
-                let render_result = tokio::runtime::Handle::current().block_on(ls.run_until(async {
-                    state.render(&mut self.scene_manager, self.delta_time, event_loop).await
-                }));
+                let render_result = block_on(state.render(&mut self.scene_manager, self.delta_time, event_loop));
 
                 if let Err(e) = render_result {
                     log::error!("Render failed: {:?}", e);
diff --git a/dropbear-engine/src/model.rs b/dropbear-engine/src/model.rs
index 47070c1..cd6effd 100644
--- a/dropbear-engine/src/model.rs
+++ b/dropbear-engine/src/model.rs
@@ -8,7 +8,6 @@ use russimp_ng::{
 use wgpu::{BufferAddress, VertexAttribute, VertexBufferLayout, util::DeviceExt};
 use lazy_static::lazy_static;
 use parking_lot::Mutex;
-use tokio::task;
 use crate::graphics::{Graphics, NO_MODEL, Texture};
 use crate::utils::ResourceReference;
 
@@ -43,20 +42,8 @@ pub struct Mesh {
     pub material: usize,
 }
 
-struct ProcessedMesh {
-    name: String,
-    vertices: Vec<ModelVertex>,
-    indices: Vec<u32>,
-    material_index: usize,
-}
-
-struct ProcessedMaterial {
-    name: String,
-    texture_bytes: Option<Vec<u8>>,
-}
-
 impl Model {
-    pub async fn load_from_memory_async(
+    pub fn load_from_memory(
         graphics: &Graphics<'_>,
         buffer: Vec<u8>,
         label: Option<&str>,
@@ -69,97 +56,9 @@ impl Model {
             return Ok(cached_model.clone());
         }
 
-        log::debug!("Loading from memory asynchronously");
+        log::debug!("Loading from memory");
         let res_ref = ResourceReference::from_bytes(buffer.clone());
 
-        let label_owned = label.clone().unwrap_or_default().to_string();
-
-        let (processed_materials, processed_meshes) = task::spawn_blocking(move || {
-            Self::process_scene_from_buffer(buffer, Some(label_owned.as_str()))
-        }).await??;
-
-        let model = Self::create_gpu_model(
-            graphics,
-            processed_materials,
-            processed_meshes,
-            cache_key.clone(),
-            res_ref,
-        ).await?;
-
-        MEMORY_MODEL_CACHE.lock().insert(cache_key, model.clone());
-        log::debug!("Model cached from memory: {:?}", label);
-        Ok(model)
-    }
-
-    pub async fn load_async(
-        graphics: &Graphics<'_>,
-        path: &PathBuf,
-        label: Option<&str>,
-    ) -> anyhow::Result<Model> {
-        let file_name = path.file_name();
-        log::debug!("Loading model asynchronously [{:?}]", file_name);
-
-        let path_str = path.to_string_lossy().to_string();
-
-        if let Some(cached_model) = MODEL_CACHE.lock().get(&path_str) {
-            log::debug!("Model loaded from cache: {:?}", path_str);
-            return Ok(cached_model.clone());
-        }
-
-        let path_clone = path.clone();
-        
-        let (processed_materials, processed_meshes) = task::spawn_blocking(move || {
-            Self::process_scene_from_file(&path_clone)
-        }).await??;
-
-        let model_label = if let Some(l) = label {
-            l.to_string()
-        } else {
-            file_name.unwrap().to_str().unwrap().split('.').next().unwrap().to_string()
-        };
-
-        let resource_ref = ResourceReference::from_path(path)?;
-        
-        let model = Self::create_gpu_model(
-            graphics,
-            processed_materials,
-            processed_meshes,
-            model_label,
-            resource_ref,
-        ).await?;
-
-        MODEL_CACHE.lock().insert(path_str, model.clone());
-        log::debug!("Model cached and loaded: {:?}", file_name);
-        Ok(model)
-    }
-
-    pub fn load_from_memory(
-        graphics: &Graphics<'_>,
-        buffer: Vec<u8>,
-        label: Option<&str>,
-    ) -> anyhow::Result<Model> {
-        let rt = tokio::runtime::Handle::try_current()
-            .or_else(|_| {
-                tokio::runtime::Runtime::new().map(|rt| rt.handle().clone())
-            })?;
-        
-        rt.block_on(Self::load_from_memory_async(graphics, buffer, label))
-    }
-
-    pub fn load(
-        graphics: &Graphics<'_>,
-        path: &PathBuf,
-        label: Option<&str>,
-    ) -> anyhow::Result<Model> {
-        let rt = tokio::runtime::Handle::current();
-        
-        rt.block_on(Self::load_async(graphics, path, label))
-    }
-
-    fn process_scene_from_buffer(
-        buffer: Vec<u8>,
-        label: Option<&str>,
-    ) -> anyhow::Result<(Vec<ProcessedMaterial>, Vec<ProcessedMesh>)> {
         let scene = match Scene::from_buffer(
             buffer.as_slice(),
             vec![
@@ -184,12 +83,137 @@ impl Model {
             },
         };
 
-        Self::process_scene(scene)
+        let mut materials = Vec::new();
+        for m in &scene.materials {
+            let mut name = String::new();
+            let diffuse_bytes_opt = m
+                .textures
+                .iter()
+                .find(|(t_type, _)| **t_type == TextureType::Diffuse)
+                .and_then(|(_, tex)| {
+                    name = tex.borrow().filename.clone();
+                    match &tex.borrow().data {
+                        DataContent::Bytes(b) => Some(b.clone()),
+                        DataContent::Texel(_) => {
+                            log::warn!("Skipping texel-based texture for material '{}'", &name);
+                            None
+                        }
+                    }
+                });
+
+            let diffuse_texture = if let Some(bytes) = diffuse_bytes_opt {
+                Texture::new(graphics, &bytes)
+            } else {
+                if !name.is_empty() {
+                    log::warn!(
+                        "Error loading material {}, using default missing texture",
+                        name
+                    );
+                } else {
+                    log::warn!("Error loading material, using default missing texture");
+                }
+                Texture::new(graphics, GREY_TEXTURE_BYTES)
+            };
+
+            let bind_group = diffuse_texture.bind_group().to_owned();
+            materials.push(Material {
+                name: name,
+                diffuse_texture,
+                bind_group,
+            });
+        }
+
+        let mut meshes = Vec::new();
+        for mesh in &scene.meshes {
+            let vertices: Vec<ModelVertex> = mesh
+                .vertices
+                .iter()
+                .enumerate()
+                .map(|(i, v)| {
+                    let normal = mesh.normals.get(i).copied().unwrap_or(Vector3D {
+                        x: 0.0,
+                        y: 1.0,
+                        z: 0.0,
+                    });
+                    let tex_coords = mesh
+                        .texture_coords
+                        .get(0)
+                        .and_then(|coords| coords.as_ref().and_then(|vec| vec.get(i)))
+                        .map(|tc| [tc.x, tc.y])
+                        .unwrap_or([0.0, 0.0]);
+                    ModelVertex {
+                        position: [v.x, v.y, v.z],
+                        tex_coords,
+                        normal: [normal.x, normal.y, normal.z],
+                    }
+                })
+                .collect();
+
+            let indices: Vec<u32> = mesh
+                .faces
+                .iter()
+                .flat_map(|f| f.0.iter().copied())
+                .collect();
+
+            let vertex_buffer =
+                graphics
+                    .state
+                    .device
+                    .create_buffer_init(&wgpu::util::BufferInitDescriptor {
+                        label: Some(&format!("{:?} Vertex Buffer", label)),
+                        contents: bytemuck::cast_slice(&vertices),
+                        usage: wgpu::BufferUsages::VERTEX,
+                    });
+            let index_buffer =
+                graphics
+                    .state
+                    .device
+                    .create_buffer_init(&wgpu::util::BufferInitDescriptor {
+                        label: Some(&format!("{:?} Index Buffer", label)),
+                        contents: bytemuck::cast_slice(&indices),
+                        usage: wgpu::BufferUsages::INDEX,
+                    });
+
+            meshes.push(Mesh {
+                name: mesh.name.clone(),
+                vertex_buffer,
+                index_buffer,
+                num_elements: indices.len() as u32,
+                material: mesh.material_index as usize,
+            });
+        }
+        log::debug!("Successfully loaded model [{:?}]", label);
+        let model = Model {
+            meshes,
+            materials,
+            label: if let Some(l) = label {
+                l.to_string()
+            } else {
+                String::from("No named model")
+            },
+            path: res_ref,
+        };
+
+        MEMORY_MODEL_CACHE.lock().insert(cache_key, model.clone());
+        log::debug!("Model cached from memory: {:?}", label);
+        Ok(model)
     }
 
-    fn process_scene_from_file(
+    pub fn load(
+        graphics: &Graphics<'_>,
         path: &PathBuf,
-    ) -> anyhow::Result<(Vec<ProcessedMaterial>, Vec<ProcessedMesh>)> {
+        label: Option<&str>,
+    ) -> anyhow::Result<Model> {
+        let file_name = path.file_name();
+        log::debug!("Loading model [{:?}]", file_name);
+
+        let path_str = path.to_string_lossy().to_string();
+
+        if let Some(cached_model) = MODEL_CACHE.lock().get(&path_str) {
+            log::debug!("Model loaded from cache: {:?}", path_str);
+            return Ok(cached_model.clone());
+        }
+
         let scene = match Scene::from_file(
             path.to_str().unwrap(),
             vec![
@@ -213,155 +237,120 @@ impl Model {
             },
         };
 
-        Self::process_scene(scene)
-    }
-
-    fn process_scene(scene: Scene) -> anyhow::Result<(Vec<ProcessedMaterial>, Vec<ProcessedMesh>)> {
-        let processed_materials: Vec<ProcessedMaterial> = scene
-            .materials
-            .into_iter()
-            .map(|m| {
-                let mut name = String::new();
-                let texture_bytes = m
-                    .textures
-                    .iter()
-                    .find(|(t_type, _)| **t_type == TextureType::Diffuse)
-                    .and_then(|(_, tex)| {
-                        name = tex.borrow().filename.clone();
-                        match &tex.borrow().data {
-                            DataContent::Bytes(b) => Some(b.clone()),
-                            DataContent::Texel(_) => {
-                                log::warn!("Skipping texel-based texture for material '{}'", &name);
-                                None
-                            }
+        let mut materials = Vec::new();
+        for m in &scene.materials {
+            let mut name = String::new();
+            let diffuse_bytes_opt = m
+                .textures
+                .iter()
+                .find(|(t_type, _)| **t_type == TextureType::Diffuse)
+                .and_then(|(_, tex)| {
+                    name = tex.borrow().filename.clone();
+                    match &tex.borrow().data {
+                        DataContent::Bytes(b) => Some(b.clone()),
+                        DataContent::Texel(_) => {
+                            log::warn!("Skipping texel-based texture for material '{}'", &name);
+                            None
                         }
-                    });
-
-                ProcessedMaterial {
-                    name,
-                    texture_bytes,
-                }
-            })
-            .collect();
-
-        let processed_meshes: Vec<ProcessedMesh> = scene
-            .meshes
-            .into_iter()
-            .map(|mesh| {
-                let vertices: Vec<ModelVertex> = mesh
-                    .vertices
-                    .iter()
-                    .enumerate()
-                    .map(|(i, v)| {
-                        let normal = mesh.normals.get(i).copied().unwrap_or(Vector3D {
-                            x: 0.0,
-                            y: 1.0,
-                            z: 0.0,
-                        });
-                        let tex_coords = mesh
-                            .texture_coords
-                            .get(0)
-                            .and_then(|coords| coords.as_ref().and_then(|vec| vec.get(i)))
-                            .map(|tc| [tc.x, tc.y])
-                            .unwrap_or([0.0, 0.0]);
-                        ModelVertex {
-                            position: [v.x, v.y, v.z],
-                            tex_coords,
-                            normal: [normal.x, normal.y, normal.z],
-                        }
-                    })
-                    .collect();
-
-                let indices: Vec<u32> = mesh
-                    .faces
-                    .iter()
-                    .flat_map(|f| f.0.iter().copied())
-                    .collect();
-
-                ProcessedMesh {
-                    name: mesh.name,
-                    vertices,
-                    indices,
-                    material_index: mesh.material_index as usize,
+                    }
+                });
+
+            let diffuse_texture = if let Some(bytes) = diffuse_bytes_opt {
+                Texture::new(graphics, &bytes)
+            } else {
+                if !name.is_empty() {
+                    log::warn!(
+                        "Error loading material {}, using default missing texture",
+                        name
+                    );
+                } else {
+                    log::warn!("Error loading material, using default missing texture");
                 }
-            })
-            .collect();
-
-        Ok((processed_materials, processed_meshes))
-    }
+                Texture::new(graphics, GREY_TEXTURE_BYTES)
+            };
+
+            let bind_group = diffuse_texture.bind_group().to_owned();
+            materials.push(Material {
+                name: name,
+                diffuse_texture,
+                bind_group,
+            });
+        }
 
-    async fn create_gpu_model(
-        graphics: &Graphics<'_>,
-        processed_materials: Vec<ProcessedMaterial>,
-        processed_meshes: Vec<ProcessedMesh>,
-        label: String,
-        path: ResourceReference,
-    ) -> anyhow::Result<Model> {
-        // Create materials with textures
-        let materials: Vec<Material> = processed_materials
-            .into_iter()
-            .map(|pm| {
-                let diffuse_texture = if let Some(bytes) = pm.texture_bytes {
-                    Texture::new(graphics, &bytes)
-                } else {
-                    if !pm.name.is_empty() {
-                        log::warn!(
-                            "Error loading material {}, using default missing texture",
-                            pm.name
-                        );
-                    } else {
-                        log::warn!("Error loading material, using default missing texture");
+        let mut meshes = Vec::new();
+        for mesh in &scene.meshes {
+            let vertices: Vec<ModelVertex> = mesh
+                .vertices
+                .iter()
+                .enumerate()
+                .map(|(i, v)| {
+                    let normal = mesh.normals.get(i).copied().unwrap_or(Vector3D {
+                        x: 0.0,
+                        y: 1.0,
+                        z: 0.0,
+                    });
+                    let tex_coords = mesh
+                        .texture_coords
+                        .get(0)
+                        .and_then(|coords| coords.as_ref().and_then(|vec| vec.get(i)))
+                        .map(|tc| [tc.x, tc.y])
+                        .unwrap_or([0.0, 0.0]);
+                    ModelVertex {
+                        position: [v.x, v.y, v.z],
+                        tex_coords,
+                        normal: [normal.x, normal.y, normal.z],
                     }
-                    Texture::new(graphics, GREY_TEXTURE_BYTES)
-                };
-
-                let bind_group = diffuse_texture.bind_group().to_owned();
-                Material {
-                    name: pm.name,
-                    diffuse_texture,
-                    bind_group,
-                }
-            })
-            .collect();
+                })
+                .collect();
 
-        let meshes: Vec<Mesh> = processed_meshes
-            .into_iter()
-            .map(|pm| {
-                let vertex_buffer = graphics
+            let indices: Vec<u32> = mesh
+                .faces
+                .iter()
+                .flat_map(|f| f.0.iter().copied())
+                .collect();
+
+            let vertex_buffer =
+                graphics
                     .state
                     .device
                     .create_buffer_init(&wgpu::util::BufferInitDescriptor {
-                        label: Some(&format!("{} Vertex Buffer", label)),
-                        contents: bytemuck::cast_slice(&pm.vertices),
+                        label: Some(&format!("{:?} Vertex Buffer", file_name)),
+                        contents: bytemuck::cast_slice(&vertices),
                         usage: wgpu::BufferUsages::VERTEX,
                     });
-
-                let index_buffer = graphics
+            let index_buffer =
+                graphics
                     .state
                     .device
                     .create_buffer_init(&wgpu::util::BufferInitDescriptor {
-                        label: Some(&format!("{} Index Buffer", label)),
-                        contents: bytemuck::cast_slice(&pm.indices),
+                        label: Some(&format!("{:?} Index Buffer", file_name)),
+                        contents: bytemuck::cast_slice(&indices),
                         usage: wgpu::BufferUsages::INDEX,
                     });
 
-                Mesh {
-                    name: pm.name,
-                    vertex_buffer,
-                    index_buffer,
-                    num_elements: pm.indices.len() as u32,
-                    material: pm.material_index,
-                }
-            })
-            .collect();
-
-        log::debug!("Successfully loaded model [{}]", label);
-        Ok(Model {
+            meshes.push(Mesh {
+                name: mesh.name.clone(),
+                vertex_buffer,
+                index_buffer,
+                num_elements: indices.len() as u32,
+                material: mesh.material_index as usize,
+            });
+        }
+        log::debug!("Successfully loaded model [{:?}]", file_name);
+        let model = Model {
             meshes,
             materials,
-            label,
-            path,
-        })
+            label: if let Some(l) = label {
+                l.to_string()
+            } else {
+                String::from(file_name.unwrap().to_str().unwrap().split(".").into_iter().next().unwrap())
+            },
+            path: ResourceReference::from_path(path).unwrap(),
+        };
+
+        MODEL_CACHE.lock().insert(path_str, model.clone());
+        log::debug!("Model cached and loaded: {:?}", file_name);
+        Ok(model)
     }
 }
 
diff --git a/eucalyptus-core/Cargo.toml b/eucalyptus-core/Cargo.toml
index 33979a7..417b464 100644
--- a/eucalyptus-core/Cargo.toml
+++ b/eucalyptus-core/Cargo.toml
@@ -26,9 +26,7 @@ serde.workspace = true
 winit.workspace = true
 zip.workspace = true
 rustyscript.workspace = true
-tokio.workspace = true
-
-wasmtime.workspace = true
+wasmer.workspace = true
 
 [features]
 editor = []
diff --git a/eucalyptus-core/src/input.rs b/eucalyptus-core/src/input.rs
new file mode 100644
index 0000000..b18352a
--- /dev/null
+++ b/eucalyptus-core/src/input.rs
@@ -0,0 +1,41 @@
+use std::{collections::{HashMap, HashSet}, time::{Duration, Instant}};
+
+use winit::{event::MouseButton, keyboard::KeyCode};
+
+#[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,
+}
+
+
+impl Default for InputState {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+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;
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/lib.rs b/eucalyptus-core/src/lib.rs
index 0ac84c1..a03b8a9 100644
--- a/eucalyptus-core/src/lib.rs
+++ b/eucalyptus-core/src/lib.rs
@@ -1,3 +1,4 @@
+pub mod input;
 pub mod logging;
 pub mod camera;
 pub mod states;
diff --git a/eucalyptus-core/src/scripting.rs b/eucalyptus-core/src/scripting.rs
new file mode 100644
index 0000000..c33fea6
--- /dev/null
+++ b/eucalyptus-core/src/scripting.rs
@@ -0,0 +1,226 @@
+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::input::InputState;
+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 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> {
+        todo!();
+    }
+
+    pub fn load_script_from_source(&mut self, script_name: &String, script_content: &String) -> anyhow::Result<String> {
+        todo!();
+    }
+
+    pub fn load_script(&mut self, script_path: &PathBuf) -> anyhow::Result<String> {
+        todo!();
+    }
+
+    pub fn init_entity_script(
+        &mut self,
+        entity_id: hecs::Entity,
+        script_name: &str,
+        world: &mut World,
+        input_state: &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() {
+            todo!();
+            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: &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() {
+            todo!();
+        } else {
+            log_once::error_once!("Unable to fetch compiled scripts for entity {:?}. Script Name: {}", entity_id, script_name);
+        }
+        Ok(())
+    }
+
+    pub fn remove_entity_script(&mut self, entity_id: hecs::Entity) {
+        self.entity_script_data.remove(&entity_id);
+    }
+}
+
+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(())
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting/camera.rs b/eucalyptus-core/src/scripting/camera.rs
deleted file mode 100644
index 9921526..0000000
--- a/eucalyptus-core/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-core/src/scripting/entity.rs b/eucalyptus-core/src/scripting/entity.rs
deleted file mode 100644
index c4a8652..0000000
--- a/eucalyptus-core/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-core/src/scripting/input.rs b/eucalyptus-core/src/scripting/input.rs
deleted file mode 100644
index d590516..0000000
--- a/eucalyptus-core/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-core/src/scripting/lighting.rs b/eucalyptus-core/src/scripting/lighting.rs
deleted file mode 100644
index 0e416e3..0000000
--- a/eucalyptus-core/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-core/src/scripting/math.rs b/eucalyptus-core/src/scripting/math.rs
deleted file mode 100644
index 761f08d..0000000
--- a/eucalyptus-core/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-core/src/scripting/mod.rs b/eucalyptus-core/src/scripting/mod.rs
deleted file mode 100644
index 37ffef7..0000000
--- a/eucalyptus-core/src/scripting/mod.rs
+++ /dev/null
@@ -1,834 +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};
-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 struct ScriptManager {
-    pub runtime: Runtime,
-    compiled_scripts: HashMap<String, ModuleHandle>,
-    entity_script_data: HashMap<hecs::Entity, serde_json::Value>,
-}
-
-impl ScriptManager {
-    pub async fn new() -> anyhow::Result<Self> {
-        // let mut runtime = Runtime::new(RuntimeOptions::default())?;
-        let mut runtime = Runtime::with_tokio_runtime_handle(RuntimeOptions::default(), tokio::runtime::Handle::try_current()?)?;
-        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(())
-    }
-}
-
-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(())
-}
\ No newline at end of file
diff --git a/eucalyptus-editor/Cargo.toml b/eucalyptus-editor/Cargo.toml
index 9d3729a..5a8e793 100644
--- a/eucalyptus-editor/Cargo.toml
+++ b/eucalyptus-editor/Cargo.toml
@@ -4,6 +4,7 @@ version.workspace = true
 edition.workspace = true
 license-file.workspace = true
 repository.workspace = true
+default-run = "eucalyptus-editor"
 readme = "README.md"
 
 [dependencies]
@@ -33,7 +34,6 @@ winit.workspace = true
 clap.workspace = true
 walkdir.workspace = true
 zip.workspace = true
-tokio.workspace = true
 
 [target.'cfg(not(target_os = "android"))'.dependencies]
 rfd.workspace = true
diff --git a/eucalyptus-editor/src/editor/component.rs b/eucalyptus-editor/src/editor/component.rs
index 60f165b..004049f 100644
--- a/eucalyptus-editor/src/editor/component.rs
+++ b/eucalyptus-editor/src/editor/component.rs
@@ -2,13 +2,13 @@
 
 use std::time::Instant;
 use egui::{CollapsingHeader, ComboBox, Ui};
+use eucalyptus_core::scripting::{ScriptAction, TEMPLATE_SCRIPT};
 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;
 
diff --git a/eucalyptus-editor/src/editor/mod.rs b/eucalyptus-editor/src/editor/mod.rs
index a68512d..f5f8b9d 100644
--- a/eucalyptus-editor/src/editor/mod.rs
+++ b/eucalyptus-editor/src/editor/mod.rs
@@ -25,7 +25,7 @@ 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::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;
@@ -73,7 +73,7 @@ pub struct Editor {
 }
 
 impl Editor {
-    pub async fn new() -> Self {
+    pub fn new() -> Self {
         let tabs = vec![EditorTab::Viewport];
         let mut dock_state = DockState::new(tabs);
 
@@ -125,7 +125,7 @@ impl Editor {
             viewport_mode: ViewportMode::None,
             signal: Signal::None,
             undo_stack: Vec::new(),
-            script_manager: ScriptManager::new().await.unwrap(),
+            script_manager: ScriptManager::new().unwrap(),
             editor_state: EditorState::Editing,
             gizmo_mode: EnumSet::empty(),
             play_mode_backup: None,
diff --git a/eucalyptus-editor/src/main.rs b/eucalyptus-editor/src/main.rs
index de4144b..5b1a62c 100644
--- a/eucalyptus-editor/src/main.rs
+++ b/eucalyptus-editor/src/main.rs
@@ -16,8 +16,7 @@ pub const APP_INFO: app_dirs2::AppInfo = app_dirs2::AppInfo {
     author: "4tkbytes",
 };
 
-#[tokio::main]
-async fn main() -> anyhow::Result<()> {
+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\
@@ -114,11 +113,9 @@ async fn main() -> anyhow::Result<()> {
             };
 
             let main_menu = Rc::new(RefCell::new(crate::menu::MainMenu::new()));
-            let editor = Rc::new(RefCell::new(crate::editor::Editor::new().await));
+            let editor = Rc::new(RefCell::new(crate::editor::Editor::new()));
 
             let _app = dropbear_engine::run_app!(config, |mut scene_manager, mut input_manager| {
-                
-
                 scene::add_scene_with_input(
                     &mut scene_manager,
                     &mut input_manager,
@@ -136,7 +133,6 @@ async fn main() -> anyhow::Result<()> {
 
                 (scene_manager, input_manager)
             })
-            .await
             .unwrap();
         }
         _ => unreachable!(),