kitgit

tirbofish/dropbear · commit

48f233823e98332210df9fa2870d2103ae9bbe32

refactor: edit compiles, but no components being shown.

Unverified

Thribhu K <4tkbytes@pm.me> · 2026-02-16 07:10

view full diff

diff --git a/crates/dropbear-engine/src/animation.rs b/crates/dropbear-engine/src/animation.rs
index 26634a0..0a99650 100644
--- a/crates/dropbear-engine/src/animation.rs
+++ b/crates/dropbear-engine/src/animation.rs
@@ -1,9 +1,11 @@
-use dropbear_traits::{Component, ComponentDescriptor, ComponentInitContext, ComponentInitFuture, InsertBundle, SerializableComponent};
+use dropbear_traits::{Component, ComponentDescriptor, ComponentInitContext, ComponentInitFuture, ComponentUpdateContext, InsertBundle, SerializableComponent};
 use std::collections::HashMap;
 use std::sync::Arc;
 use egui::{CollapsingHeader, Ui};
 use glam::Mat4;
 use wgpu::util::DeviceExt;
+use crate::asset::ASSET_REGISTRY;
+use crate::entity::MeshRenderer;
 use crate::graphics::SharedGraphicsContext;
 use crate::model::{AnimationInterpolation, ChannelValues, Model, NodeTransform};
 use std::any::Any;
@@ -73,6 +75,29 @@ impl Component for AnimationComponent {
             // todo: complete some more
         });
     }
+
+    fn update(&mut self, ctx: &mut ComponentUpdateContext) {
+        let world = ctx.world();
+        let Ok(renderer) = world.get::<&MeshRenderer>(ctx.entity) else {
+            return;
+        };
+
+        let handle = renderer.model();
+        if handle.is_null() {
+            return;
+        }
+
+        let registry = ASSET_REGISTRY.read();
+        let Some(model) = registry.get_model(handle) else {
+            return;
+        };
+
+        self.update(ctx.dt, model);
+
+        if let Some(graphics) = ctx.resources.get::<Arc<SharedGraphicsContext>>() {
+            self.prepare_gpu_resources(graphics.clone());
+        }
+    }
 }
 
 impl Default for AnimationComponent {
diff --git a/crates/dropbear-engine/src/entity.rs b/crates/dropbear-engine/src/entity.rs
index 2b96893..b1d6af4 100644
--- a/crates/dropbear-engine/src/entity.rs
+++ b/crates/dropbear-engine/src/entity.rs
@@ -347,6 +347,7 @@ impl Transform {
 pub struct MeshRenderer {
     import_scale: f32,
 
+    pub is_selected: bool,
     handle: Handle<Model>,
     pub instance: Instance,
     previous_matrix: DMat4,
@@ -361,6 +362,7 @@ impl MeshRenderer {
             previous_matrix: DMat4::IDENTITY,
             import_scale: 1.0,
             texture_override: None,
+            is_selected: false,
         }
     }
     
@@ -382,6 +384,7 @@ impl MeshRenderer {
             import_scale: 1.0,
             previous_matrix: DMat4::IDENTITY,
             texture_override: None,
+            is_selected: false,
         })
     }
 
diff --git a/crates/dropbear-engine/src/lib.rs b/crates/dropbear-engine/src/lib.rs
index 5841f21..432307d 100644
--- a/crates/dropbear-engine/src/lib.rs
+++ b/crates/dropbear-engine/src/lib.rs
@@ -21,6 +21,7 @@ pub mod mipmap;
 pub mod sky;
 pub mod features;
 pub mod animation;
+pub use dropbear_traits as component;
 
 features! {
     pub mod build {
diff --git a/crates/dropbear-traits/src/lib.rs b/crates/dropbear-traits/src/lib.rs
index 459f85f..ec5af64 100644
--- a/crates/dropbear-traits/src/lib.rs
+++ b/crates/dropbear-traits/src/lib.rs
@@ -53,6 +53,7 @@ pub trait Component {
     fn deserialize(serialized: &Self::Serialized) -> Self;
     fn serialize(&self) -> Self::Serialized;
     fn inspect(&mut self, ui: &mut egui::Ui);
+    fn update(&mut self, _ctx: &mut ComponentUpdateContext) {}
 }
 
 pub mod registry;
@@ -61,6 +62,28 @@ pub struct ComponentResources {
     map: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
 }
 
+pub struct ComponentUpdateContext {
+    pub entity: Entity,
+    pub dt: f32,
+    pub resources: Arc<ComponentResources>,
+    world_ptr: *const World,
+}
+
+impl ComponentUpdateContext {
+    pub fn world(&self) -> &World {
+        unsafe { &*self.world_ptr }
+    }
+
+    pub fn new(entity: Entity, dt: f32, resources: Arc<ComponentResources>, world: &World) -> Self {
+        Self {
+            entity,
+            dt,
+            resources,
+            world_ptr: world as *const World,
+        }
+    }
+}
+
 impl ComponentResources {
     pub fn new() -> Self {
         Self {
@@ -93,13 +116,13 @@ pub struct ComponentInitContext {
 
 pub type ComponentInitFuture = Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn ComponentInsert>>> + Send + 'static>>;
 
-pub trait ComponentInsert: Send {
+pub trait ComponentInsert: Sync + Send {
     fn insert(self: Box<Self>, world: &mut World, entity: Entity) -> anyhow::Result<()>;
 }
 
 pub struct InsertBundle<T: DynamicBundle + Send + 'static>(pub T);
 
-impl<T: DynamicBundle + Send + 'static> ComponentInsert for InsertBundle<T> {
+impl<T: DynamicBundle + Send + 'static + Sync> ComponentInsert for InsertBundle<T> {
     fn insert(self: Box<Self>, world: &mut World, entity: Entity) -> anyhow::Result<()> {
         world.insert(entity, self.0).map_err(|e| anyhow::anyhow!(e.to_string()))?;
         Ok(())
diff --git a/crates/dropbear-traits/src/registry.rs b/crates/dropbear-traits/src/registry.rs
index 03e14b6..da8e150 100644
--- a/crates/dropbear-traits/src/registry.rs
+++ b/crates/dropbear-traits/src/registry.rs
@@ -1,9 +1,11 @@
 use std::any::TypeId;
 use std::collections::HashMap;
+use std::sync::Arc;
 
+use egui::Ui;
 use hecs::{Entity, World};
 
-use crate::SerializableComponent;
+use crate::{Component, ComponentResources, ComponentUpdateContext, SerializableComponent};
 
 pub struct ComponentRegistry {
     next_id: u32,
@@ -13,12 +15,15 @@ pub struct ComponentRegistry {
 }
 
 struct ComponentEntry {
+    #[allow(dead_code)]
     type_id: TypeId,
     fqtn: &'static str,
     display_name: String,
     create_default: Option<fn() -> Box<dyn SerializableComponent>>,
     extract_fn: Option<fn(&World, Entity) -> Option<Box<dyn SerializableComponent>>>,
     remove_fn: Option<fn(&mut World, Entity)>,
+    inspect_fn: Option<fn(&mut World, Entity, &mut Ui) -> bool>,
+    update_fn: Option<fn(&mut World, f32, &Arc<ComponentResources>)>,
 }
 
 struct ConverterEntry {
@@ -40,6 +45,48 @@ impl ComponentRegistry {
     where
         T: SerializableComponent + Default + Clone + 'static,
     {
+        self.register_entry::<T>(None, None);
+    }
+
+    pub fn register_with_default_component<T>(&mut self)
+    where
+        T: Component + SerializableComponent + Default + Clone + 'static,
+    {
+        let inspect_fn: fn(&mut World, Entity, &mut Ui) -> bool = |world, entity, ui| {
+            if let Ok(mut component) = world.get::<&mut T>(entity) {
+                component.inspect(ui);
+                true
+            } else {
+                false
+            }
+        };
+
+        let update_fn: fn(&mut World, f32, &Arc<ComponentResources>) =
+            |world, dt, resources| {
+                let world_ptr = world as *const World;
+                let mut query = world.query::<(Entity, &mut T)>();
+                for (entity, component) in query.iter() {
+                    let mut ctx = ComponentUpdateContext::new(
+                        entity,
+                        dt,
+                        resources.clone(),
+                        unsafe { &*world_ptr },
+                    );
+                    component.update(&mut ctx);
+                }
+            };
+
+        self.register_entry::<T>(Some(inspect_fn), Some(update_fn));
+    }
+
+    fn register_entry<T>(
+        &mut self,
+        inspect_fn: Option<fn(&mut World, Entity, &mut Ui) -> bool>,
+        update_fn: Option<fn(&mut World, f32, &Arc<ComponentResources>)>,
+    )
+    where
+        T: SerializableComponent + Default + Clone + 'static,
+    {
         let type_id = TypeId::of::<T>();
         if self.type_to_id.contains_key(&type_id) {
             return;
@@ -62,6 +109,8 @@ impl ComponentRegistry {
             remove_fn: Some(|world, entity| {
                 let _ = world.remove_one::<T>(entity);
             }),
+            inspect_fn,
+            update_fn,
         };
 
         self.entries.insert(id, entry);
@@ -137,6 +186,54 @@ impl ComponentRegistry {
         components
     }
 
+    pub fn inspect_components(&self, world: &mut World, entity: Entity, ui: &mut Ui) {
+        let mut ids: Vec<u32> = self.entries.keys().copied().collect();
+        ids.sort_unstable();
+
+        for id in ids {
+            let Some(entry) = self.entries.get(&id) else {
+                continue;
+            };
+
+            if let Some(inspect_fn) = entry.inspect_fn {
+                if inspect_fn(world, entity, ui) {
+                    ui.add_space(6.0);
+                }
+            }
+        }
+    }
+
+    pub fn update_components(
+        &self,
+        world: &mut World,
+        dt: f32,
+        resources: &Arc<ComponentResources>,
+    ) {
+        let mut ids: Vec<u32> = self.entries.keys().copied().collect();
+        ids.sort_unstable();
+
+        for id in ids {
+            let Some(entry) = self.entries.get(&id) else {
+                continue;
+            };
+
+            if let Some(update_fn) = entry.update_fn {
+                update_fn(world, dt, resources);
+            }
+        }
+    }
+    
+    pub fn find_components_by_numeric_id(
+        &self,
+        id: u64
+    ) -> Vec<(u32, &str)> {
+        self.entries
+            .iter()
+            .filter(|(_, entry)| entry.display_name.starts_with(&id.to_string()))
+            .map(|(id, entry)| (*id, entry.fqtn))
+            .collect()
+    }
+
     pub fn iter(&self) -> impl Iterator<Item = (u32, &str)> {
         self.iter_available_components()
     }
diff --git a/crates/eucalyptus-core/src/camera.rs b/crates/eucalyptus-core/src/camera.rs
index 9b9d48c..ddd081d 100644
--- a/crates/eucalyptus-core/src/camera.rs
+++ b/crates/eucalyptus-core/src/camera.rs
@@ -1,5 +1,5 @@
 //! Additional information and context for cameras from the [`dropbear_engine::camera`]
-use crate::states::Camera3D;
+use crate::states::SerializableCamera;
 use dropbear_engine::camera::{Camera, CameraBuilder, CameraSettings};
 use dropbear_traits::{ComponentInitContext, ComponentInitFuture, InsertBundle, SerializableComponent};
 use glam::DVec3;
@@ -56,8 +56,8 @@ impl SerializableComponent for CameraComponent {
     }
 }
 
-impl From<Camera3D> for CameraBuilder {
-    fn from(value: Camera3D) -> Self {
+impl From<SerializableCamera> for CameraBuilder {
+    fn from(value: SerializableCamera) -> Self {
         let forward = value.transform.rotation * DVec3::Z;
         let up = if matches!(value.camera_type, CameraType::Debug | CameraType::Normal) {
             DVec3::Y
@@ -81,8 +81,8 @@ impl From<Camera3D> for CameraBuilder {
     }
 }
 
-impl From<Camera3D> for CameraComponent {
-    fn from(value: Camera3D) -> Self {
+impl From<SerializableCamera> for CameraComponent {
+    fn from(value: SerializableCamera) -> Self {
         let settings = CameraSettings::new(
             value.speed as f64,
             value.sensitivity as f64,
diff --git a/crates/eucalyptus-core/src/lib.rs b/crates/eucalyptus-core/src/lib.rs
index 3fa5c48..3c17c63 100644
--- a/crates/eucalyptus-core/src/lib.rs
+++ b/crates/eucalyptus-core/src/lib.rs
@@ -35,7 +35,7 @@ use crate::camera::CameraComponent;
 use crate::physics::collider::ColliderGroup;
 use crate::physics::kcc::KCC;
 use crate::physics::rigidbody::RigidBody;
-use crate::states::{Camera3D, Light, Script, SerializedMeshRenderer};
+use crate::states::{SerializableCamera, Light, Script, SerializedMeshRenderer};
 
 /// The appdata directory for storing any information.
 ///
@@ -45,27 +45,20 @@ pub const APP_INFO: app_dirs2::AppInfo = app_dirs2::AppInfo {
     author: "tirbofish",
 };
 
-#[unsafe(no_mangle)]
-pub extern "C" fn get_rustc_version() -> *const u8 {
-    let meta = rustc_version_runtime::version_meta();
-    let meta_string = format!("{:?}", meta);
-    Box::leak(meta_string.into_boxed_str()).as_ptr()
-}
-
 /// Registers all available and potential serializers and deserializers of an entity.
 pub fn register_components(
     component_registry: &mut ComponentRegistry,
 ) {
-    component_registry.register_with_default::<EntityTransform>();
+    component_registry.register_with_default_component::<EntityTransform>();
     component_registry.register_with_default::<CustomProperties>();
     component_registry.register_with_default::<Light>();
     component_registry.register_with_default::<Script>();
     component_registry.register_with_default::<SerializedMeshRenderer>();
-    component_registry.register_with_default::<Camera3D>();
+    component_registry.register_with_default::<SerializableCamera>();
     component_registry.register_with_default::<RigidBody>();
     component_registry.register_with_default::<ColliderGroup>();
     component_registry.register_with_default::<KCC>();
-    component_registry.register_with_default::<AnimationComponent>();
+    component_registry.register_with_default_component::<AnimationComponent>();
 
     component_registry.register_converter::<MeshRenderer, SerializedMeshRenderer, _>(
         |_, _, renderer| {
@@ -73,7 +66,7 @@ pub fn register_components(
         },
     );
 
-    component_registry.register_converter::<CameraComponent, Camera3D, _>(
+    component_registry.register_converter::<CameraComponent, SerializableCamera, _>(
         |world, entity, component| {
             let Ok(camera) = world.get::<&Camera>(entity) else {
                 log::debug!(
@@ -83,7 +76,7 @@ pub fn register_components(
                 return None;
             };
 
-            Some(Camera3D::from_ecs_camera(&camera, component))
+            Some(SerializableCamera::from_ecs_camera(&camera, component))
         },
     );
 
diff --git a/crates/eucalyptus-core/src/scene.rs b/crates/eucalyptus-core/src/scene.rs
index 3aa7d99..7adaa2c 100644
--- a/crates/eucalyptus-core/src/scene.rs
+++ b/crates/eucalyptus-core/src/scene.rs
@@ -5,7 +5,7 @@ pub mod scripting;
 
 use crate::camera::CameraComponent;
 use crate::hierarchy::{Children, Parent, SceneHierarchy};
-use crate::states::{Camera3D, Label, Light, Script, SerializedMeshRenderer, WorldLoadingStatus, PROJECT};
+use crate::states::{SerializableCamera, Label, Light, Script, SerializedMeshRenderer, WorldLoadingStatus, PROJECT};
 use dropbear_engine::camera::Camera;
 use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform};
 use dropbear_engine::graphics::SharedGraphicsContext;
diff --git a/crates/eucalyptus-core/src/states.rs b/crates/eucalyptus-core/src/states.rs
index 4980327..697b9b0 100644
--- a/crates/eucalyptus-core/src/states.rs
+++ b/crates/eucalyptus-core/src/states.rs
@@ -23,7 +23,7 @@ use std::fmt::{Display, Formatter};
 use std::ops::{Deref, DerefMut};
 use std::path::PathBuf;
 use std::sync::Arc;
-use egui::Ui;
+use egui::{CollapsingHeader, TextEdit, Ui};
 use hecs::{Entity, World};
 use dropbear_traits::{Component, ComponentDescriptor};
 use crate::properties::Value;
@@ -156,8 +156,57 @@ pub struct Script {
     pub tags: Vec<String>,
 }
 
+impl Component for Script {
+    type Serialized = Self;
+
+    fn static_descriptor() -> ComponentDescriptor {
+        ComponentDescriptor {
+            fqtn: "eucalyptus_core::states::Script".to_string(),
+            type_name: "Script".to_string(),
+            category: Some("Logic".to_string()),
+            description: Some("A script component that can be attached to entities.".to_string()),
+        }
+    }
+
+    fn deserialize(serialized: &Self::Serialized) -> Self {
+        serialized.clone()
+    }
+
+    fn serialize(&self) -> Self::Serialized {
+        self.clone()
+    }
+
+    fn inspect(&mut self, ui: &mut Ui) {
+        ui.vertical(|ui| {
+            CollapsingHeader::new("Logic")
+                .default_open(true)
+                .show(ui, |ui| {
+                    let mut local_del: Option<usize> = None;
+                    for (i, tag) in self.tags.iter_mut().enumerate() {
+                        let current_width = ui.available_width();
+                        ui.horizontal(|ui| {
+                            ui.add_sized(
+                                [current_width * 70.0 / 100.0, 20.0],
+                                TextEdit::singleline(tag),
+                            );
+                            if ui.button("🗑️").clicked() {
+                                local_del = Some(i);
+                            }
+                        });
+                    }
+                    if let Some(i) = local_del {
+                        self.tags.remove(i);
+                    }
+                    if ui.button("➕ Add").clicked() {
+                        self.tags.push(String::new())
+                    }
+                });
+        });
+    }
+}
+
 #[derive(Debug, Serialize, Deserialize, Clone)]
-pub struct Camera3D {
+pub struct SerializableCamera {
     pub label: String,
     pub transform: Transform,
     pub camera_type: CameraType,
@@ -173,7 +222,7 @@ pub struct Camera3D {
     pub starting_camera: bool,
 }
 
-impl Default for Camera3D {
+impl Default for SerializableCamera {
     fn default() -> Self {
         let default = CameraComponent::new();
         Self {
@@ -191,7 +240,7 @@ impl Default for Camera3D {
     }
 }
 
-impl Camera3D {
+impl SerializableCamera {
     pub fn from_ecs_camera(camera: &Camera, component: &CameraComponent) -> Self {
         let position = glam::DVec3::from_array(camera.eye.to_array());
         let target = glam::DVec3::from_array(camera.target.to_array());
@@ -444,7 +493,7 @@ impl SerializableComponent for Script {
 }
 
 #[typetag::serde]
-impl SerializableComponent for Camera3D {
+impl SerializableComponent for SerializableCamera {
     fn as_any(&self) -> &dyn Any {
         self
     }
diff --git a/crates/eucalyptus-editor/src/camera.rs b/crates/eucalyptus-editor/src/camera.rs
index 8e53574..54d89e4 100644
--- a/crates/eucalyptus-editor/src/camera.rs
+++ b/crates/eucalyptus-editor/src/camera.rs
@@ -1,80 +1,79 @@
-use crate::editor::component::InspectableComponent;
 use crate::editor::{Signal, StaticallyKept, UndoableAction};
 use dropbear_engine::camera::Camera;
 use egui::{Ui};
 use eucalyptus_core::camera::{CameraComponent};
 use hecs::Entity;
 
-impl InspectableComponent for Camera {
-    fn inspect(
-        &mut self,
-        _entity: &mut Entity,
-        _cfg: &mut StaticallyKept,
-        ui: &mut Ui,
-        _undo_stack: &mut Vec<UndoableAction>,
-        _signal: &mut Signal,
-        _label: &mut String,
-    ) {
-        ui.vertical(|ui| {
-            ui.horizontal(|ui| {
-                ui.label("Position:");
-                ui.label(format!(
-                    "{:.2}, {:.2}, {:.2}",
-                    self.eye.x, self.eye.y, self.eye.z
-                ));
-                if ui.button("Reset").clicked() {
-                    self.eye = glam::DVec3::ZERO;
-                }
-            });
-
-            ui.horizontal(|ui| {
-                ui.label("Target:");
-                ui.label(format!(
-                    "{:.2}, {:.2}, {:.2}",
-                    self.target.x, self.target.y, self.target.z
-                ));
-                if ui.button("Reset").clicked() {
-                    self.target = glam::DVec3::ZERO;
-                }
-            });
-        });
-    }
-}
-
-impl InspectableComponent for CameraComponent {
-    fn inspect(
-        &mut self,
-        _entity: &mut Entity,
-        _cfg: &mut StaticallyKept,
-        ui: &mut Ui,
-        _undo_stack: &mut Vec<UndoableAction>,
-        _signal: &mut Signal,
-        _label: &mut String,
-    ) {
-        ui.vertical(|ui| {
-            ui.horizontal(|ui| {
-                ui.label("Speed:");
-                ui.add(
-                    egui::DragValue::new(&mut self.settings.speed)
-                        .speed(0.1),
-                );
-            });
-
-            ui.horizontal(|ui| {
-                ui.label("Sensitivity:");
-                ui.add(
-                    egui::DragValue::new(&mut self.settings.sensitivity)
-                        .speed(0.0001)
-                        .range(0.0001..=1.5),
-                );
-            });
-
-            ui.horizontal(|ui| {
-                ui.label("FOV:");
-                ui.add(
-                    egui::Slider::new(&mut self.settings.fov_y, 10.0..=120.0).suffix("°"),
-                );
-            });
-        });
-    }
-}
+// impl InspectableComponent for Camera {
+//     fn inspect(
+//         &mut self,
+//         _entity: &mut Entity,
+//         _cfg: &mut StaticallyKept,
+//         ui: &mut Ui,
+//         _undo_stack: &mut Vec<UndoableAction>,
+//         _signal: &mut Signal,
+//         _label: &mut String,
+//     ) {
+//         ui.vertical(|ui| {
+//             ui.horizontal(|ui| {
+//                 ui.label("Position:");
+//                 ui.label(format!(
+//                     "{:.2}, {:.2}, {:.2}",
+//                     self.eye.x, self.eye.y, self.eye.z
+//                 ));
+//                 if ui.button("Reset").clicked() {
+//                     self.eye = glam::DVec3::ZERO;
+//                 }
+//             });
+// 
+//             ui.horizontal(|ui| {
+//                 ui.label("Target:");
+//                 ui.label(format!(
+//                     "{:.2}, {:.2}, {:.2}",
+//                     self.target.x, self.target.y, self.target.z
+//                 ));
+//                 if ui.button("Reset").clicked() {
+//                     self.target = glam::DVec3::ZERO;
+//                 }
+//             });
+//         });
+//     }
+// }
+// 
+// impl InspectableComponent for CameraComponent {
+//     fn inspect(
+//         &mut self,
+//         _entity: &mut Entity,
+//         _cfg: &mut StaticallyKept,
+//         ui: &mut Ui,
+//         _undo_stack: &mut Vec<UndoableAction>,
+//         _signal: &mut Signal,
+//         _label: &mut String,
+//     ) {
+//         ui.vertical(|ui| {
+//             ui.horizontal(|ui| {
+//                 ui.label("Speed:");
+//                 ui.add(
+//                     egui::DragValue::new(&mut self.settings.speed)
+//                         .speed(0.1),
+//                 );
+//             });
+// 
+//             ui.horizontal(|ui| {
+//                 ui.label("Sensitivity:");
+//                 ui.add(
+//                     egui::DragValue::new(&mut self.settings.sensitivity)
+//                         .speed(0.0001)
+//                         .range(0.0001..=1.5),
+//                 );
+//             });
+// 
+//             ui.horizontal(|ui| {
+//                 ui.label("FOV:");
+//                 ui.add(
+//                     egui::Slider::new(&mut self.settings.fov_y, 10.0..=120.0).suffix("°"),
+//                 );
+//             });
+//         });
+//     }
+// }
diff --git a/crates/eucalyptus-editor/src/editor/asset_viewer.rs b/crates/eucalyptus-editor/src/editor/asset_viewer.rs
index c2cb0d1..5cb28d7 100644
--- a/crates/eucalyptus-editor/src/editor/asset_viewer.rs
+++ b/crates/eucalyptus-editor/src/editor/asset_viewer.rs
@@ -1,5 +1,16 @@
+use std::{cmp::Ordering, fs, hash::DefaultHasher, io, path::Path};
+use std::hash::{Hash, Hasher};
+use dropbear_engine::{graphics::NO_TEXTURE, utils::ResourceReference};
+use egui_ltreeview::{Action, NodeBuilder, TreeViewBuilder};
+use eucalyptus_core::states::PROJECT;
+use hecs::Entity;
+
+use crate::editor::{ComponentNodeSelection, DraggedAsset, EditorTabViewer, FsEntry, StaticallyKept, TABS_GLOBAL};
+
 impl<'a> EditorTabViewer<'a> {
-    fn show_asset_viewer(&mut self, cfg: &mut StaticallyKept, ui: &mut egui::Ui) {
+    pub(crate) fn show_asset_viewer(&mut self, ui: &mut egui::Ui) {
+        let mut cfg = TABS_GLOBAL.lock();
+
         let project_root = {
             let project = PROJECT.read();
             if project.project_path.as_os_str().is_empty() {
@@ -11,9 +22,9 @@ impl<'a> EditorTabViewer<'a> {
 
         let (_resp, action) = egui_ltreeview::TreeView::new(egui::Id::new("asset_viewer")).show(ui, |builder| {
             builder.node(Self::dir_node("euca://"));
-            Self::build_resource_branch(cfg, builder, &project_root);
-            Self::build_scripts_branch(cfg, builder, &project_root);
-            Self::build_scene_branch(cfg, builder, &project_root);
+            Self::build_resource_branch(&mut cfg, builder, &project_root);
+            Self::build_scripts_branch(&mut cfg, builder, &project_root);
+            Self::build_scene_branch(&mut cfg, builder, &project_root);
             builder.close_dir();
         });
 
@@ -479,13 +490,13 @@ impl<'a> EditorTabViewer<'a> {
         builder.node(Self::leaf_node_labeled(id_source, label));
     }
 
-    fn handle_tree_selection(&mut self, cfg: &mut StaticallyKept, items: &[u64]) {
+    pub(crate) fn handle_tree_selection(&mut self, cfg: &mut StaticallyKept, items: &[u64]) {
         for node_id in items {
             self.resolve_tree_node(cfg, *node_id);
         }
     }
 
-    fn handle_tree_activate(
+    pub(crate) fn handle_tree_activate(
         &mut self,
         cfg: &mut StaticallyKept,
         activate: &egui_ltreeview::Activate<u64>,
@@ -493,7 +504,7 @@ impl<'a> EditorTabViewer<'a> {
         self.handle_tree_selection(cfg, &activate.selected);
     }
 
-    fn handle_tree_drag(
+    pub(crate) fn handle_tree_drag(
         &mut self,
         cfg: &mut StaticallyKept,
         drag: &egui_ltreeview::DragAndDrop<u64>,
@@ -506,7 +517,7 @@ impl<'a> EditorTabViewer<'a> {
         }
     }
 
-    fn handle_tree_move(
+    pub(crate) fn handle_tree_move(
         &mut self,
         cfg: &mut StaticallyKept,
         drag: &egui_ltreeview::DragAndDrop<u64>,
@@ -552,7 +563,7 @@ impl<'a> EditorTabViewer<'a> {
         let component_id = selection.component_type_id;
         let matches = self
             .component_registry
-            .find_components_by_numeric_id(&*self.world, component_id);
+            .find_components_by_numeric_id(component_id);
 
         if matches.is_empty() {
             log::warn!("Component id #{} not found in world", component_id);
@@ -562,7 +573,7 @@ impl<'a> EditorTabViewer<'a> {
         for (entity, component) in matches {
             log::debug!(
                 "Serializable component '{}' (id #{}) attached to entity {:?}",
-                component.type_name(),
+                component,
                 component_id,
                 entity
             );
diff --git a/crates/eucalyptus-editor/src/editor/build_console.rs b/crates/eucalyptus-editor/src/editor/build_console.rs
index 5242498..5c97995 100644
--- a/crates/eucalyptus-editor/src/editor/build_console.rs
+++ b/crates/eucalyptus-editor/src/editor/build_console.rs
@@ -1,3 +1,9 @@
+use std::path::PathBuf;
+
+use egui::{Margin, RichText};
+
+use crate::editor::{EditorTabViewer, console_error::{ConsoleItem, ErrorLevel}};
+
 impl<'a> EditorTabViewer<'a> {
     pub fn build_console(&mut self, ui: &mut egui::Ui) {
         fn analyse_error(log: &Vec<String>) -> Vec<ConsoleItem> {
@@ -122,7 +128,7 @@ impl<'a> EditorTabViewer<'a> {
                                                 );
                                             }
                                             Err(e) => {
-                                                warn!(
+                                                eucalyptus_core::warn!(
                                                     "Failed to open '{}' in VS Code: {}",
                                                     location_arg, e
                                                 );
diff --git a/crates/eucalyptus-editor/src/editor/component.rs b/crates/eucalyptus-editor/src/editor/component.rs
index 0616ce2..52b8e0a 100644
--- a/crates/eucalyptus-editor/src/editor/component.rs
+++ b/crates/eucalyptus-editor/src/editor/component.rs
@@ -1,7 +1,7 @@
 //! This module should describe the different components that are editable in the resource inspector.
 
 use crate::editor::{Signal, StaticallyKept, UndoableAction};
-use dropbear_engine::asset::{AssetHandle, ASSET_REGISTRY};
+use dropbear_engine::asset::{ASSET_REGISTRY};
 use dropbear_engine::attenuation::ATTENUATION_PRESETS;
 use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform};
 use dropbear_engine::lighting::LightType;
@@ -16,7 +16,6 @@ use hecs::Entity;
 use std::time::Instant;
 use dropbear_engine::procedural::ProcObj;
 use eucalyptus_core::properties::{CustomProperties, Value};
-use eucalyptus_core::ui::UIComponent;
 
 /// A trait that can added to any component that allows you to inspect the value in the editor.
 pub trait InspectableComponent {
@@ -910,31 +909,7 @@ impl InspectableComponent for Script {
         _signal: &mut Signal,
         _label: &mut String,
     ) {
-        ui.vertical(|ui| {
-            CollapsingHeader::new("Logic")
-                .default_open(true)
-                .show(ui, |ui| {
-                    let mut local_del: Option<usize> = None;
-                    for (i, tag) in self.tags.iter_mut().enumerate() {
-                        let current_width = ui.available_width();
-                        ui.horizontal(|ui| {
-                            ui.add_sized(
-                                [current_width * 70.0 / 100.0, 20.0],
-                                TextEdit::singleline(tag),
-                            );
-                            if ui.button("🗑️").clicked() {
-                                local_del = Some(i);
-                            }
-                        });
-                    }
-                    if let Some(i) = local_del {
-                        self.tags.remove(i);
-                    }
-                    if ui.button("➕ Add").clicked() {
-                        self.tags.push(String::new())
-                    }
-                });
-        });
+
     }
 }
 
@@ -1549,67 +1524,4 @@ impl InspectableComponent for Camera3D {
         });
         ui.separator();
     }
-}
-
-impl InspectableComponent for UIComponent {
-    fn inspect(
-        &mut self,
-        _entity: &mut Entity,
-        cfg: &mut StaticallyKept,
-        ui: &mut Ui,
-        _undo_stack: &mut Vec<UndoableAction>,
-        _signal: &mut Signal,
-        _label: &mut String,
-    ) {
-        ui.vertical(|ui| {
-            ui.checkbox(&mut self.disabled, "Disable rendering of UI");
-
-            ui.separator();
-
-            let (rect, response) = ui.allocate_exact_size(
-                egui::vec2(ui.available_width(), 72.0),
-                egui::Sense::click(),
-            );
-
-            let fill = if response.hovered() {
-                ui.visuals().widgets.hovered.bg_fill
-            } else {
-                ui.visuals().widgets.inactive.bg_fill
-            };
-
-            ui.painter().rect_filled(rect, 4.0, fill);
-            ui.painter().rect_stroke(
-                rect,
-                4.0,
-                ui.visuals().widgets.inactive.bg_stroke,
-                egui::StrokeKind::Inside,
-            );
-
-            let mut card_ui = ui.new_child(
-                UiBuilder::new()
-                    .layout(egui::Layout::centered_and_justified(egui::Direction::LeftToRight))
-                    .max_rect(rect),
-            );
-
-            if let Some(uri) = self.ui_file.as_uri() {
-                card_ui.label(uri);
-            } else {
-                card_ui.label("Drop .kts file here");
-            }
-
-            let pointer_released = ui.input(|i| i.pointer.any_released());
-            if pointer_released && response.hovered() {
-                if let Some(asset) = cfg.dragged_asset.clone() {
-                    if let Some(uri) = asset.path.as_uri() {
-                        if uri.ends_with(".kts") {
-                            if let Ok(new_ref) = ResourceReference::from_euca_uri(uri) {
-                                self.ui_file = new_ref;
-                            }
-                            cfg.dragged_asset = None;
-                        }
-                    }
-                }
-            }
-        });
-    }
 }
\ No newline at end of file
diff --git a/crates/eucalyptus-editor/src/editor/dock.rs b/crates/eucalyptus-editor/src/editor/dock.rs
index 8d2ca87..cb10585 100644
--- a/crates/eucalyptus-editor/src/editor/dock.rs
+++ b/crates/eucalyptus-editor/src/editor/dock.rs
@@ -1,42 +1,24 @@
 use super::*;
-use crate::editor::{
-    console_error::{ConsoleItem, ErrorLevel},
-    ViewportMode,
-};
+use crate::editor::ViewportMode;
 use std::{
-    cmp::Ordering,
-    collections::{hash_map::DefaultHasher, HashMap},
-    fs,
-    hash::{Hash, Hasher},
-    io,
-    path::{Path, PathBuf},
+    collections::HashMap
+    ,
+    hash::Hash
+    ,
+    path::PathBuf,
     sync::LazyLock,
 };
 
-use crate::editor::component::InspectableComponent;
+use crate::editor::console::EucalyptusConsole;
 use crate::plugin::PluginRegistry;
-use dropbear_engine::graphics::NO_TEXTURE;
 use dropbear_engine::utils::ResourceReference;
-use dropbear_engine::{
-    entity::{EntityTransform, MeshRenderer, Transform},
-    lighting::LightComponent,
-};
-use egui::{self, CollapsingHeader, Margin, RichText};
+use dropbear_engine::entity::{EntityTransform, Transform};
+use egui::{self};
 use egui_dock::TabViewer;
-use egui_ltreeview::{Action, NodeBuilder, TreeViewBuilder};
-use eucalyptus_core::{hierarchy::{Children, Hierarchy, Parent}, ui::UIComponent};
-use eucalyptus_core::states::{Label, Light, Script, PROJECT};
 use eucalyptus_core::traits::registry::ComponentRegistry;
-use hecs::{Entity, EntityBuilder, World};
-use indexmap::Equivalent;
-use log;
+use hecs::{Entity, World};
 use parking_lot::Mutex;
-use transform_gizmo_egui::{EnumSet, Gizmo, GizmoConfig, GizmoExt, GizmoMode, GizmoOrientation};
-use eucalyptus_core::physics::collider::{Collider, ColliderGroup};
-use eucalyptus_core::physics::kcc::KCC;
-use eucalyptus_core::physics::rigidbody::RigidBody;
-use eucalyptus_core::properties::CustomProperties;
-use crate::editor::console::EucalyptusConsole;
+use transform_gizmo_egui::{EnumSet, Gizmo, GizmoMode, GizmoOrientation};
 
 pub struct EditorTabViewer<'a> {
     pub view: egui::TextureId,
@@ -115,8 +97,8 @@ pub struct StaticallyKept {
     show_context_menu: bool,
     context_menu_pos: egui::Pos2,
     context_menu_tab: Option<EditorTab>,
-    is_focused: bool,
-    old_pos: Transform,
+    pub(crate) is_focused: bool,
+    pub(crate) old_pos: Transform,
     pub(crate) scale_locked: bool,
 
     pub(crate) old_label_entity: Option<hecs::Entity>,
@@ -131,18 +113,18 @@ pub struct StaticallyKept {
     pub(crate) transform_rotation_cache: HashMap<Entity, glam::DVec3>,
 
     pub(crate) dragged_asset: Option<DraggedAsset>,
-    asset_node_assets: HashMap<u64, DraggedAsset>,
+    pub(crate) asset_node_assets: HashMap<u64, DraggedAsset>,
 
-    component_node_ids: HashMap<ComponentNodeKey, u64>,
-    component_node_lookup: HashMap<u64, ComponentNodeKey>,
-    next_component_node_id: u64,
+    pub(crate) component_node_ids: HashMap<ComponentNodeKey, u64>,
+    pub(crate) component_node_lookup: HashMap<u64, ComponentNodeKey>,
+    pub(crate) next_component_node_id: u64,
     pub(crate) last_component_lookup: Option<ComponentNodeSelection>,
     pub(crate) pending_component_drag: Option<ComponentNodeSelection>,
     pub(crate) root_node_selected: bool,
 }
 
 impl StaticallyKept {
-    fn next_component_node_id(&mut self) -> u64 {
+    pub(crate) fn next_component_node_id(&mut self) -> u64 {
         if self.next_component_node_id == 0 {
             self.next_component_node_id = 1;
         }
@@ -154,7 +136,7 @@ impl StaticallyKept {
         id
     }
 
-    fn component_node_id(&mut self, entity: Entity, component_type_id: u64) -> u64 {
+    pub(crate) fn component_node_id(&mut self, entity: Entity, component_type_id: u64) -> u64 {
         let key = ComponentNodeKey::new(entity, component_type_id);
         if let Some(id) = self.component_node_ids.get(&key) {
             *id
@@ -166,13 +148,13 @@ impl StaticallyKept {
         }
     }
 
-    fn component_selection(&self, node_id: u64) -> Option<ComponentNodeSelection> {
+    pub(crate) fn component_selection(&self, node_id: u64) -> Option<ComponentNodeSelection> {
         self.component_node_lookup
             .get(&node_id)
             .map(|key| key.as_selection(node_id))
     }
 
-    fn remember_component_lookup(&mut self, selection: ComponentNodeSelection) {
+    pub(crate) fn remember_component_lookup(&mut self, selection: ComponentNodeSelection) {
         self.last_component_lookup = Some(selection);
     }
 }
@@ -199,13 +181,12 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
     }
 
     fn ui(&mut self, ui: &mut egui::Ui, tab: &mut Self::Tab) {
-        let mut cfg = TABS_GLOBAL.lock();
-
         ui.ctx().input(|i| {
             if i.pointer.button_pressed(egui::PointerButton::Secondary)
                 && let Some(pos) = i.pointer.hover_pos()
                 && ui.available_rect_before_wrap().contains(pos)
             {
+                let mut cfg = TABS_GLOBAL.lock();
                 cfg.show_context_menu = true;
                 cfg.context_menu_pos = pos;
                 cfg.context_menu_tab = Some(tab.clone());
@@ -220,7 +201,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                 self.entity_list(ui);
             }
             EditorTab::AssetViewer => {
-                self.show_asset_viewer(&mut cfg, ui);
+                self.show_asset_viewer(ui);
             }
             EditorTab::ResourceInspector => {
                 self.resource_inspector(ui);
@@ -314,592 +295,12 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
     }
 }
 
-impl<'a> EditorTabViewer<'a> {
-    fn show_asset_viewer(&mut self, cfg: &mut StaticallyKept, ui: &mut egui::Ui) {
-        let project_root = {
-            let project = PROJECT.read();
-            if project.project_path.as_os_str().is_empty() {
-                ui.label("Open a project to browse assets.");
-                return;
-            }
-            project.project_path.clone()
-        };
-
-        let (_resp, action) = egui_ltreeview::TreeView::new(egui::Id::new("asset_viewer")).show(ui, |builder| {
-            builder.node(Self::dir_node("euca://"));
-            Self::build_resource_branch(cfg, builder, &project_root);
-            Self::build_scripts_branch(cfg, builder, &project_root);
-            Self::build_scene_branch(cfg, builder, &project_root);
-            builder.close_dir();
-        });
-
-        for a in action {
-            match a {
-                Action::SetSelected(selected) => {
-                    log_once::debug_once!("Selected: {:?}", selected);
-                }
-                Action::Move(moved) => {
-                    log_once::debug_once!("Moved: {:?}", moved);
-                }
-                Action::Drag(dragged) => {
-                    log_once::debug_once!("Dragged: {:?}", dragged);
-
-                    if let Some(&node_id) = dragged.source.first() {
-                        if let Some(asset) = cfg.asset_node_assets.get(&node_id).cloned() {
-                            cfg.dragged_asset = Some(asset);
-                        }
-                    }
-                }
-                Action::Activate(activated) => {
-                    log_once::debug_once!("Activated: {:?}", activated);
-                }
-                Action::DragExternal(_) => {}
-                Action::MoveExternal(_) => {}
-            }
-        }
-    }
-
-    fn build_resource_branch(
-        cfg: &mut StaticallyKept,
-        builder: &mut TreeViewBuilder<u64>,
-        project_root: &Path,
-    ) {
-        let label = "euca://resources";
-        builder.node(Self::dir_node_labeled(label, "resources"));
-        let resources_root = project_root.join("resources");
-        if resources_root.exists() {
-            Self::walk_resource_directory(cfg, builder, &resources_root, &resources_root);
-        } else {
-            Self::add_placeholder_leaf(builder, "euca://resources/missing", "missing");
-        }
-        builder.close_dir();
-    }
-
-    fn walk_resource_directory(
-        cfg: &mut StaticallyKept,
-        builder: &mut TreeViewBuilder<u64>,
-        base_path: &Path,
-        current_path: &Path,
-    ) {
-        let entries = match Self::sorted_entries(current_path) {
-            Ok(entries) => entries,
-            Err(err) => {
-                log_once::warn_once!(
-                    "Unable to enumerate resources at '{}': {}",
-                    current_path.parent().unwrap_or(current_path).display(),
-                    err
-                );
-                return;
-            }
-        };
-
-        for entry in entries {
-            let full_label = Self::resource_label(base_path, &entry.path);
-            if entry.is_dir {
-                builder.node(Self::dir_node_labeled(&full_label, &entry.name));
-                Self::walk_resource_directory(cfg, builder, base_path, &entry.path);
-                builder.close_dir();
-            } else {
-                if entry.name.eq_ignore_ascii_case("resources.eucc") {
-                    continue;
-                }
-
-                cfg.asset_node_assets.insert(
-                    Self::asset_node_id(&full_label),
-                    DraggedAsset {
-                        name: entry.name.clone(),
-                        path: ResourceReference::from_euca_uri(&full_label)
-                            .unwrap_or_else(|_| ResourceReference::default()),
-                    },
-                );
-                builder.node(Self::leaf_node_labeled(&full_label, &entry.name));
-            }
-        }
-    }
-
-    fn resource_label(base_path: &Path, path: &Path) -> String {
-        let relative = path
-            .strip_prefix(base_path)
-            .map(|rel| rel.to_string_lossy().replace('\\', "/"))
-            .unwrap_or_else(|_| path.to_string_lossy().replace('\\', "/"));
-        if relative.is_empty() {
-            "euca://resources".to_string()
-        } else {
-            format!("euca://resources/{}", relative)
-        }
-    }
-
-    fn build_scripts_branch(
-        cfg: &mut StaticallyKept,
-        builder: &mut TreeViewBuilder<u64>,
-        project_root: &Path,
-    ) {
-        let label = "euca://scripts";
-        builder.node(Self::dir_node_labeled(label, "scripts"));
-        let scripts_root = project_root.join("src");
-        if !scripts_root.exists() {
-            Self::walk_resource_directory(cfg, builder, &scripts_root, &scripts_root);
-            builder.close_dir();
-            return;
-        }
-
-        let entries = match Self::sorted_entries(&scripts_root) {
-            Ok(entries) => entries,
-            Err(err) => {
-                log::warn!(
-                    "Unable to enumerate scripts at '{}': {}",
-                    scripts_root.display(),
-                    err
-                );
-                builder.close_dir();
-                return;
-            }
-        };
-
-        let mut had_content = false;
-        for entry in entries {
-            if entry.is_dir {
-                let source_label = format!("{}/{}", label, entry.name);
-                builder.node(Self::dir_node_labeled(&source_label, &entry.name));
-                if Self::build_script_source_set(builder, &entry.path, &source_label) {
-                    had_content = true;
-                }
-                builder.close_dir();
-            } else if !entry.name.eq_ignore_ascii_case("source.eucc") {
-                let file_label = format!("{}/{}", label, entry.name);
-                builder.node(Self::leaf_node_labeled(&file_label, &entry.name));
-                had_content = true;
-            }
-        }
-
-        if !had_content {
-            Self::add_placeholder_leaf(builder, "euca://scripts/empty", "empty");
-        }
-
-        builder.close_dir();
-    }
-
-    fn build_script_source_set(
-        builder: &mut TreeViewBuilder<u64>,
-        source_path: &Path,
-        source_label: &str,
-    ) -> bool {
-        let entries = match Self::sorted_entries(source_path) {
-            Ok(entries) => entries,
-            Err(err) => {
-                log::warn!(
-                    "Unable to enumerate source set at '{}': {}",
-                    source_path.display(),
-                    err
-                );
-                Self::add_placeholder_leaf(
-                    builder,
-                    &format!("{source_label}/unreadable"),
-                    "unreadable",
-                );
-                return true;
-            }
-        };
-
-        let mut had_content = false;
-        for entry in entries {
-            if entry.is_dir {
-                if entry.name.eq_ignore_ascii_case("kotlin") {
-                    if Self::build_kotlin_tree(builder, &entry.path, source_label) {
-                        had_content = true;
-                    }
-                } else {
-                    let child_label = format!("{}/{}", source_label, entry.name);
-                    builder.node(Self::dir_node_labeled(&child_label, &entry.name));
-                    Self::build_plain_directory(builder, &entry.path, &child_label);
-                    builder.close_dir();
-                    had_content = true;
-                }
-            } else if !entry.name.eq_ignore_ascii_case("source.eucc") {
-                let file_label = format!("{}/{}", source_label, entry.name);
-                builder.node(Self::leaf_node_labeled(&file_label, &entry.name));
-                had_content = true;
-            }
-        }
-
-        if !had_content {
-            Self::add_placeholder_leaf(builder, &format!("{source_label}/empty"), "empty");
-            had_content = true;
-        }
-
-        had_content
-    }
-
-    fn build_plain_directory(
-        builder: &mut TreeViewBuilder<u64>,
-        dir_path: &Path,
-        parent_label: &str,
-    ) {
-        let entries = match Self::sorted_entries(dir_path) {
-            Ok(entries) => entries,
-            Err(err) => {
-                log::warn!(
-                    "Unable to enumerate directory '{}': {}",
-                    dir_path.display(),
-                    err
-                );
-                Self::add_placeholder_leaf(
-                    builder,
-                    &format!("{parent_label}/unreadable"),
-                    "unreadable",
-                );
-                return;
-            }
-        };
-
-        if entries.is_empty() {
-            Self::add_placeholder_leaf(builder, &format!("{parent_label}/empty"), "empty");
-            return;
-        }
-
-        for entry in entries {
-            let child_label = format!("{}/{}", parent_label, entry.name);
-            if entry.is_dir {
-                builder.node(Self::dir_node_labeled(&child_label, &entry.name));
-                Self::build_plain_directory(builder, &entry.path, &child_label);
-                builder.close_dir();
-            } else {
-                builder.node(Self::leaf_node_labeled(&child_label, &entry.name));
-            }
-        }
-    }
-
-    fn build_kotlin_tree(
-        builder: &mut TreeViewBuilder<u64>,
-        kotlin_path: &Path,
-        source_label: &str,
-    ) -> bool {
-        let entries = match Self::sorted_entries(kotlin_path) {
-            Ok(entries) => entries,
-            Err(err) => {
-                log::warn!(
-                    "Unable to enumerate kotlin sources at '{}': {}",
-                    kotlin_path.display(),
-                    err
-                );
-                Self::add_placeholder_leaf(
-                    builder,
-                    &format!("{source_label}/unreadable"),
-                    "unreadable",
-                );
-                return true;
-            }
-        };
-
-        if entries.is_empty() {
-            Self::add_placeholder_leaf(
-                builder,
-                &format!("{source_label}/no_kotlin_files"),
-                "no kotlin files",
-            );
-            return true;
-        }
-
-        let mut had_entries = false;
-        for entry in entries {
-            if entry.is_dir {
-                Self::build_kotlin_package_collapsed(
-                    builder,
-                    &entry.path,
-                    source_label,
-                    vec![entry.name.clone()],
-                );
-                had_entries = true;
-            } else {
-                let file_id = format!("{}/{}", source_label, entry.name);
-                builder.node(Self::leaf_node_labeled(&file_id, &entry.name));
-                had_entries = true;
-            }
-        }
-
-        had_entries
-    }
-
-    fn build_kotlin_package_collapsed(
-        builder: &mut TreeViewBuilder<u64>,
-        dir_path: &Path,
-        parent_path_str: &str,
-        accumulated_parts: Vec<String>,
-    ) {
-        let entries = match Self::sorted_entries(dir_path) {
-            Ok(entries) => entries,
-            Err(err) => {
-                let package_suffix = accumulated_parts.join(".");
-                let full_path_str = format!("{}/{}", parent_path_str, package_suffix);
-                log::warn!(
-                    "Unable to enumerate package '{}' ({}): {}",
-                    package_suffix,
-                    dir_path.display(),
-                    err
-                );
-                Self::add_placeholder_leaf(
-                    builder,
-                    &format!("{full_path_str}/unreadable"),
-                    "unreadable",
-                );
-                return;
-            }
-        };
-
-        let subdirs: Vec<&FsEntry> = entries.iter().filter(|e| e.is_dir).collect();
-        let files: Vec<&FsEntry> = entries.iter().filter(|e| !e.is_dir).collect();
-
-        if files.is_empty() && subdirs.len() == 1 {
-            let subdir = subdirs[0];
-            let mut new_parts = accumulated_parts;
-            new_parts.push(subdir.name.clone());
-            Self::build_kotlin_package_collapsed(builder, &subdir.path, parent_path_str, new_parts);
-        } else {
-            let package_suffix = accumulated_parts.join(".");
-            let full_path_str = format!("{}/{}", parent_path_str, package_suffix);
-
-            builder.node(Self::dir_node_labeled(&full_path_str, &package_suffix));
-
-            for file in files {
-                let file_id = format!("{}/{}", full_path_str, file.name);
-                builder.node(Self::leaf_node_labeled(&file_id, &file.name));
-            }
-
-            for subdir in subdirs {
-                Self::build_kotlin_package_collapsed(
-                    builder,
-                    &subdir.path,
-                    &full_path_str,
-                    vec![subdir.name.clone()],
-                );
-            }
-
-            builder.close_dir();
-        }
-    }
-
-    fn build_scene_branch(
-        _cfg: &mut StaticallyKept,
-        builder: &mut TreeViewBuilder<u64>,
-        project_root: &Path,
-    ) {
-        let label = "euca://scenes";
-        builder.node(Self::dir_node_labeled(label, "scenes"));
-        let scenes_root = project_root.join("scenes");
-        if !scenes_root.exists() {
-            Self::add_placeholder_leaf(builder, "euca://scenes/missing", "missing");
-            builder.close_dir();
-            return;
-        }
-
-        let entries = match Self::sorted_entries(&scenes_root) {
-            Ok(entries) => entries,
-            Err(err) => {
-                log::warn!(
-                    "Unable to enumerate scenes at '{}': {}",
-                    scenes_root.display(),
-                    err
-                );
-                Self::add_placeholder_leaf(builder, "euca://scenes/unreadable", "unreadable");
-                builder.close_dir();
-                return;
-            }
-        };
-
-        let mut had_entries = false;
-        for entry in entries {
-            if entry.is_dir {
-                let child_label = format!("{}/{}", label, entry.name);
-                builder.node(Self::dir_node_labeled(&child_label, &entry.name));
-                Self::build_plain_directory(builder, &entry.path, &child_label);
-                builder.close_dir();
-                had_entries = true;
-            } else if entry
-                .path
-                .extension()
-                .and_then(|ext| ext.to_str())
-                .map(|ext| ext.eq_ignore_ascii_case("eucs"))
-                .unwrap_or(false)
-            {
-                let file_label = format!("{}/{}", label, entry.name);
-                builder.node(Self::leaf_node_labeled(&file_label, &entry.name));
-                had_entries = true;
-            }
-        }
-
-        if !had_entries {
-            Self::add_placeholder_leaf(builder, "euca://scenes/no_scenes", "no scenes");
-        }
-
-        builder.close_dir();
-    }
-
-    fn sorted_entries(path: &Path) -> io::Result<Vec<FsEntry>> {
-        let mut entries = Vec::new();
-        for entry in fs::read_dir(path)? {
-            let entry = entry?;
-            let file_type = entry.file_type()?;
-            let name = entry.file_name().to_string_lossy().to_string();
-            entries.push(FsEntry {
-                path: entry.path(),
-                name_lower: name.to_lowercase(),
-                name,
-                is_dir: file_type.is_dir(),
-            });
-        }
-
-        entries.sort_by(|a, b| match b.is_dir.cmp(&a.is_dir) {
-            Ordering::Equal => a.name_lower.cmp(&b.name_lower),
-            other => other,
-        });
-
-        Ok(entries)
-    }
-
-    fn asset_node_id(label: &str) -> u64 {
-        let mut hasher = DefaultHasher::new();
-        label.hash(&mut hasher);
-        let mut id = hasher.finish();
-        if id == 0 {
-            id = 1;
-        }
-        id
-    }
-
-    fn dir_node<'ui>(label: &str) -> NodeBuilder<'ui, u64> {
-        Self::with_icon(NodeBuilder::dir(Self::asset_node_id(label)).label(label.to_string()))
-    }
-
-    fn dir_node_labeled<'ui>(id_source: &str, label: &str) -> NodeBuilder<'ui, u64> {
-        Self::with_icon(NodeBuilder::dir(Self::asset_node_id(id_source)).label(label.to_string()))
-    }
-
-    fn leaf_node_labeled<'ui>(id_source: &str, label: &str) -> NodeBuilder<'ui, u64> {
-        Self::with_icon(NodeBuilder::leaf(Self::asset_node_id(id_source)).label(label.to_string()))
-    }
-
-    fn with_icon<'ui>(builder: NodeBuilder<'ui, u64>) -> NodeBuilder<'ui, u64> {
-        builder.icon(|ui| {
-            egui_extras::install_image_loaders(ui.ctx());
-            Self::draw_asset_icon(ui)
-        })
-    }
-
-    fn draw_asset_icon(ui: &mut egui::Ui) {
-        let image = egui::Image::from_bytes("bytes://asset-viewer-icon", NO_TEXTURE)
-            .max_size(egui::vec2(14.0, 14.0));
-        ui.add(image);
-    }
-
-    fn add_placeholder_leaf(builder: &mut TreeViewBuilder<u64>, id_source: &str, label: &str) {
-        builder.node(Self::leaf_node_labeled(id_source, label));
-    }
-
-    fn handle_tree_selection(&mut self, cfg: &mut StaticallyKept, items: &[u64]) {
-        for node_id in items {
-            self.resolve_tree_node(cfg, *node_id);
-        }
-    }
-
-    fn handle_tree_activate(
-        &mut self,
-        cfg: &mut StaticallyKept,
-        activate: &egui_ltreeview::Activate<u64>,
-    ) {
-        self.handle_tree_selection(cfg, &activate.selected);
-    }
-
-    fn handle_tree_drag(
-        &mut self,
-        cfg: &mut StaticallyKept,
-        drag: &egui_ltreeview::DragAndDrop<u64>,
-    ) {
-        if let Some(&node_id) = drag.source.first() {
-            if let Some(selection) = cfg.component_selection(node_id) {
-                cfg.pending_component_drag = Some(selection);
-                self.inspect_component_selection(cfg, selection);
-            }
-        }
-    }
-
-    fn handle_tree_move(
-        &mut self,
-        cfg: &mut StaticallyKept,
-        drag: &egui_ltreeview::DragAndDrop<u64>,
-    ) {
-        let selection = cfg.pending_component_drag.take().or_else(|| {
-            drag.source
-                .first()
-                .and_then(|node_id| cfg.component_selection(*node_id))
-        });
-
-        if let Some(selection) = selection {
-            self.inspect_component_selection(cfg, selection);
-            if let Some(target_entity) = Self::entity_from_node_id(drag.target) {
-                log::info!(
-                    "Component id #{} ready to drop onto entity {:?}",
-                    selection.component_type_id,
-                    target_entity
-                );
-            }
-        }
-    }
-
-    fn resolve_tree_node(&mut self, cfg: &mut StaticallyKept, node_id: u64) {
-        if node_id == u64::MAX {
-            log_once::debug_once!("Root node has been selected");
-            cfg.root_node_selected = true;
-            *self.selected_entity = None;
-        } else if let Some(selection) = cfg.component_selection(node_id) {
-            cfg.root_node_selected = false;
-            self.inspect_component_selection(cfg, selection);
-        } else if let Some(entity) = Self::entity_from_node_id(node_id) {
-            cfg.root_node_selected = false;
-            *self.selected_entity = Some(entity);
-        }
-    }
-
-    fn inspect_component_selection(
-        &mut self,
-        cfg: &mut StaticallyKept,
-        selection: ComponentNodeSelection,
-    ) {
-        cfg.remember_component_lookup(selection);
-        let component_id = selection.component_type_id;
-        let matches = self
-            .component_registry
-            .find_components_by_numeric_id(&*self.world, component_id);
-
-        if matches.is_empty() {
-            log::warn!("Component id #{} not found in world", component_id);
-            return;
-        }
-
-        for (entity, component) in matches {
-            log::debug!(
-                "Serializable component '{}' (id #{}) attached to entity {:?}",
-                component.type_name(),
-                component_id,
-                entity
-            );
-        }
-    }
-
-    fn entity_from_node_id(node_id: u64) -> Option<Entity> {
-        if node_id == u64::MAX {
-            None
-        } else {
-            Entity::from_bits(node_id)
-        }
-    }
-}
-
 #[derive(Clone)]
-struct FsEntry {
-    path: PathBuf,
-    name: String,
-    name_lower: String,
-    is_dir: bool,
+pub(crate) struct FsEntry {
+    pub(crate) path: PathBuf,
+    pub(crate) name: String,
+    pub(crate) name_lower: String,
+    pub(crate) is_dir: bool,
 }
 
 #[derive(Debug, Clone, Copy)]
diff --git a/crates/eucalyptus-editor/src/editor/entity_list.rs b/crates/eucalyptus-editor/src/editor/entity_list.rs
index 88d5552..eaf3bfb 100644
--- a/crates/eucalyptus-editor/src/editor/entity_list.rs
+++ b/crates/eucalyptus-editor/src/editor/entity_list.rs
@@ -1,8 +1,12 @@
-use crate::editor::{EditorTabViewer, TABS_GLOBAL};
+use egui_ltreeview::{NodeBuilder, TreeViewBuilder};
+use eucalyptus_core::{hierarchy::{Children, Hierarchy, Parent}, physics::{collider::ColliderGroup, rigidbody::RigidBody}, states::{Label, PROJECT}, traits::registry::ComponentRegistry};
+use hecs::{Entity, World};
+
+use crate::editor::{EditorTabViewer, Signal, StaticallyKept, TABS_GLOBAL};
 
 impl<'a> EditorTabViewer<'a> {
     pub(crate) fn entity_list(&mut self, ui: &mut egui::Ui) {
-        let cfg = TABS_GLOBAL.lock();
+        let mut cfg = TABS_GLOBAL.lock();
 
         let (_response, action) = egui_ltreeview::TreeView::new(egui::Id::new(
             "model_entity_list",
@@ -59,21 +63,10 @@ impl<'a> EditorTabViewer<'a> {
                                 });
                                 ui.menu_button("Add", |ui| {
                                     log_once::debug_once!("Available components: ");
-                                    for (id, name) in registry.iter_available_components() {
-                                        log_once::debug_once!("id: {}, name: {}", id, name);
-                                        // if name.contains("EntityTransform") {
-                                        //     continue;
-                                        // }
-                                        let short_name =
-                                            name.split("::").last().unwrap_or(name);
-                                        let display_name =
-                                            if short_name == "SerializedMeshRenderer" {
-                                                "MeshRenderer"
-                                            } else {
-                                                short_name
-                                            };
-
-                                        if ui.button(display_name).clicked() {
+                                    for (id, fqtn) in registry.iter_available_components() {
+                                        log_once::debug_once!("id: {}, name: {}", id, fqtn);
+
+                                        if ui.button(fqtn).clicked() {
                                             if let Some(component) = registry.create_default_component(id) {
                                                 *signal = Signal::AddComponent(entity, component);
                                             }
@@ -100,7 +93,7 @@ impl<'a> EditorTabViewer<'a> {
                             continue;
                         };
                         let component_node_id =
-                            cfg.component_node_id(entity, component_type_id);
+                            cfg.component_node_id(entity, component_type_id as u64);
                         let display =
                             format!("{} (id #{component_type_id})", component.display_name());
 
diff --git a/crates/eucalyptus-editor/src/editor/mod.rs b/crates/eucalyptus-editor/src/editor/mod.rs
index 67d3075..96b5856 100644
--- a/crates/eucalyptus-editor/src/editor/mod.rs
+++ b/crates/eucalyptus-editor/src/editor/mod.rs
@@ -1,6 +1,6 @@
 pub mod asset_viewer;
 pub mod build_console;
-pub mod component;
+// pub mod component;
 pub mod console_error;
 pub mod console;
 pub mod dock;
@@ -23,7 +23,7 @@ use dropbear_engine::entity::EntityTransform;
 use dropbear_engine::graphics::InstanceRaw;
 use dropbear_engine::pipelines::light_cube::LightCubePipeline;
 use dropbear_engine::texture::{Texture, TextureWrapMode};
-use dropbear_engine::{camera::Camera, entity::{MeshRenderer, Transform}, future::FutureHandle, graphics::{SharedGraphicsContext}, model::{ModelId, MODEL_CACHE}, scene::SceneCommand, DropbearWindowBuilder, WindowData};
+use dropbear_engine::{camera::Camera, entity::{MeshRenderer, Transform}, future::FutureHandle, graphics::{SharedGraphicsContext}, scene::SceneCommand, DropbearWindowBuilder, WindowData};
 use egui::{self, Context};
 use egui_dock::{DockArea, DockState, NodeIndex, Style};
 use eucalyptus_core::{register_components, APP_INFO};
@@ -83,7 +83,7 @@ pub struct Editor {
     pub dock_state: DockState<EditorTab>,
     pub texture_id: Option<egui::TextureId>,
     pub size: Extent3d,
-    pub instance_buffer_cache: HashMap<ModelId, ResizableBuffer<InstanceRaw>>,
+    pub instance_buffer_cache: HashMap<u64, ResizableBuffer<InstanceRaw>>,
     pub collider_wireframe_geometry_cache: HashMap<ColliderShapeKey, WireframeGeometry>,
     pub collider_instance_buffer: Option<ResizableBuffer<ColliderInstanceRaw>>,
     pub color: Color,
@@ -95,6 +95,8 @@ pub struct Editor {
     pub collider_wireframe_pipeline: Option<ColliderWireframePipeline>,
     pub mipmapper: Option<MipMapper>,
     pub sky_pipeline: Option<SkyPipeline>,
+    pub(crate) default_skinning_buffer: Option<wgpu::Buffer>,
+    pub(crate) default_skinning_bind_group: Option<wgpu::BindGroup>,
 
     pub active_camera: Arc<Mutex<Option<Entity>>>,
 
@@ -124,7 +126,6 @@ pub struct Editor {
     // might as well save some memory if its not required...
     // #[allow(unused)] // unused to allow for JVM to startup
     // pub(crate) script_manager: ScriptManager,
-    pub play_mode_backup: Option<PlayModeBackup>,
 
     /// State of the input
     pub(crate) input_state: Box<InputState>,
@@ -242,7 +243,6 @@ impl Editor {
             gizmo_mode: EnumSet::empty(),
             gizmo_orientation: GizmoOrientation::Global,
             console: EucalyptusConsole::new(None),
-            play_mode_backup: None,
             input_state: Box::new(InputState::new()),
             light_cube_pipeline: None,
             active_camera: Arc::new(Mutex::new(None)),
@@ -279,6 +279,8 @@ impl Editor {
             collider_instance_buffer: None,
             mipmapper: None,
             sky_pipeline: None,
+            default_skinning_buffer: None,
+            default_skinning_bind_group: None,
         })
     }
 
@@ -658,11 +660,6 @@ impl Editor {
         self.shader_globals = None;
         self.texture_id = None;
         self.light_cube_pipeline = None;
-
-        {
-            let mut cache = MODEL_CACHE.lock();
-            cache.clear();
-        }
     }
 
     fn start_async_scene_load(&mut self, mut scene: SceneConfig, graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {
@@ -1183,119 +1180,6 @@ impl Editor {
         stats.show_window = open_flag;
     }
 
-    /// Restores transform components back to its original state before PlayMode.
-    pub fn restore(&mut self) -> anyhow::Result<()> {
-        if let Some(window) = &self.window {
-            let _ = window.set_cursor_grab(CursorGrabMode::None);
-        }
-
-        if let Some(backup) = &self.play_mode_backup {
-            for (
-                entity_id,
-                original_mesh_renderer,
-                original_transform,
-                original_properties,
-                original_script,
-            ) in &backup.entities
-            {
-                if let Ok(mut mesh_renderer) = self.world.get::<&mut MeshRenderer>(*entity_id) {
-                    mesh_renderer.clone_from(original_mesh_renderer);
-                    mesh_renderer.sync_asset_registry();
-                }
-
-                if let Ok(mut transform) = self.world.get::<&mut EntityTransform>(*entity_id) {
-                    *transform = *original_transform;
-                }
-
-                if let Ok(mut properties) = self.world.get::<&mut CustomProperties>(*entity_id) {
-                    properties.clone_from(original_properties);
-                }
-
-                let has_script = self.world.get::<&Script>(*entity_id).is_ok();
-                match (has_script, original_script) {
-                    (true, Some(original)) => {
-                        if let Ok(mut script) = self.world.get::<&mut Script>(*entity_id) {
-                            *script = original.clone();
-                        }
-                    }
-                    (true, None) => {
-                        let _ = self.world.remove_one::<Script>(*entity_id);
-                    }
-                    (false, Some(original)) => {
-                        let _ = self.world.insert_one(*entity_id, original.clone());
-                    }
-                    (false, None) => {}
-                }
-            }
-
-            for (entity_id, original_camera, original_camera_component) in &backup.camera_data {
-                if let Ok(mut camera) = self.world.get::<&mut Camera>(*entity_id) {
-                    *camera = original_camera.clone();
-                }
-
-                if let Ok(mut camera_component) = self.world.get::<&mut CameraComponent>(*entity_id)
-                {
-                    *camera_component = original_camera_component.clone();
-                }
-            }
-
-            log::info!("Restored scene from play mode backup");
-
-            self.play_mode_backup = None;
-            Ok(())
-        } else {
-            Err(anyhow::anyhow!("No play mode backup found to restore"))
-        }
-    }
-
-    pub fn create_backup(&mut self) -> anyhow::Result<()> {
-        let mut entities = Vec::new();
-
-        for (entity_id, mesh_renderer, transform, properties) in self
-            .world
-            .query::<(Entity, &MeshRenderer, &EntityTransform, &CustomProperties)>()
-            .iter()
-        {
-            let script = self
-                .world
-                .query_one::<&Script>(entity_id)
-                .get()
-                .ok()
-                .cloned();
-            entities.push((
-                entity_id,
-                mesh_renderer.clone(),
-                *transform,
-                properties.clone(),
-                script,
-            ));
-        }
-
-        let mut camera_data = Vec::new();
-
-        for (entity_id, camera, component) in
-            self.world.query::<(Entity, &Camera, &CameraComponent)>().iter()
-        {
-            camera_data.push((entity_id, camera.clone(), component.clone()));
-        }
-
-        let backup = PlayModeBackup {
-            entities,
-            camera_data,
-        };
-
-        let entity_count = backup.entities.len();
-        let camera_count = backup.camera_data.len();
-        self.play_mode_backup = Some(backup);
-
-        log::info!(
-            "Created play mode backup with {} entities and {} cameras",
-            entity_count,
-            camera_count
-        );
-        Ok(())
-    }
-
     pub fn switch_to_debug_camera(&mut self) {
         let debug_camera = self
             .world
@@ -1538,53 +1422,9 @@ pub enum Signal {
     LogEntities,
     /// Adds a new component instance using the async init pipeline.
     AddComponent(hecs::Entity, Box<dyn SerializableComponent>),
-
-    /// Loads a model from a URI/path and swaps it onto an existing MeshRenderer (or adds one if missing).
-    ReplaceModel(hecs::Entity, String),
-
-    /// Clears the currently selected model (sets MeshRenderer to an unassigned placeholder).
-    ClearModel(hecs::Entity),
-
-    /// Legacy model load signal used by entity spawning flows.
-    LoadModel(hecs::Entity, String),
-
-    /// Switches the entity's MeshRenderer to a procedural cuboid.
-    SetProceduralCuboid(hecs::Entity, [f32; 3]),
-    /// Updates the extents for an existing procedural cuboid renderer.
-    UpdateProceduralCuboid(hecs::Entity, [f32; 3]),
-
-    /// Applies a diffuse texture to a material by loading from a URI/path.
-    SetMaterialTexture(hecs::Entity, String, String, TextureWrapMode),
-
-    /// Changes the sampler wrap mode for a material.
-    SetMaterialWrapMode(hecs::Entity, String, TextureWrapMode),
-
-    /// Sets UV tiling (repeat counts) for a material.
-    SetMaterialUvTiling(hecs::Entity, String, [f32; 2]),
-    /// Removes the current material texture (replaces with a neutral fallback).
-    ClearMaterialTexture(hecs::Entity, String),
-    /// Sets a material tint colour (RGBA, unmultiplied).
-    SetMaterialTint(hecs::Entity, String, [f32; 4]),
-
-    /// Sets (bakes) the import scale for an entity's MeshRenderer.
-    ///
-    /// This updates the MeshRenderer's baked import scale (saved into the scene and used at runtime).
-    SetModelImportScale(hecs::Entity, f32),
     RequestNewWindow(WindowData),
 }
 
-#[derive(Clone)]
-pub struct PlayModeBackup {
-    entities: Vec<(
-        Entity,
-        MeshRenderer,
-        EntityTransform,
-        CustomProperties,
-        Option<Script>,
-    )>,
-    camera_data: Vec<(Entity, Camera, CameraComponent)>,
-}
-
 #[derive(Debug)]
 pub enum EditorState {
     Editing,
diff --git a/crates/eucalyptus-editor/src/editor/resource.rs b/crates/eucalyptus-editor/src/editor/resource.rs
index a15625d..d55ff10 100644
--- a/crates/eucalyptus-editor/src/editor/resource.rs
+++ b/crates/eucalyptus-editor/src/editor/resource.rs
@@ -1,303 +1,20 @@
-use egui::CollapsingHeader;
-use hecs::Entity;
-use indexmap::Equivalent;
-use dropbear_engine::camera::Camera;
-use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform};
-use dropbear_engine::lighting::LightComponent;
-use eucalyptus_core::camera::{CameraComponent, CameraType};
-use eucalyptus_core::properties::CustomProperties;
-use eucalyptus_core::states::{Label, Light, Script};
-use eucalyptus_core::{success, warn};
-use eucalyptus_core::physics::collider::{Collider, ColliderGroup};
-use eucalyptus_core::physics::kcc::KCC;
-use eucalyptus_core::physics::rigidbody::RigidBody;
-use crate::editor::{EditorTabViewer, UndoableAction, TABS_GLOBAL};
+use crate::editor::{EditorTabViewer, TABS_GLOBAL};
 
 impl<'a> EditorTabViewer<'a> {
     pub(crate) fn resource_inspector(&mut self, ui: &mut egui::Ui) {
         let mut cfg = TABS_GLOBAL.lock();
 
         let local_scene_settings = cfg.root_node_selected;
-        let mut local_add_collider: Option<Entity> = None;
 
         if let Some(entity) = self.selected_entity {
-            let mut local_set_initial_camera = false;
-            let mut inspect_entity = *entity;
-            let world = &self.world;
+            let inspect_entity = *entity;
 
             if !cfg.root_node_selected {
-                if let Ok((label, )) = world.query_one::<(&mut Label,)>(inspect_entity).get() {
-                    label.inspect(
-                        &mut inspect_entity,
-                        &mut cfg,
-                        ui,
-                        self.undo_stack,
-                        self.signal,
-                        &mut String::new(),
-                    );
+                ui.label(format!("Entity ID: {}", inspect_entity.id()));
+                ui.separator();
 
-                    ui.label(format!("Entity ID: {}", inspect_entity.id()));
-
-                    ui.separator();
-
-                    // mesh renderer
-                    if let Ok(e) = world.query_one::<&mut MeshRenderer>(inspect_entity).get()
-                    {
-                        CollapsingHeader::new("MeshRenderer").default_open(true).show(ui, |ui| {
-                            e.inspect(
-                                &mut inspect_entity,
-                                &mut cfg,
-                                ui,
-                                self.undo_stack,
-                                self.signal,
-                                &mut String::new(),
-                            );
-                        });
-                    }
-
-                    // entity transform
-                    if let Ok(t) = world.query_one::<&mut EntityTransform>(inspect_entity).get()
-                    {
-                        CollapsingHeader::new("Transform").default_open(true).show(ui, |ui| {
-                            t.inspect(
-                                &mut inspect_entity,
-                                &mut cfg,
-                                ui,
-                                self.undo_stack,
-                                self.signal,
-                                &mut String::new(),
-                            );
-                        });
-                        ui.separator();
-                    }
-
-                    // custom properties
-                    if let Ok(props) = world.query_one::<&mut CustomProperties>(inspect_entity).get()
-                    {
-                        CollapsingHeader::new("Custom Properties").default_open(true).show(ui, |ui| {
-                            props.inspect(
-                                &mut inspect_entity,
-                                &mut cfg,
-                                ui,
-                                self.undo_stack,
-                                self.signal,
-                                label.as_mut_string(),
-                            );
-                        });
-                        ui.separator();
-                    }
-
-                    // camera
-                    if let Ok((camera, camera_component)) = world
-                        .query_one::<(&mut Camera, &mut CameraComponent)>(inspect_entity).get()
-                    {
-                        CollapsingHeader::new("Camera").default_open(true).show(ui, |ui| {
-                            camera.inspect(
-                                &mut inspect_entity,
-                                &mut cfg,
-                                ui,
-                                self.undo_stack,
-                                self.signal,
-                                &mut String::new(),
-                            );
-
-                            ui.separator();
-
-                            camera_component.inspect(
-                                &mut inspect_entity,
-                                &mut cfg,
-                                ui,
-                                self.undo_stack,
-                                self.signal,
-                                &mut camera.label.clone(),
-                            );
-
-                            ui.separator();
-
-                            // camera controller
-                            let mut active_camera = self.active_camera.lock();
-
-                            if active_camera.equivalent(&Some(*entity)) {
-                                ui.label("Status: Currently viewing through camera");
-                            } else {
-                                ui.label("Status: Not viewing through this camera");
-                            }
-
-                            if ui.button("Set as active camera").clicked() {
-                                *active_camera = Some(*entity);
-                                log::info!(
-                                            "Currently viewing from camera angle '{}'",
-                                            camera.label
-                                        );
-                            }
-
-                            if camera_component.starting_camera {
-                                if ui.button("Stop making camera initial").clicked() {
-                                    log::debug!("'Stop making camera initial' button clicked");
-                                    camera_component.starting_camera = false;
-                                    success!("Removed {} from starting camera", camera.label);
-                                }
-                            } else if ui.button("Set as initial camera").clicked() {
-                                log::debug!("'Set as initial camera' button clicked");
-                                if matches!(camera_component.camera_type, CameraType::Debug) {
-                                    warn!(
-                                                "Cannot set any cameras of type 'Debug' to initial camera"
-                                            );
-                                } else {
-                                    local_set_initial_camera = true
-                                }
-                            }
-                        });
-                        ui.separator();
-                    }
-
-                    // light
-                    if let Ok((light, comp, transform)) = world.query_one::<(&mut Light, &mut LightComponent, &mut Transform)>(inspect_entity).get()
-                    {
-                        light.transform = *transform;
-                        light.light_component = comp.clone();
-
-                        light.inspect(
-                            entity,
-                            &mut cfg,
-                            ui,
-                            self.undo_stack,
-                            self.signal,
-                            &mut String::new(),
-                        );
-
-                        *transform = light.transform;
-                        *comp = light.light_component.clone();
-                        ui.separator();
-                    }
-
-                    // script
-                    if let Ok((script, ui_c)) = world.query_one::<(Option<&mut Script>, Option<&mut UIComponent>)>(*entity).get()
-                    {
-                        CollapsingHeader::new("Script").default_open(true).show(ui, |ui| {
-                            if let Some(s) = script {
-                                s.inspect(
-                                    entity,
-                                    &mut cfg,
-                                    ui,
-                                    self.undo_stack,
-                                    self.signal,
-                                    label.as_mut_string(),
-                                );
-                            }
-
-                            if let Some(ui_c) = ui_c {
-                                CollapsingHeader::new("UI").default_open(true).show(ui, |ui| {
-                                    ui_c.inspect(
-                                        entity,
-                                        &mut cfg,
-                                        ui,
-                                        self.undo_stack,
-                                        self.signal,
-                                        label.as_mut_string(),
-                                    );
-                                });
-                            }
-                        });
-                        ui.separator();
-                    }
-
-                    // physics
-                    if let Ok((rigid, colliders, kcc)) = world.query_one::<(Option<&mut RigidBody>, Option<&mut ColliderGroup>, Option<&mut KCC>)>(*entity).get()
-                    {
-                        if rigid.is_some() || colliders.is_some() || kcc.is_some() {
-                            CollapsingHeader::new("Physics").default_open(true).show(ui, |ui| {
-
-                                if let Some(kcc) = kcc {
-                                    CollapsingHeader::new("Kinematic Character Controller").default_open(true).show(ui, |ui| {
-                                        kcc.inspect(
-                                            entity,
-                                            &mut cfg,
-                                            ui,
-                                            self.undo_stack,
-                                            self.signal,
-                                            label.as_mut_string(),
-                                        );
-                                    });
-                                    ui.separator();
-                                }
-
-                                if let Some(rigid) = rigid {
-                                    CollapsingHeader::new("RigidBody").default_open(true).show(ui, |ui| {
-                                        rigid.inspect(
-                                            entity,
-                                            &mut cfg,
-                                            ui,
-                                            self.undo_stack,
-                                            self.signal,
-                                            label.as_mut_string(),
-                                        );
-                                    });
-                                    ui.separator();
-                                }
-
-                                if let Some(col) = colliders {
-                                    CollapsingHeader::new("Colliders").default_open(true).show(ui, |ui| {
-                                        let mut to_remove: Option<usize> = None;
-
-                                        for (index, c) in col.colliders.iter_mut().enumerate() {
-                                            ui.horizontal(|ui| {
-                                                let header = CollapsingHeader::new(format!("Collider {}", index + 1))
-                                                    .default_open(true);
-
-                                                header.show(ui, |ui| {
-                                                    c.inspect(
-                                                        entity,
-                                                        &mut cfg,
-                                                        ui,
-                                                        self.undo_stack,
-                                                        self.signal,
-                                                        label.as_mut_string(),
-                                                    );
-                                                });
-
-                                                ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
-                                                    if ui.button("🗑").on_hover_text("Remove collider").clicked() {
-                                                        to_remove = Some(index);
-                                                    }
-                                                });
-                                            });
-
-                                            ui.separator();
-                                        }
-
-                                        if let Some(index) = to_remove {
-                                            col.colliders.remove(index);
-                                        }
-
-                                        if ui.button("➕ Add new collider").clicked() {
-                                            local_add_collider = Some(*entity);
-                                        }
-                                    });
-                                }
-
-                                ui.separator();
-                            });
-                        }
-                        ui.separator();
-                    }
-                } else {
-                    log_once::debug_once!("Unable to query entity inside resource inspector");
-                }
-            }
-
-            if local_set_initial_camera {
-                for (id, comp) in self.world.query::<(Entity, &mut CameraComponent)>().iter() {
-                    comp.starting_camera = false;
-                    self.undo_stack
-                        .push(UndoableAction::RemoveStartingCamera(id))
-                }
-
-                if let Ok(comp) = self.world.query_one_mut::<&mut CameraComponent>(*entity)
-                {
-                    success!("This camera is currently set as the initial camera");
-                    comp.starting_camera = true;
-                }
+                self.component_registry
+                    .inspect_components(self.world, inspect_entity, ui);
             }
         } else if !local_scene_settings {
             ui.label("No entity selected, therefore no info to provide. Go on, what are you waiting for? Click an entity!");
@@ -307,23 +24,5 @@ impl<'a> EditorTabViewer<'a> {
             log_once::debug_once!("Rendering scene settings");
             self.scene_settings(&mut cfg, ui);
         }
-
-        if let Some(e) = local_add_collider {
-            let mut manual_edit = false;
-            if let Ok(col) = self.world.query_one::<Option<&mut ColliderGroup>>(e).get()
-            {
-                if let Some(col) = col {
-                    let mut collider = Collider::new();
-                    collider.id = col.colliders.len() as u32 + 1;
-                    col.insert(collider);
-                } else {
-                    manual_edit = true;
-                }
-            }
-
-            if manual_edit {
-                let _ = self.world.insert_one(e, ColliderGroup::new());
-            }
-        }
     }
 }
\ No newline at end of file
diff --git a/crates/eucalyptus-editor/src/editor/scene.rs b/crates/eucalyptus-editor/src/editor/scene.rs
index 8910a14..485d4a8 100644
--- a/crates/eucalyptus-editor/src/editor/scene.rs
+++ b/crates/eucalyptus-editor/src/editor/scene.rs
@@ -1,14 +1,15 @@
 use crossbeam_channel::unbounded;
+use dropbear_engine::animation::AnimationComponent;
 use dropbear_engine::buffer::ResizableBuffer;
-use glam::DMat4;
+use glam::{DMat4, Mat4};
 use wgpu::util::DeviceExt;
 use std::collections::HashMap;
+use std::sync::Arc;
 use super::*;
 use crate::signal::SignalController;
 use crate::spawn::PendingSpawnController;
-use dropbear_engine::asset::{PointerKind, ASSET_REGISTRY};
+use dropbear_engine::asset::{ASSET_REGISTRY, Handle};
 use dropbear_engine::graphics::{CommandEncoder, InstanceRaw};
-use dropbear_engine::model::MODEL_CACHE;
 use dropbear_engine::{
     entity::{EntityTransform, MeshRenderer, Transform},
     lighting::{Light, LightComponent, MAX_LIGHTS},
@@ -21,6 +22,7 @@ use log;
 use parking_lot::Mutex;
 use wgpu::Color;
 use winit::{event_loop::ActiveEventLoop, keyboard::KeyCode};
+use dropbear_engine::component::ComponentResources;
 use eucalyptus_core::physics::collider::ColliderGroup;
 use eucalyptus_core::physics::collider::ColliderShapeKey;
 use eucalyptus_core::physics::collider::shader::{ColliderInstanceRaw, create_wireframe_geometry};
@@ -122,6 +124,14 @@ impl Scene for Editor {
             }
         }
 
+        {
+            let mut resources = ComponentResources::new();
+            resources.insert(graphics.clone());
+            let resources = Arc::new(resources);
+            self.component_registry
+                .update_components(self.world.as_mut(), dt, &resources);
+        }
+
         if !self.is_world_loaded.is_fully_loaded() {
             log::debug!("Scene is not fully loaded, initialising...");
             return;
@@ -204,9 +214,6 @@ impl Scene for Editor {
             }
         }
 
-        let cache_mutex_ptr = std::sync::LazyLock::force(&MODEL_CACHE) as *const _;
-        ASSET_REGISTRY.add_pointer(PointerKind::Const("model_cache"), cache_mutex_ptr as usize);
-
         if let Some((_, tab)) = self.dock_state.find_active_focused() {
             self.is_viewport_focused = matches!(tab, EditorTab::Viewport);
         } else {
@@ -347,30 +354,14 @@ impl Scene for Editor {
     }
 
     fn render<'a>(&mut self, graphics: Arc<SharedGraphicsContext>) {
-        self.size = graphics.viewport_texture.size;
-        self.texture_id = Some(*graphics.texture_id.clone());
-        self.window = Some(graphics.window.clone());
+        self.editor_specific_render(&graphics);
 
         let hdr = graphics.hdr.read();
 
-        self.show_ui(&graphics.get_egui_context());
-        let clear_color = Color {
-            r: 100.0 / 255.0,
-            g: 149.0 / 255.0,
-            b: 237.0 / 255.0,
-            a: 1.0,
-        };
-        self.color = clear_color;
-        eucalyptus_core::logging::render(&graphics.get_egui_context());
-
-        let mut encoder = CommandEncoder::new(graphics.clone(), Some("editor viewport encoder"));
-
-        let cam = {
-            let c = self.active_camera.lock();
-            *c
-        };
+        let mut encoder = CommandEncoder::new(graphics.clone(), Some("runtime viewport encoder"));
 
-        let Some(active_camera) = cam else {
+        let active_camera = {self.active_camera.lock().as_ref().cloned()};
+        let Some(active_camera) = active_camera else {
             return;
         };
         log_once::debug_once!("Active camera found: {:?}", active_camera);
@@ -438,32 +429,34 @@ impl Scene for Editor {
                 globals.write(&graphics.queue);
             }
 
-        let renderers = {
-            let mut renderers = Vec::new();
-            let mut query = self.world.query::<&MeshRenderer>();
-            for renderer in query.iter() {
-                renderers.push(renderer.clone());
-            }
-            renderers
-        };
+        let mut static_batches: HashMap<u64, Vec<InstanceRaw>> = HashMap::new();
+        let mut animated_instances: Vec<(u64, InstanceRaw, wgpu::BindGroup)> = Vec::new();
+
+        {
+            let mut query = self
+                .world
+                .query::<(&MeshRenderer, Option<&AnimationComponent>)>();
 
-        let mut model_batches: HashMap<ModelId, Vec<InstanceRaw>> = HashMap::new();
-        for renderer in &renderers {
-            model_batches
-                .entry(renderer.model_id())
-                .or_default()
-                .push(renderer.instance.to_raw());
+            for (renderer, animation) in query.iter() {
+                let handle = renderer.model();
+                if handle.is_null() {
+                    continue;
+                }
+
+                let instance = renderer.instance.to_raw();
+                if let Some(bind_group) = animation.and_then(|anim| anim.bind_group.clone()) {
+                    animated_instances.push((handle.id, instance, bind_group));
+                } else {
+                    static_batches.entry(handle.id).or_default().push(instance);
+                }
+            }
         }
 
+        let registry = ASSET_REGISTRY.read();
         let mut prepared_models = Vec::new();
-        for (model_id, instances) in model_batches {
-            let model_opt = {
-                let cache = MODEL_CACHE.lock();
-                cache.values().find(|model| model.id == model_id).cloned()
-            };
-
-            let Some(model) = model_opt else {
-                log_once::error_once!("Missing model {:?} in cache", model_id);
+        for (handle, instances) in static_batches {
+            let Some(model) = registry.get_model(Handle::new(handle)).cloned() else {
+                log_once::error_once!("Missing model handle {} in registry", handle);
                 continue;
             };
 
@@ -499,12 +492,13 @@ impl Scene for Editor {
             }
         }
 
+        let registry = ASSET_REGISTRY.read();
         {
             let mut render_pass = encoder
                 .begin_render_pass(&wgpu::RenderPassDescriptor {
                     label: Some("light cube render pass"),
                     color_attachments: &[Some(wgpu::RenderPassColorAttachment {
-                            view: hdr.view(),
+                        view: hdr.view(),
                         depth_slice: None,
                         resolve_target: None,
                         ops: wgpu::Operations {
@@ -527,17 +521,54 @@ impl Scene for Editor {
                 render_pass.set_pipeline(light_pipeline.pipeline());
                 for (light, component) in &lights {
                     render_pass.set_vertex_buffer(1, light.instance_buffer.buffer().slice(..));
-                    if component.visible {
-                        render_pass.draw_light_model(
-                            &light.cube_model,
-                            &camera.bind_group,
-                            &light.bind_group
-                        );
+                    if !component.visible {
+                        continue;
                     }
+
+                    let Some(model) = registry.get_model(light.cube_model) else {
+                        log_once::error_once!(
+                            "Missing light cube model handle {} in registry",
+                            light.cube_model.id
+                        );
+                        continue;
+                    };
+
+                    render_pass.draw_light_model(
+                        model,
+                        &camera.bind_group,
+                        &light.bind_group,
+                    );
                 }
             }
         }
 
+        if self.default_skinning_bind_group.is_none() {
+            let identity = [Mat4::IDENTITY.to_cols_array_2d()];
+            let buffer = graphics.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
+                label: Some("default skinning buffer"),
+                contents: bytemuck::cast_slice(&identity),
+                usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
+            });
+
+            let bind_group = graphics.device.create_bind_group(&wgpu::BindGroupDescriptor {
+                label: Some("default skinning bind group"),
+                layout: &graphics.layouts.skinning_bind_group_layout,
+                entries: &[wgpu::BindGroupEntry {
+                    binding: 0,
+                    resource: buffer.as_entire_binding(),
+                }],
+            });
+
+            self.default_skinning_buffer = Some(buffer);
+            self.default_skinning_bind_group = Some(bind_group);
+        }
+
+        let default_skinning_bind_group = self
+            .default_skinning_bind_group
+            .as_ref()
+            .expect("Default skinning bind group not initialized");
+
+        // model rendering
         if let Some(lcp) = &self.light_cube_pipeline {
             for (model, instance_buffer, instance_count) in prepared_models {
                 let globals_bind_group = &self
@@ -572,15 +603,85 @@ impl Scene for Editor {
                 render_pass.set_pipeline(pipeline.pipeline());
                 render_pass.set_vertex_buffer(1, instance_buffer.slice(..));
                 render_pass.set_bind_group(3, globals_bind_group, &[]);
-                render_pass.draw_model_instanced(
-                    &model,
-                    0..instance_count,
-                    &camera.bind_group,
-                    lcp.bind_group(),
+
+                for mesh in &model.meshes {
+                    let material = &model.materials[mesh.material];
+                    render_pass.draw_mesh_instanced(
+                        mesh,
+                        material,
+                        0..instance_count,
+                        &camera.bind_group,
+                        lcp.bind_group(),
+                        Some(default_skinning_bind_group),
+                    );
+                }
+            }
+        }
+
+        if let Some(lcp) = &self.light_cube_pipeline {
+            let globals_bind_group = &self
+                .shader_globals
+                .as_ref()
+                .expect("Shader globals not initialised")
+                .bind_group;
+
+            for (handle, instance, skin_bind_group) in animated_instances {
+                let Some(model) = registry.get_model(Handle::new(handle)).cloned() else {
+                    log_once::error_once!("Missing model handle {} in registry", handle);
+                    continue;
+                };
+
+                let instance_buffer = graphics.device.create_buffer_init(
+                    &wgpu::util::BufferInitDescriptor {
+                        label: Some("Runtime Animated Instance Buffer"),
+                        contents: bytemuck::cast_slice(&[instance]),
+                        usage: wgpu::BufferUsages::VERTEX,
+                    },
                 );
+
+                let mut render_pass = encoder
+                    .begin_render_pass(&wgpu::RenderPassDescriptor {
+                        label: Some("model render pass (animated)"),
+                        color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+                            view: hdr.view(),
+                            depth_slice: None,
+                            resolve_target: None,
+                            ops: wgpu::Operations {
+                                load: wgpu::LoadOp::Load,
+                                store: wgpu::StoreOp::Store,
+                            },
+                        })],
+                        depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
+                            view: &graphics.depth_texture.view,
+                            depth_ops: Some(wgpu::Operations {
+                                load: wgpu::LoadOp::Load,
+                                store: wgpu::StoreOp::Store,
+                            }),
+                            stencil_ops: None,
+                        }),
+                        occlusion_query_set: None,
+                        timestamp_writes: None,
+                    });
+
+                render_pass.set_pipeline(pipeline.pipeline());
+                render_pass.set_vertex_buffer(1, instance_buffer.slice(..));
+                render_pass.set_bind_group(3, globals_bind_group, &[]);
+
+                for mesh in &model.meshes {
+                    let material = &model.materials[mesh.material];
+                    render_pass.draw_mesh_instanced(
+                        mesh,
+                        material,
+                        0..1,
+                        &camera.bind_group,
+                        lcp.bind_group(),
+                        Some(&skin_bind_group),
+                    );
+                }
             }
         }
 
+        // collider pipeline
         {
             let show_hitboxes = self
                 .current_scene_name
@@ -714,44 +815,44 @@ impl Scene for Editor {
                             );
                         }
                     }
-}
+                }
             }
+        }
 
-            if let Some(sky) = &self.sky_pipeline {
-                let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
-                    label: Some("sky render pass"),
-                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
-                        view: hdr.view(),
-                        resolve_target: None,
-                        ops: wgpu::Operations {
-                            load: wgpu::LoadOp::Load,
-                            store: wgpu::StoreOp::Store,
-                        },
-                        depth_slice: None,
-                    })],
-                    depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
-                        view: &graphics.depth_texture.view,
-                        depth_ops: Some(wgpu::Operations {
-                            load: wgpu::LoadOp::Load,
-                            store: wgpu::StoreOp::Store,
-                        }),
-                        stencil_ops: None,
+        if let Some(sky) = &self.sky_pipeline {
+            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+                label: Some("sky render pass"),
+                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+                    view: hdr.view(),
+                    resolve_target: None,
+                    ops: wgpu::Operations {
+                        load: wgpu::LoadOp::Load,
+                        store: wgpu::StoreOp::Store,
+                    },
+                    depth_slice: None,
+                })],
+                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
+                    view: &graphics.depth_texture.view,
+                    depth_ops: Some(wgpu::Operations {
+                        load: wgpu::LoadOp::Load,
+                        store: wgpu::StoreOp::Store,
                     }),
-                    timestamp_writes: None,
-                    occlusion_query_set: None,
-                });
+                    stencil_ops: None,
+                }),
+                timestamp_writes: None,
+                occlusion_query_set: None,
+            });
 
-                render_pass.set_pipeline(&sky.pipeline);
-                render_pass.set_bind_group(0, &camera.bind_group, &[]);
-                render_pass.set_bind_group(1, &sky.bind_group, &[]);
-                render_pass.draw(0..3, 0..1);
-            }
+            render_pass.set_pipeline(&sky.pipeline);
+            render_pass.set_bind_group(0, &camera.bind_group, &[]);
+            render_pass.set_bind_group(1, &sky.bind_group, &[]);
+            render_pass.draw(0..3, 0..1);
+        }
 
-            hdr.process(&mut encoder, &graphics.viewport_texture.view);
+        hdr.process(&mut encoder, &graphics.viewport_texture.view);
 
-            if let Err(e) = encoder.submit() {
-                log_once::error_once!("{}", e);
-            }
+        if let Err(e) = encoder.submit() {
+            log_once::error_once!("{}", e);
         }
     }
 
@@ -760,4 +861,15 @@ impl Scene for Editor {
     fn run_command(&mut self) -> SceneCommand {
         std::mem::replace(&mut self.scene_command, SceneCommand::None)
     }
+}
+
+impl Editor {
+    fn editor_specific_render(&mut self, graphics: &Arc<SharedGraphicsContext>) {
+        self.size = graphics.viewport_texture.size;
+        self.texture_id = Some(*graphics.texture_id.clone());
+        self.window = Some(graphics.window.clone());
+        
+        self.show_ui(&graphics.get_egui_context());
+        eucalyptus_core::logging::render(&graphics.get_egui_context());
+    }
 }
\ No newline at end of file
diff --git a/crates/eucalyptus-editor/src/physics.rs b/crates/eucalyptus-editor/src/physics.rs
index 77a31d1..ee4c291 100644
--- a/crates/eucalyptus-editor/src/physics.rs
+++ b/crates/eucalyptus-editor/src/physics.rs
@@ -1,3 +1,3 @@
-pub mod rigidbody;
-pub mod collider;
-pub mod kcc;
\ No newline at end of file
+// pub mod rigidbody;
+// pub mod collider;
+// pub mod kcc;
\ No newline at end of file
diff --git a/crates/eucalyptus-editor/src/physics/collider.rs b/crates/eucalyptus-editor/src/physics/collider.rs
index defae08..9c7909c 100644
--- a/crates/eucalyptus-editor/src/physics/collider.rs
+++ b/crates/eucalyptus-editor/src/physics/collider.rs
@@ -2,7 +2,6 @@ use egui::{ComboBox, Ui};
 use hecs::Entity;
 use eucalyptus_core::physics::collider::{Collider, ColliderShape};
 use eucalyptus_core::states::Label;
-use crate::editor::component::InspectableComponent;
 use crate::editor::{Signal, StaticallyKept, UndoableAction};
 
 impl InspectableComponent for Collider {
diff --git a/crates/eucalyptus-editor/src/signal.rs b/crates/eucalyptus-editor/src/signal.rs
index 7cc26dc..43d5fce 100644
--- a/crates/eucalyptus-editor/src/signal.rs
+++ b/crates/eucalyptus-editor/src/signal.rs
@@ -4,13 +4,13 @@ use dropbear_engine::camera::Camera;
 use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform};
 use dropbear_engine::graphics::SharedGraphicsContext;
 use dropbear_engine::lighting::{Light as EngineLight, LightComponent};
-use dropbear_engine::model::{LoadedModel, Material, Model, MODEL_CACHE};
+use dropbear_engine::model::{Material, Model};
 use dropbear_engine::procedural::{ProcedurallyGeneratedObject, ProcObj};
 use dropbear_engine::texture::{Texture, TextureWrapMode};
 use dropbear_engine::utils::{
     relative_path_from_euca, EUCA_SCHEME, ResourceReference, ResourceReferenceType,
 };
-use dropbear_traits::{ComponentInitContext, ComponentInsert, ComponentResources};
+use dropbear_engine::component::{ComponentInitContext, ComponentInsert, ComponentResources};
 use egui::Align2;
 use eucalyptus_core::camera::{CameraComponent, CameraType};
 use eucalyptus_core::properties::CustomProperties;
@@ -90,14 +90,6 @@ impl SignalController for Editor {
                 self.signal = Signal::Copy(scene_entity.clone());
                 Ok(())
             }
-
-            Signal::SetModelImportScale(entity, scale) => {
-                if let Ok(mut renderer) = self.world.get::<&mut MeshRenderer>(*entity) {
-                    renderer.set_import_scale(*scale);
-                }
-                self.signal = Signal::None;
-                Ok(())
-            }
             Signal::Delete => {
                 if let Some(sel_e) = &self.selected_entity {
                     let is_viewport_cam =
@@ -588,417 +580,6 @@ impl SignalController for Editor {
                 self.signal = Signal::None;
                 Ok(())
             }
-
-            Signal::ClearModel(entity) => {
-                let unassigned_id = (*entity).to_bits().get();
-                let reference = ResourceReference::from_reference(
-                    ResourceReferenceType::Unassigned { id: unassigned_id },
-                );
-
-                let model = std::sync::Arc::new(Model {
-                    label: "None".to_string(),
-                    hash: unassigned_id,
-                    path: reference,
-                    meshes: Vec::new(),
-                    materials: Vec::new(),
-                    skins: Vec::new(),
-                    animations: Vec::new(),
-                    nodes: Vec::new(),
-                });
-                let loaded_model = LoadedModel::new_raw(&dropbear_engine::asset::ASSET_REGISTRY, model);
-
-                if let Ok(mut renderer) = self.world.get::<&mut MeshRenderer>(*entity) {
-                    renderer.set_handle(loaded_model);
-                } else {
-                    let renderer = MeshRenderer::from_handle(loaded_model);
-                    let _ = self.world.insert_one(*entity, renderer);
-                }
-
-                self.signal = Signal::None;
-                Ok(())
-            }
-            Signal::ReplaceModel(entity, uri) => {
-                let graphics_clone = graphics.clone();
-                let uri_clone = uri.clone();
-                let future = async move {
-                    let mut loaded_model = if is_legacy_internal_cube_uri(&uri_clone) {
-                        let size = glam::DVec3::new(1.0, 1.0, 1.0);
-                        let size_bits = [1.0f32.to_bits(), 1.0f32.to_bits(), 1.0f32.to_bits()];
-                        let mut loaded = ProcedurallyGeneratedObject::cuboid(size)
-                            .build_model(graphics_clone.clone(), None, Some("Cuboid"), ASSET_REGISTRY.clone());
-
-                        let model = loaded.make_mut();
-                        model.path = ResourceReference::from_reference(ResourceReferenceType::ProcObj(ProcObj::Cuboid { size_bits }));
-                        loaded.refresh_registry();
-                        loaded
-                    } else {
-                        let path = resolve_editor_path(&uri_clone);
-                        Model::load(graphics_clone.clone(), &path, Some(&uri_clone), None).await?
-                    };
-
-                    // Ensure imports start as pure white; users can tint later.
-                    {
-                        let model = loaded_model.make_mut();
-                        for material in &mut model.materials {
-                            material.set_tint(graphics_clone.as_ref(), [1.0, 1.0, 1.0, 1.0]);
-                        }
-                    }
-
-                    loaded_model.refresh_registry();
-                    Ok::<LoadedModel, anyhow::Error>(loaded_model)
-                };
-
-                let handle = graphics.future_queue.push(Box::pin(future));
-                self.pending_model_swaps.push((*entity, handle));
-                success!("Queued model swap for entity {:?} from '{}'", entity, uri);
-                self.signal = Signal::None;
-                Ok(())
-            }
-
-            Signal::LoadModel(entity, uri) => {
-                let graphics_clone = graphics.clone();
-                let uri_clone = uri.clone();
-                let future = async move {
-                    let renderer = if is_legacy_internal_cube_uri(&uri_clone) {
-                        let size = glam::DVec3::new(1.0, 1.0, 1.0);
-                        let size_bits = [1.0f32.to_bits(), 1.0f32.to_bits(), 1.0f32.to_bits()];
-                        let mut loaded_model = ProcedurallyGeneratedObject::cuboid(size)
-                            .build_model(graphics_clone.clone(), None, Some("Cuboid"), ASSET_REGISTRY.clone());
-
-                        {
-                            let model = loaded_model.make_mut();
-                            model.path = ResourceReference::from_reference(ResourceReferenceType::ProcObj(ProcObj::Cuboid { size_bits }));
-                            for material in &mut model.materials {
-                                material.set_tint(graphics_clone.as_ref(), [1.0, 1.0, 1.0, 1.0]);
-                            }
-                        }
-                        loaded_model.refresh_registry();
-
-                        MeshRenderer::from_handle(loaded_model)
-                    } else {
-                        let path = resolve_editor_path(&uri_clone);
-                        let mut model = dropbear_engine::model::Model::load(
-                            graphics_clone.clone(),
-                            &path,
-                            Some(&uri_clone),
-                            None,
-                        )
-                        .await?;
-
-                        {
-                            let model_mut = model.make_mut();
-                            for material in &mut model_mut.materials {
-                                material.set_tint(graphics_clone.as_ref(), [1.0, 1.0, 1.0, 1.0]);
-                            }
-                        }
-
-                        model.refresh_registry();
-                        dropbear_engine::entity::MeshRenderer::from_handle(model)
-                    };
-
-                    Ok::<Box<dyn ComponentInsert>, anyhow::Error>(Box::new(InsertMeshRenderer {
-                        renderer,
-                        ensure_transform: true,
-                    }))
-                };
-
-                let handle = graphics.future_queue.push(Box::pin(future));
-                self.pending_components.push((*entity, handle));
-                success!("Queued model load for entity {:?} from '{}'", entity, uri);
-                self.signal = Signal::None;
-                Ok(())
-            }
-
-            Signal::SetProceduralCuboid(entity, size)
-            | Signal::UpdateProceduralCuboid(entity, size) => {
-                let previous_customisation: Option<
-                    Vec<(String, [f32; 4], Option<String>, TextureWrapMode, [f32; 2])>,
-                > =
-                    self.world
-                        .get::<&MeshRenderer>(*entity)
-                        .ok()
-                        .map(|renderer| {
-                            renderer
-                                .model()
-                                .materials
-                                .iter()
-                                .map(|mat| {
-                                    (
-                                        mat.name.clone(),
-                                        mat.tint,
-                                        mat.texture_tag.clone(),
-                                        mat.wrap_mode,
-                                        mat.uv_tiling,
-                                    )
-                                })
-                                .collect()
-                        });
-
-                let label = self
-                    .world
-                    .get::<&eucalyptus_core::states::Label>(*entity)
-                    .map(|l| l.to_string())
-                    .unwrap_or_else(|_| "Cuboid".to_string());
-
-                {
-                    let mut cache_guard = MODEL_CACHE.lock();
-                    cache_guard.remove(&label);
-                }
-
-                let size_bits = [size[0].to_bits(), size[1].to_bits(), size[2].to_bits()];
-                let size_vec = glam::DVec3::new(size[0] as f64, size[1] as f64, size[2] as f64);
-
-                let mut loaded_model = ProcedurallyGeneratedObject::cuboid(size_vec)
-                    .build_model(graphics.clone(), None, Some(&label), ASSET_REGISTRY.clone());
-
-                {
-                    let model = loaded_model.make_mut();
-                    model.path = ResourceReference::from_reference(ResourceReferenceType::ProcObj(ProcObj::Cuboid { size_bits }));
-                }
-                loaded_model.refresh_registry();
-
-                if let Ok(mut renderer) = self.world.get::<&mut MeshRenderer>(*entity) {
-                    renderer.set_handle(loaded_model);
-
-                    if let Some(previous) = previous_customisation {
-                        let model = renderer.make_model_mut();
-                        for (mat_name, tint, texture_tag, wrap_mode, uv_tiling) in previous {
-                            if let Some(material) =
-                                model.materials.iter_mut().find(|m| m.name == mat_name)
-                            {
-                                material.wrap_mode = wrap_mode;
-                                material.set_tint(graphics.as_ref(), tint);
-                                material.set_uv_tiling(graphics.as_ref(), uv_tiling);
-
-                                if let Some(uri) = texture_tag {
-                                    if uri.to_ascii_lowercase().ends_with(".png")
-                                        || uri.to_ascii_lowercase().ends_with(".jpg")
-                                        || uri.to_ascii_lowercase().ends_with(".jpeg")
-                                        || uri.to_ascii_lowercase().ends_with(".tga")
-                                        || uri.to_ascii_lowercase().ends_with(".bmp")
-                                    {
-                                        let path = resolve_editor_path(&uri);
-
-                                        if let Ok(bytes) = std::fs::read(&path) {
-                                            let diffuse = Texture::from_bytes_verbose_mipmapped(
-                                                graphics.clone(),
-                                                &bytes,
-                                                None,
-                                                None,
-                                                Some(Texture::sampler_from_wrap(wrap_mode)),
-                                                Some(mat_name.as_str()),
-                                            );
-                                            let flat_normal = (*dropbear_engine::asset::ASSET_REGISTRY
-                                                .solid_texture_rgba8(
-                                                    graphics.clone(),
-                                                    [128, 128, 255, 255],
-                                                ))
-                                            .clone();
-
-                                            material.diffuse_texture = diffuse;
-                                            material.normal_texture = flat_normal;
-                                            material.bind_group =
-                                                dropbear_engine::model::Material::create_bind_group(
-                                                    graphics.as_ref(),
-                                                    &material.diffuse_texture,
-                                                    &material.normal_texture,
-                                                    &material.tint_buffer,
-                                                    &material.name,
-                                                );
-                                            material.texture_tag = Some(uri);
-                                        }
-                                    } else {
-                                        material.texture_tag = Some(uri);
-                                    }
-                                }
-                            }
-                        }
-
-                        renderer.sync_asset_registry();
-                    }
-                } else {
-                    let renderer = MeshRenderer::from_handle(loaded_model);
-                    let _ = self.world.insert_one(*entity, renderer);
-                }
-
-                self.signal = Signal::None;
-                Ok(())
-            }
-
-            Signal::SetMaterialTexture(entity, target_material, uri, wrap_mode) => {
-                let path = resolve_editor_path(uri);
-
-                let bytes = match std::fs::read(&path) {
-                    Ok(bytes) => bytes,
-                    Err(err) => {
-                        warn!("Failed to read texture '{}': {}", path.display(), err);
-                        self.signal = Signal::None;
-                        return Ok(());
-                    }
-                };
-
-                let diffuse = Texture::from_bytes_verbose_mipmapped(
-                    graphics.clone(),
-                    &bytes,
-                    None,
-                    None,
-                    Some(Texture::sampler_from_wrap(wrap_mode.clone())),
-                    Some(target_material),
-                );
-                let flat_normal = (*dropbear_engine::asset::ASSET_REGISTRY
-                    .solid_texture_rgba8(graphics.clone(), [128, 128, 255, 255]))
-                    .clone();
-
-                if let Ok(mut renderer) = self.world.get::<&mut MeshRenderer>(*entity) {
-                    let model = renderer.make_model_mut();
-                    if let Some(material) = model
-                        .materials
-                        .iter_mut()
-                        .find(|mat| mat.name == *target_material)
-                    {
-                        material.diffuse_texture = diffuse;
-                        material.normal_texture = flat_normal;
-                        material.bind_group = Material::create_bind_group(
-                            graphics.as_ref(),
-                            &material.diffuse_texture,
-                            &material.normal_texture,
-                            &material.tint_buffer,
-                            &material.name,
-                        );
-                        material.texture_tag = Some(uri.clone());
-                        material.wrap_mode = *wrap_mode;
-                    } else {
-                        warn!("Material '{}' not found on renderer", target_material);
-                    }
-
-                    renderer.sync_asset_registry();
-                }
-
-                self.signal = Signal::None;
-                Ok(())
-            }
-
-            Signal::SetMaterialWrapMode(entity, target_material, wrap_mode) => {
-                if let Ok(mut renderer) = self.world.get::<&mut MeshRenderer>(*entity) {
-                    let model = renderer.make_model_mut();
-                    if let Some(material) = model
-                        .materials
-                        .iter_mut()
-                        .find(|mat| mat.name == *target_material)
-                    {
-                        material.wrap_mode = *wrap_mode;
-
-                        if let Some(uri) = material.texture_tag.clone() {
-                            let path = resolve_editor_path(&uri);
-
-                            if let Ok(bytes) = std::fs::read(&path) {
-                                let diffuse = Texture::from_bytes_verbose_mipmapped(
-                                    graphics.clone(),
-                                    &bytes,
-                                    None,
-                                    None,
-                                    Some(Texture::sampler_from_wrap(wrap_mode.clone())),
-                                    Some(target_material),
-                                );
-                                material.diffuse_texture = diffuse;
-                                material.bind_group = Material::create_bind_group(
-                                    graphics.as_ref(),
-                                    &material.diffuse_texture,
-                                    &material.normal_texture,
-                                    &material.tint_buffer,
-                                    &material.name,
-                                );
-                            } else {
-                                warn!(
-                                    "Failed to read texture '{}' to apply wrap mode",
-                                    path.display()
-                                );
-                            }
-                        }
-                    } else {
-                        warn!("Material '{}' not found on renderer", target_material);
-                    }
-
-                    renderer.sync_asset_registry();
-                }
-
-                self.signal = Signal::None;
-                Ok(())
-            }
-
-            Signal::SetMaterialUvTiling(entity, target_material, uv_tiling) => {
-                if let Ok(mut renderer) = self.world.get::<&mut MeshRenderer>(*entity) {
-                    let model = renderer.make_model_mut();
-                    if let Some(material) = model
-                        .materials
-                        .iter_mut()
-                        .find(|mat| mat.name == *target_material)
-                    {
-                        material.set_uv_tiling(graphics.as_ref(), *uv_tiling);
-                    } else {
-                        warn!("Material '{}' not found on renderer", target_material);
-                    }
-
-                    renderer.sync_asset_registry();
-                }
-
-                self.signal = Signal::None;
-                Ok(())
-            }
-
-            Signal::ClearMaterialTexture(entity, target_material) => {
-                let diffuse =
-                    (*dropbear_engine::asset::ASSET_REGISTRY.grey_texture(graphics.clone()))
-                        .clone();
-                let flat_normal = (*dropbear_engine::asset::ASSET_REGISTRY
-                    .solid_texture_rgba8(graphics.clone(), [128, 128, 255, 255]))
-                    .clone();
-
-                if let Ok(mut renderer) = self.world.get::<&mut MeshRenderer>(*entity) {
-                    let model = renderer.make_model_mut();
-                    if let Some(material) = model
-                        .materials
-                        .iter_mut()
-                        .find(|mat| mat.name == *target_material)
-                    {
-                        material.diffuse_texture = diffuse;
-                        material.normal_texture = flat_normal;
-                        material.bind_group = Material::create_bind_group(
-                            graphics.as_ref(),
-                            &material.diffuse_texture,
-                            &material.normal_texture,
-                            &material.tint_buffer,
-                            &material.name,
-                        );
-                        material.texture_tag = None;
-                    } else {
-                        warn!("Material '{}' not found on renderer", target_material);
-                    }
-
-                    renderer.sync_asset_registry();
-                }
-
-                self.signal = Signal::None;
-                Ok(())
-            }
-
-            Signal::SetMaterialTint(entity, target_material, tint) => {
-                if let Ok(mut renderer) = self.world.get::<&mut MeshRenderer>(*entity) {
-                    let model = renderer.make_model_mut();
-                    if let Some(material) = model
-                        .materials
-                        .iter_mut()
-                        .find(|mat| mat.name == *target_material)
-                    {
-                        material.set_tint(graphics.as_ref(), *tint);
-                    } else {
-                        warn!("Material '{}' not found on renderer", target_material);
-                    }
-                }
-
-                self.signal = Signal::None;
-                Ok(())
-            }
             Signal::RequestNewWindow(window_data) => {
                 use dropbear_engine::scene::SceneCommand;
                 self.scene_command = SceneCommand::RequestWindow(window_data.clone());
diff --git a/crates/eucalyptus-editor/src/spawn.rs b/crates/eucalyptus-editor/src/spawn.rs
index 489812f..df16315 100644
--- a/crates/eucalyptus-editor/src/spawn.rs
+++ b/crates/eucalyptus-editor/src/spawn.rs
@@ -2,12 +2,13 @@ use crate::editor::Editor;
 use dropbear_engine::entity::{EntityTransform, MeshRenderer};
 use dropbear_engine::future::FutureQueue;
 use dropbear_engine::graphics::{SharedGraphicsContext};
-use dropbear_engine::model::LoadedModel;
-use dropbear_traits::{ComponentInitContext, ComponentInsert, ComponentResources};
+use dropbear_engine::model::{Model};
 pub(crate) use eucalyptus_core::spawn::{PendingSpawnController, PENDING_SPAWNS};
 use eucalyptus_core::{fatal, success};
 use hecs::Entity;
 use std::sync::Arc;
+use dropbear_engine::asset::Handle;
+use dropbear_engine::component::{ComponentInitContext, ComponentInsert, ComponentResources};
 
 impl PendingSpawnController for Editor {
     fn check_up(
@@ -126,11 +127,11 @@ impl PendingSpawnController for Editor {
         let mut completed_swaps = Vec::new();
         for (index, (entity, handle)) in self.pending_model_swaps.iter().enumerate() {
             if let Some(result) = queue.exchange_owned(handle) {
-                match result.downcast::<anyhow::Result<LoadedModel>>() {
+                match result.downcast::<anyhow::Result<Handle<Model>>>() {
                     Ok(r) => match Arc::try_unwrap(r) {
                         Ok(Ok(loaded_model)) => {
                             if let Ok(mut renderer) = self.world.get::<&mut MeshRenderer>(*entity) {
-                                renderer.set_handle(loaded_model);
+                                renderer.set_model(loaded_model)
                             } else {
                                 let renderer = MeshRenderer::from_handle(loaded_model);
                                 let _ = self.world.insert_one(*entity, renderer);
diff --git a/crates/redback-runtime/src/scene.rs b/crates/redback-runtime/src/scene.rs
index 67afe4e..c584f38 100644
--- a/crates/redback-runtime/src/scene.rs
+++ b/crates/redback-runtime/src/scene.rs
@@ -32,6 +32,7 @@ use eucalyptus_core::rapier3d::geometry::SharedShape;
 use eucalyptus_core::states::{Label, PROJECT};
 use eucalyptus_core::states::SCENES;
 use eucalyptus_core::scene::loading::{IsSceneLoaded, SceneLoadResult, SCENE_LOADER};
+use eucalyptus_core::traits::ComponentResources;
 use crate::PlayMode;
 use eucalyptus_core::physics::collider::shader::create_wireframe_geometry;
 use kino_ui::widgets::{Anchor, Border, Fill};
@@ -334,6 +335,14 @@ impl Scene for PlayMode {
         }
 
         {
+            let mut resources = ComponentResources::new();
+            resources.insert(graphics.clone());
+            let resources = Arc::new(resources);
+            self.component_registry
+                .update_components(self.world.as_mut(), dt, &resources);
+        }
+
+        {
             let mut query = self.world.query::<(&mut MeshRenderer, &Transform)>();
             for (renderer, transform) in query.iter() {
                 renderer.update(transform);
diff --git a/include/dropbear.h b/include/dropbear.h
index 66ea211..7672e60 100644
--- a/include/dropbear.h
+++ b/include/dropbear.h
@@ -68,39 +68,6 @@ typedef struct ColliderShapeFfi {
 
 typedef ColliderShapeFfi ColliderShape;
 
-typedef enum NShapeCastStatusTag {
-    NShapeCastStatusTag_OutOfIterations = 0,
-    NShapeCastStatusTag_Converged = 1,
-    NShapeCastStatusTag_Failed = 2,
-    NShapeCastStatusTag_PenetratingOrWithinTargetDist = 3,
-} NShapeCastStatusTag;
-
-typedef struct NShapeCastStatusOutOfIterations {
-} NShapeCastStatusOutOfIterations;
-
-typedef struct NShapeCastStatusConverged {
-} NShapeCastStatusConverged;
-
-typedef struct NShapeCastStatusFailed {
-} NShapeCastStatusFailed;
-
-typedef struct NShapeCastStatusPenetratingOrWithinTargetDist {
-} NShapeCastStatusPenetratingOrWithinTargetDist;
-
-typedef union NShapeCastStatusData {
-    NShapeCastStatusOutOfIterations OutOfIterations;
-    NShapeCastStatusConverged Converged;
-    NShapeCastStatusFailed Failed;
-    NShapeCastStatusPenetratingOrWithinTargetDist PenetratingOrWithinTargetDist;
-} NShapeCastStatusData;
-
-typedef struct NShapeCastStatusFfi {
-    NShapeCastStatusTag tag;
-    NShapeCastStatusData data;
-} NShapeCastStatusFfi;
-
-typedef NShapeCastStatusFfi NShapeCastStatus;
-
 typedef enum NAnimationInterpolationTag {
     NAnimationInterpolationTag_Linear = 0,
     NAnimationInterpolationTag_Step = 1,
@@ -179,6 +146,59 @@ typedef struct NChannelValuesFfi {
 
 typedef NChannelValuesFfi NChannelValues;
 
+typedef enum NShapeCastStatusTag {
+    NShapeCastStatusTag_OutOfIterations = 0,
+    NShapeCastStatusTag_Converged = 1,
+    NShapeCastStatusTag_Failed = 2,
+    NShapeCastStatusTag_PenetratingOrWithinTargetDist = 3,
+} NShapeCastStatusTag;
+
+typedef struct NShapeCastStatusOutOfIterations {
+} NShapeCastStatusOutOfIterations;
+
+typedef struct NShapeCastStatusConverged {
+} NShapeCastStatusConverged;
+
+typedef struct NShapeCastStatusFailed {
+} NShapeCastStatusFailed;
+
+typedef struct NShapeCastStatusPenetratingOrWithinTargetDist {
+} NShapeCastStatusPenetratingOrWithinTargetDist;
+
+typedef union NShapeCastStatusData {
+    NShapeCastStatusOutOfIterations OutOfIterations;
+    NShapeCastStatusConverged Converged;
+    NShapeCastStatusFailed Failed;
+    NShapeCastStatusPenetratingOrWithinTargetDist PenetratingOrWithinTargetDist;
+} NShapeCastStatusData;
+
+typedef struct NShapeCastStatusFfi {
+    NShapeCastStatusTag tag;
+    NShapeCastStatusData data;
+} NShapeCastStatusFfi;
+
+typedef NShapeCastStatusFfi NShapeCastStatus;
+
+typedef struct IndexNative {
+    uint32_t index;
+    uint32_t generation;
+} IndexNative;
+
+typedef struct IndexNativeArray {
+    IndexNative* values;
+    size_t length;
+    size_t capacity;
+} IndexNativeArray;
+
+typedef struct CharacterCollisionArray {
+    uint64_t entity_id;
+    IndexNativeArray collisions;
+} CharacterCollisionArray;
+
+typedef void* WorldPtr;
+
+typedef void* AssetRegistryPtr;
+
 typedef struct f64Array {
     double* values;
     size_t length;
@@ -210,6 +230,27 @@ typedef struct NAnimationArray {
     size_t capacity;
 } NAnimationArray;
 
+typedef struct NCollider {
+    IndexNative index;
+    uint64_t entity_id;
+    uint32_t id;
+} NCollider;
+
+typedef struct NColliderArray {
+    NCollider* values;
+    size_t length;
+    size_t capacity;
+} NColliderArray;
+
+typedef void* CommandBufferPtr;
+
+typedef void* SceneLoaderPtr;
+
+typedef struct NVector2 {
+    double x;
+    double y;
+} NVector2;
+
 typedef struct u64Array {
     uint64_t* values;
     size_t length;
@@ -220,8 +261,6 @@ typedef struct ConnectedGamepadIds {
     u64Array ids;
 } ConnectedGamepadIds;
 
-typedef void* InputStatePtr;
-
 typedef struct NVector4 {
     double x;
     double y;
@@ -229,10 +268,41 @@ typedef struct NVector4 {
     double w;
 } NVector4;
 
-typedef struct NVector2 {
-    double x;
-    double y;
-} NVector2;
+typedef struct i32Array {
+    int32_t* values;
+    size_t length;
+    size_t capacity;
+} i32Array;
+
+typedef struct NModelVertex {
+    NVector3 position;
+    NVector3 normal;
+    NVector4 tangent;
+    NVector2 tex_coords0;
+    NVector2 tex_coords1;
+    NVector4 colour0;
+    i32Array joints0;
+    NVector4 weights0;
+} NModelVertex;
+
+typedef struct NModelVertexArray {
+    NModelVertex* values;
+    size_t length;
+    size_t capacity;
+} NModelVertexArray;
+
+typedef struct NMesh {
+    const char* name;
+    int32_t num_elements;
+    int32_t material_index;
+    NModelVertexArray vertices;
+} NMesh;
+
+typedef struct NMeshArray {
+    NMesh* values;
+    size_t length;
+    size_t capacity;
+} NMeshArray;
 
 typedef struct NMaterial {
     const char* name;
@@ -258,48 +328,13 @@ typedef struct NMaterialArray {
     size_t capacity;
 } NMaterialArray;
 
-typedef struct i32Array {
-    int32_t* values;
-    size_t length;
-    size_t capacity;
-} i32Array;
-
-typedef struct f64ArrayArray {
-    double* values;
-    size_t length;
-    size_t capacity;
-} f64ArrayArray;
-
-typedef struct NSkin {
-    const char* name;
-    i32Array joints;
-    f64ArrayArray inverse_bind_matrices;
-    const int32_t* skeleton_root;
-} NSkin;
-
-typedef struct NSkinArray {
-    NSkin* values;
-    size_t length;
-    size_t capacity;
-} NSkinArray;
-
-typedef void* GraphicsContextPtr;
-
-typedef struct IndexNative {
-    uint32_t index;
-    uint32_t generation;
-} IndexNative;
-
-typedef struct IndexNativeArray {
-    IndexNative* values;
-    size_t length;
-    size_t capacity;
-} IndexNativeArray;
+typedef void* PhysicsStatePtr;
 
-typedef struct CharacterCollisionArray {
-    uint64_t entity_id;
-    IndexNativeArray collisions;
-} CharacterCollisionArray;
+typedef struct NTransform {
+    NVector3 position;
+    NQuaternion rotation;
+    NVector3 scale;
+} NTransform;
 
 typedef struct NNodeTransform {
     NVector3 translation;
@@ -320,47 +355,6 @@ typedef struct NNodeArray {
     size_t capacity;
 } NNodeArray;
 
-typedef struct NCollider {
-    IndexNative index;
-    uint64_t entity_id;
-    uint32_t id;
-} NCollider;
-
-typedef struct AxisLock {
-    bool x;
-    bool y;
-    bool z;
-} AxisLock;
-
-typedef void* PhysicsStatePtr;
-
-typedef void* SceneLoaderPtr;
-
-typedef struct NTransform {
-    NVector3 position;
-    NQuaternion rotation;
-    NVector3 scale;
-} NTransform;
-
-typedef struct NAttenuation {
-    float constant;
-    float linear;
-    float quadratic;
-} NAttenuation;
-
-typedef struct NColliderArray {
-    NCollider* values;
-    size_t length;
-    size_t capacity;
-} NColliderArray;
-
-typedef struct NColour {
-    uint8_t r;
-    uint8_t g;
-    uint8_t b;
-    uint8_t a;
-} NColour;
-
 typedef struct NShapeCastHit {
     NCollider collider;
     double distance;
@@ -371,63 +365,69 @@ typedef struct NShapeCastHit {
     NShapeCastStatus status;
 } NShapeCastHit;
 
-typedef void* WorldPtr;
+typedef struct NAttenuation {
+    float constant;
+    float linear;
+    float quadratic;
+} NAttenuation;
 
-typedef void* CommandBufferPtr;
+typedef struct Progress {
+    size_t current;
+    size_t total;
+    const char* message;
+} Progress;
 
-typedef struct NModelVertex {
-    NVector3 position;
-    NVector3 normal;
-    NVector4 tangent;
-    NVector2 tex_coords0;
-    NVector2 tex_coords1;
-    NVector4 colour0;
-    i32Array joints0;
-    NVector4 weights0;
-} NModelVertex;
+typedef struct NRange {
+    float start;
+    float end;
+} NRange;
 
-typedef struct NModelVertexArray {
-    NModelVertex* values;
+typedef struct f64ArrayArray {
+    double* values;
     size_t length;
     size_t capacity;
-} NModelVertexArray;
+} f64ArrayArray;
 
-typedef struct NMesh {
+typedef struct NSkin {
     const char* name;
-    int32_t num_elements;
-    int32_t material_index;
-    NModelVertexArray vertices;
-} NMesh;
+    i32Array joints;
+    f64ArrayArray inverse_bind_matrices;
+    const int32_t* skeleton_root;
+} NSkin;
 
-typedef struct NMeshArray {
-    NMesh* values;
+typedef struct NSkinArray {
+    NSkin* values;
     size_t length;
     size_t capacity;
-} NMeshArray;
+} NSkinArray;
 
 typedef struct RayHit {
     NCollider collider;
     double distance;
 } RayHit;
 
-typedef struct NRange {
-    float start;
-    float end;
-} NRange;
-
-typedef struct Progress {
-    size_t current;
-    size_t total;
-    const char* message;
-} Progress;
-
-typedef void* AssetRegistryPtr;
-
 typedef struct RigidBodyContext {
     IndexNative index;
     uint64_t entity_id;
 } RigidBodyContext;
 
+typedef struct AxisLock {
+    bool x;
+    bool y;
+    bool z;
+} AxisLock;
+
+typedef void* InputStatePtr;
+
+typedef void* GraphicsContextPtr;
+
+typedef struct NColour {
+    uint8_t r;
+    uint8_t g;
+    uint8_t b;
+    uint8_t a;
+} NColour;
+
 int32_t dropbear_gamepad_is_button_pressed(InputStatePtr input, uint64_t gamepad_id, int32_t button_ordinal, bool* out0);
 int32_t dropbear_gamepad_get_left_stick_position(InputStatePtr input, uint64_t gamepad_id, NVector2* out0);
 int32_t dropbear_gamepad_get_right_stick_position(InputStatePtr input, uint64_t gamepad_id, NVector2* out0);
@@ -459,6 +459,10 @@ int32_t dropbear_collider_get_collider_translation(PhysicsStatePtr physics, cons
 int32_t dropbear_collider_set_collider_translation(PhysicsStatePtr physics, const NCollider* collider, const NVector3* translation);
 int32_t dropbear_collider_get_collider_rotation(PhysicsStatePtr physics, const NCollider* collider, NVector3* out0);
 int32_t dropbear_collider_set_collider_rotation(PhysicsStatePtr physics, const NCollider* collider, const NVector3* rotation);
+int32_t dropbear_kcc_kcc_exists_for_entity(WorldPtr world, uint64_t entity, bool* out0);
+int32_t dropbear_kcc_move_character(WorldPtr world, PhysicsStatePtr physics_state, uint64_t entity, const NVector3* translation, double delta_time);
+int32_t dropbear_kcc_set_rotation(WorldPtr world, PhysicsStatePtr physics_state, uint64_t entity, const NQuaternion* rotation);
+int32_t dropbear_kcc_get_hit(WorldPtr world, uint64_t entity, CharacterCollisionArray* out0);
 int32_t dropbear_rigidbody_exists_for_entity(WorldPtr world, PhysicsStatePtr physics, uint64_t entity, IndexNative* out0, bool* out0_present);
 int32_t dropbear_rigidbody_get_rigidbody_mode(WorldPtr _world, PhysicsStatePtr physics, const RigidBodyContext* rigidbody, int32_t* out0);
 int32_t dropbear_rigidbody_set_rigidbody_mode(WorldPtr world, PhysicsStatePtr physics, const RigidBodyContext* rigidbody, int32_t mode);
@@ -483,10 +487,6 @@ int32_t dropbear_rigidbody_set_rigidbody_lock_rotation(WorldPtr world, PhysicsSt
 int32_t dropbear_rigidbody_get_rigidbody_children(WorldPtr _world, PhysicsStatePtr physics, const RigidBodyContext* rigidbody, NColliderArray* out0);
 int32_t dropbear_rigidbody_apply_impulse(WorldPtr world, PhysicsStatePtr physics, const RigidBodyContext* rigidbody, double x, double y, double z);
 int32_t dropbear_rigidbody_apply_torque_impulse(WorldPtr world, PhysicsStatePtr physics, const RigidBodyContext* rigidbody, double x, double y, double z);
-int32_t dropbear_kcc_kcc_exists_for_entity(WorldPtr world, uint64_t entity, bool* out0);
-int32_t dropbear_kcc_move_character(WorldPtr world, PhysicsStatePtr physics_state, uint64_t entity, const NVector3* translation, double delta_time);
-int32_t dropbear_kcc_set_rotation(WorldPtr world, PhysicsStatePtr physics_state, uint64_t entity, const NQuaternion* rotation);
-int32_t dropbear_kcc_get_hit(WorldPtr world, uint64_t entity, CharacterCollisionArray* out0);
 int32_t dropbear_scripting_load_scene_async(CommandBufferPtr command_buffer, SceneLoaderPtr scene_loader, const char* scene_name, uint64_t* out0);
 int32_t dropbear_scripting_load_scene_async_with_loading(CommandBufferPtr command_buffer, SceneLoaderPtr scene_loader, const char* scene_name, const char* loading_scene, uint64_t* out0);
 int32_t dropbear_scripting_switch_to_scene_immediate(CommandBufferPtr command_buffer, const char* scene_name);
@@ -494,6 +494,16 @@ int32_t dropbear_scripting_get_scene_load_handle_scene_name(SceneLoaderPtr scene
 int32_t dropbear_scripting_switch_to_scene_async(CommandBufferPtr command_buffer, SceneLoaderPtr scene_loader, uint64_t scene_id);
 int32_t dropbear_scripting_get_scene_load_progress(SceneLoaderPtr scene_loader, uint64_t scene_id, Progress* out0);
 int32_t dropbear_scripting_get_scene_load_status(SceneLoaderPtr scene_loader, uint64_t scene_id, uint32_t* out0);
+int32_t dropbear_engine_get_asset(AssetRegistryPtr asset, const char* label, const AssetKind* kind, uint64_t* out0, bool* out0_present);
+int32_t dropbear_asset_model_get_label(AssetRegistryPtr asset, uint64_t model_handle, char** out0);
+int32_t dropbear_asset_model_get_meshes(AssetRegistryPtr asset, uint64_t model_handle, NMeshArray* out0);
+int32_t dropbear_asset_model_get_materials(AssetRegistryPtr asset, uint64_t model_handle, NMaterialArray* out0);
+int32_t dropbear_asset_model_get_skins(AssetRegistryPtr asset, uint64_t model_handle, NSkinArray* out0);
+int32_t dropbear_asset_model_get_animations(AssetRegistryPtr asset, uint64_t model_handle, NAnimationArray* out0);
+int32_t dropbear_asset_model_get_nodes(AssetRegistryPtr asset, uint64_t model_handle, NNodeArray* out0);
+int32_t dropbear_asset_texture_get_label(AssetRegistryPtr asset_manager, uint64_t texture_handle, char** out0, bool* out0_present);
+int32_t dropbear_asset_texture_get_width(AssetRegistryPtr asset_manager, uint64_t texture_handle, uint32_t* out0);
+int32_t dropbear_asset_texture_get_height(AssetRegistryPtr asset_manager, uint64_t texture_handle, uint32_t* out0);
 int32_t dropbear_camera_exists_for_entity(WorldPtr world, uint64_t entity, bool* out0);
 int32_t dropbear_camera_get_eye(WorldPtr world, uint64_t entity, NVector3* out0);
 int32_t dropbear_camera_set_eye(WorldPtr world, uint64_t entity, const NVector3* eye);
@@ -516,6 +526,8 @@ int32_t dropbear_camera_get_speed(WorldPtr world, uint64_t entity, double* out0)
 int32_t dropbear_camera_set_speed(WorldPtr world, uint64_t entity, double speed);
 int32_t dropbear_camera_get_sensitivity(WorldPtr world, uint64_t entity, double* out0);
 int32_t dropbear_camera_set_sensitivity(WorldPtr world, uint64_t entity, double sensitivity);
+int32_t dropbear_engine_get_entity(WorldPtr world, const char* label, uint64_t* out0);
+int32_t dropbear_engine_quit(CommandBufferPtr command_buffer);
 int32_t dropbear_entity_label_exists_for_entity(WorldPtr world, uint64_t entity, bool* out0);
 int32_t dropbear_entity_get_label(WorldPtr world, uint64_t entity, char** out0);
 int32_t dropbear_entity_get_children(WorldPtr world, uint64_t entity, u64Array* out0);
@@ -555,6 +567,9 @@ int32_t dropbear_lighting_get_casts_shadows(WorldPtr world, uint64_t entity, boo
 int32_t dropbear_lighting_set_casts_shadows(WorldPtr world, uint64_t entity, bool casts_shadows);
 int32_t dropbear_lighting_get_depth(WorldPtr world, uint64_t entity, NRange* out0);
 int32_t dropbear_lighting_set_depth(WorldPtr world, uint64_t entity, const NRange* depth);
+int32_t dropbear_mesh_get_texture(WorldPtr world, AssetRegistryPtr asset, uint64_t entity, const char* material_name, uint64_t* out0, bool* out0_present);
+int32_t dropbear_mesh_set_texture_override(WorldPtr world, AssetRegistryPtr asset, uint64_t entity, const char* material_name, uint64_t texture_handle);
+int32_t dropbear_mesh_set_material_tint(WorldPtr world, AssetRegistryPtr asset, GraphicsContextPtr graphics, uint64_t entity, const char* material_name, float r, float g, float b, float a);
 int32_t dropbear_physics_get_gravity(PhysicsStatePtr physics, NVector3* out0);
 int32_t dropbear_physics_set_gravity(PhysicsStatePtr physics, const NVector3* gravity);
 int32_t dropbear_physics_raycast(PhysicsStatePtr physics, const NVector3* origin, const NVector3* dir, double time_of_impact, bool solid, RayHit* out0, bool* out0_present);
@@ -583,20 +598,5 @@ int32_t dropbear_transform_set_local_transform(WorldPtr world, uint64_t entity, 
 int32_t dropbear_transform_get_world_transform(WorldPtr world, uint64_t entity, NTransform* out0);
 int32_t dropbear_transform_set_world_transform(WorldPtr world, uint64_t entity, const NTransform* transform);
 int32_t dropbear_transform_propogate_transform(WorldPtr world, uint64_t entity, NTransform* out0);
-int32_t dropbear_engine_get_entity(WorldPtr world, const char* label, uint64_t* out0);
-int32_t dropbear_engine_quit(CommandBufferPtr command_buffer);
-int32_t dropbear_engine_get_asset(AssetRegistryPtr asset, const char* label, const AssetKind* kind, uint64_t* out0, bool* out0_present);
-int32_t dropbear_asset_model_get_label(AssetRegistryPtr asset, uint64_t model_handle, char** out0);
-int32_t dropbear_asset_model_get_meshes(AssetRegistryPtr asset, uint64_t model_handle, NMeshArray* out0);
-int32_t dropbear_asset_model_get_materials(AssetRegistryPtr asset, uint64_t model_handle, NMaterialArray* out0);
-int32_t dropbear_asset_model_get_skins(AssetRegistryPtr asset, uint64_t model_handle, NSkinArray* out0);
-int32_t dropbear_asset_model_get_animations(AssetRegistryPtr asset, uint64_t model_handle, NAnimationArray* out0);
-int32_t dropbear_asset_model_get_nodes(AssetRegistryPtr asset, uint64_t model_handle, NNodeArray* out0);
-int32_t dropbear_asset_texture_get_label(AssetRegistryPtr asset_manager, uint64_t texture_handle, char** out0, bool* out0_present);
-int32_t dropbear_asset_texture_get_width(AssetRegistryPtr asset_manager, uint64_t texture_handle, uint32_t* out0);
-int32_t dropbear_asset_texture_get_height(AssetRegistryPtr asset_manager, uint64_t texture_handle, uint32_t* out0);
-int32_t dropbear_mesh_get_texture(WorldPtr world, AssetRegistryPtr asset, uint64_t entity, const char* material_name, uint64_t* out0, bool* out0_present);
-int32_t dropbear_mesh_set_texture_override(WorldPtr world, AssetRegistryPtr asset, uint64_t entity, const char* material_name, uint64_t texture_handle);
-int32_t dropbear_mesh_set_material_tint(WorldPtr world, AssetRegistryPtr asset, GraphicsContextPtr graphics, uint64_t entity, const char* material_name, float r, float g, float b, float a);
 
 #endif /* DROPBEAR_H */