kitgit

tirbofish/dropbear · commit

2f6997ca90e16b8944c140cc96e6160d43f33099

Merge pull request #74 from tirbofish/physics-scripting feature: overhaul to scripting and FFI jarvis, run github actions

Unverified

Thribhu K <4tkbytes@pm.me> · 2025-12-31 12:31

view full diff

diff --git a/Cargo.toml b/Cargo.toml
index 9257b36..299d5a4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -80,6 +80,7 @@ egui_ltreeview = { version = "0.6", features = ["doc"] }
 dyn-hash = "1.0"
 semver = { version = "1.0", features = ["serde"] }
 rapier3d = { version = "0.31", features = [ "simd-stable", "serde-serialize" ] }
+cbindgen = { version = "0.29.2" }
 
 [workspace.dependencies.image]
 version = "0.25"
diff --git a/build.gradle.kts b/build.gradle.kts
index 5f5eac2..47daf77 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -2,7 +2,7 @@ plugins {
     alias(libs.plugins.kotlinMultiplatform)
     alias(libs.plugins.kotlinxSerialization)
     `maven-publish`
-    id("org.jetbrains.dokka") version "2.0.0"
+    id("org.jetbrains.dokka") version "2.1.0"
 }
 
 group = "com.dropbear"
@@ -42,9 +42,7 @@ val libPathProvider = provider {
 }
 
 kotlin {
-    jvm {
-        withJava()
-    }
+    jvm { }
 
     val nativeTarget = when {
         isMacOs && isArm64 -> macosArm64("nativeLib")
@@ -73,8 +71,8 @@ kotlin {
                 cinterops {
                     val dropbear by creating {
                         defFile(project.file("src/dropbear.def"))
-                        includeDirs.headerFilterOnly(project.file("headers"))
-                        compilerOpts("-I${project.file("headers").absolutePath}")
+                        includeDirs.headerFilterOnly(project.file("include"))
+                        compilerOpts("-I${project.file("include").absolutePath}")
                     }
                 }
             }
@@ -99,7 +97,7 @@ kotlin {
                 cinterops {
                     val dropbear by creating {
                         defFile(project.file("src/dropbear.def"))
-                        includeDirs.headerFilterOnly(project.file("headers"))
+                        includeDirs.headerFilterOnly(project.file("include"))
                     }
                 }
             }
@@ -109,7 +107,7 @@ kotlin {
     sourceSets {
         commonMain {
             dependencies {
-                api("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0")
+                api("org.jetbrains.kotlinx:kotlinx-datetime:0.7.0")
             }
         }
         nativeMain {
@@ -138,7 +136,7 @@ kotlin {
 }
 
 tasks.register<JavaCompile>("generateJniHeaders") {
-    val outputDir = layout.buildDirectory.dir("generated/jni-headers")
+    val outputDir = layout.buildDirectory.dir("generated/jni-include")
     options.headerOutputDirectory.set(outputDir.get().asFile)
 
     destinationDirectory.set(layout.buildDirectory.dir("classes/java/jni"))
@@ -152,6 +150,23 @@ tasks.register<JavaCompile>("generateJniHeaders") {
     }
 
     dependsOn("compileKotlinJvm")
+
+    doFirst {
+        val javaFiles = source.files
+        if (javaFiles.isEmpty()) {
+            println("WARNING: No Java files found in src/jvmMain/kotlin for JNI header generation")
+        } else {
+            println("Generating JNI include for ${javaFiles.size} Java files:")
+            javaFiles.forEach { println("  - ${it.name}") }
+        }
+    }
+
+    doLast {
+        val headerDir = outputDir.get().asFile
+        val headers = headerDir.listFiles()?.filter { it.extension == "h" } ?: emptyList()
+        println("Generated ${headers.size} JNI include:")
+        headers.forEach { println("  - ${it.name}") }
+    }
 }
 
 publishing {
@@ -179,7 +194,7 @@ publishing {
                 developer {
                     id.set("tirbofish")
                     name.set("tk")
-                    email.set("tirbofish@pm.me")
+                    email.set("4tkbytes@pm.me")
                 }
             }
 
diff --git a/cbindgen.toml b/cbindgen.toml
new file mode 100644
index 0000000..d028744
--- /dev/null
+++ b/cbindgen.toml
@@ -0,0 +1,13 @@
+# cbindgen.toml
+
+language = "C"
+include_guard = "DROPBEAR_H"
+line_length = 100
+tab_width = 4
+
+[export]
+prefix = "dropbear_"
+
+[parse]
+parse_deps = true
+include = ["eucalyptus_core"]
\ No newline at end of file
diff --git a/dropbear-engine/src/entity.rs b/dropbear-engine/src/entity.rs
index 905522f..b47dc21 100644
--- a/dropbear-engine/src/entity.rs
+++ b/dropbear-engine/src/entity.rs
@@ -333,6 +333,26 @@ impl MeshRenderer {
         self.material_handle_raw(&ASSET_REGISTRY, material_name)
     }
 
+    pub fn collect_all_material_handles_raw(
+        &self,
+        registry: &AssetRegistry,
+    ) -> Vec<AssetHandle> {
+        let model = self.model();
+        let model_id = self.model_id();
+
+        model
+            .materials
+            .iter()
+            .filter_map(|material| {
+                registry.material_handle(model_id, &material.name)
+            })
+            .collect()
+    }
+
+    pub fn collect_all_material_handles(&self) -> Vec<AssetHandle> {
+        self.collect_all_material_handles_raw(&ASSET_REGISTRY)
+    }
+
     pub fn material_handle_raw(
         &self,
         registry: &AssetRegistry,
diff --git a/dropbear-gradle-plugin/build.gradle.kts b/dropbear-gradle-plugin/build.gradle.kts
deleted file mode 100644
index 7a511fe..0000000
--- a/dropbear-gradle-plugin/build.gradle.kts
+++ /dev/null
@@ -1,72 +0,0 @@
-import org.gradle.kotlin.dsl.compileOnly
-
-plugins {
-    `kotlin-dsl`
-    `maven-publish`
-    id("com.gradle.plugin-publish") version "2.0.0"
-}
-
-group = "com.dropbear"
-version = "1.0-SNAPSHOT"
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    implementation("de.undercouch:gradle-download-task:5.6.0")
-    compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:${KotlinVersion.CURRENT}")
-}
-
-gradlePlugin {
-    website.set("https://github.com/tirbofish/dropbear")
-    vcsUrl.set("https://github.com/tirbofish/dropbear")
-    plugins {
-
-        create("dropbearGradlePlugin") {
-            id = "dropbear-gradle-plugin"
-            implementationClass = "com.dropbear.gradle.DropbearGradlePlugin"
-            displayName = "dropbear-gradle-plugin"
-            description = "Gradle plugin for dependency management for dropbear-based projects"
-            version = version as String
-        }
-    }
-}
-
-publishing {
-    publications {
-        withType<MavenPublication>().configureEach {
-            pom {
-                name.set("dropbear-gradle-plugin")
-                description.set("Gradle plugin for dropbear dependency management")
-                url.set("https://tirbofish.github.io/dropbear/")
-
-                licenses {
-                    license {
-                        name.set("MIT License")
-                        url.set("https://opensource.org/licenses/MIT")
-                    }
-                }
-                developers {
-                    developer {
-                        id.set("tirbofish")
-                        name.set("tk")
-                        email.set("tirbofish@pm.me")
-                    }
-                }
-                scm {
-                    connection.set("scm:git:git://github.com/tirbofish/dropbear.git")
-                    developerConnection.set("scm:git:ssh://github.com/tirbofish/dropbear.git")
-                    url.set("https://github.com/tirbofish/dropbear")
-                }
-            }
-        }
-    }
-
-    repositories {
-        maven {
-            name = "GitHubPages"
-            url = uri("${layout.buildDirectory}/repo")
-        }
-    }
-}
\ No newline at end of file
diff --git a/dropbear-gradle-plugin/src/main/kotlin/com/dropbear/gradle/DropbearGradlePlugin.kt b/dropbear-gradle-plugin/src/main/kotlin/com/dropbear/gradle/DropbearGradlePlugin.kt
deleted file mode 100644
index 5d0c24c..0000000
--- a/dropbear-gradle-plugin/src/main/kotlin/com/dropbear/gradle/DropbearGradlePlugin.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.dropbear.gradle
-
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-
-class DropbearGradlePlugin: Plugin<Project> {
-    override fun apply(target: Project) {
-        TODO("Not yet implemented")
-    }
-}
\ No newline at end of file
diff --git a/dropbear-macro/src/lib.rs b/dropbear-macro/src/lib.rs
index 3f4852e..75f2f72 100644
--- a/dropbear-macro/src/lib.rs
+++ b/dropbear-macro/src/lib.rs
@@ -1,6 +1,6 @@
 use proc_macro::TokenStream;
 use quote::quote;
-use syn::{DeriveInput, parse_macro_input};
+use syn::{DeriveInput, parse_macro_input, ItemMod, Item, Type, parse_quote, ItemFn, FnArg, ReturnType, PathArguments, GenericArgument};
 
 /// A `derive` macro that converts a struct to a usable [SerializableComponent].
 ///
@@ -46,3 +46,140 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
 
     TokenStream::from(expanded)
 }
+
+/// Converts a module's functions into C API compatible functions.
+/// 
+/// Each function must require a return type of `DropbearNativeResult<T>`. If the return type
+/// is not `DropbearNativeResult<T>`, it is recommended that you move it to another module (such as `super::shared`). 
+/// 
+/// A function like that of:
+/// ```
+/// pub fn dropbear_mesh_renderer_exists_for_entity(
+///      world_ptr: *mut hecs::World,
+///      entity_id: u64,
+///  ) -> DropbearNativeResult<bool> {...}
+/// ``` 
+/// will get converted to something like:
+/// ```
+/// pub unsafe extern "C" fn dropbear_mesh_renderer_exists_for_entity(world_ptr: WorldPtr, entity_id: u64, out_result: *mut bool) -> i32 {...}
+/// ```
+#[proc_macro_attribute]
+pub fn impl_c_api(_attr: TokenStream, item: TokenStream) -> TokenStream {
+    let mut module = parse_macro_input!(item as ItemMod);
+
+    let mut new_content = Vec::new();
+
+    if let Some((brace, content)) = module.content {
+        for item in content {
+            match item {
+                Item::Fn(func) => {
+                    new_content.push(Item::Fn(transform_function(func)));
+                }
+                _ => new_content.push(item),
+            }
+        }
+        module.content = Some((brace, new_content));
+    }
+
+    TokenStream::from(quote! { #module })
+}
+
+fn transform_function(mut func: ItemFn) -> ItemFn {
+    let inputs = func.sig.inputs.clone();
+    let output = func.sig.output.clone();
+    let block = func.block;
+
+    let inner_type = extract_inner_type(&output);
+    let is_void = is_unit_type(&inner_type);
+
+    let mut new_inputs = inputs.clone();
+
+    if !is_void {
+        let out_ptr_type: Type = parse_quote! { *mut #inner_type };
+
+        new_inputs.push(FnArg::Typed(syn::PatType {
+            attrs: vec![],
+            pat: Box::new(parse_quote! { out_result }),
+            colon_token: Default::default(),
+            ty: Box::new(out_ptr_type),
+        }));
+    }
+    
+    let pointer_check = if !is_void {
+        quote! {
+            if out_result.is_null() {
+                return crate::scripting::native::DropbearNativeError::NullPointer as i32;
+            }
+        }
+    } else {
+        quote! {}
+    };
+
+    let success_handling = if !is_void {
+        quote! {
+            unsafe { *out_result = val; }
+            crate::scripting::native::DropbearNativeError::Success as i32
+        }
+    } else {
+        quote! {
+            crate::scripting::native::DropbearNativeError::Success as i32
+        }
+    };
+
+    let new_body = quote! {
+        {
+            #pointer_check
+            
+            let logic = || #output {
+                #block
+            };
+            
+            match logic() {
+                DropbearNativeResult::Ok(val) => {
+                    #success_handling
+                }
+                DropbearNativeResult::Err(e) => {
+                    e as i32
+                }
+            }
+        }
+    };
+
+    func.sig.inputs = new_inputs;
+    func.sig.output = parse_quote! { -> i32 };
+    func.sig.abi = Some(parse_quote! { extern "C" });
+    func.sig.unsafety = Some(parse_quote! { unsafe });
+
+    func.attrs.push(parse_quote! { #[unsafe(no_mangle)] });
+
+    func.block = Box::new(syn::parse2(new_body).expect("Failed to parse new body"));
+
+    func
+}
+
+/// Helper to dig into Result<T, E> and get T
+fn extract_inner_type(output: &ReturnType) -> Type {
+    match output {
+        ReturnType::Type(_, ty) => {
+            if let Type::Path(type_path) = &**ty {
+                if let Some(segment) = type_path.path.segments.last() {
+                    if let PathArguments::AngleBracketed(args) = &segment.arguments {
+                        if let Some(GenericArgument::Type(inner)) = args.args.first() {
+                            return inner.clone();
+                        }
+                    }
+                }
+            }
+            parse_quote! { () }
+        }
+        ReturnType::Default => parse_quote! { () },
+    }
+}
+
+/// Helper to check if type is ()
+fn is_unit_type(ty: &Type) -> bool {
+    if let Type::Tuple(tuple) = ty {
+        return tuple.elems.is_empty();
+    }
+    false
+}
diff --git a/eucalyptus-core/Cargo.toml b/eucalyptus-core/Cargo.toml
index 77ce714..47b3bcd 100644
--- a/eucalyptus-core/Cargo.toml
+++ b/eucalyptus-core/Cargo.toml
@@ -56,5 +56,6 @@ jvm = []
 jvm_debug = ["jvm"]
 
 [build-dependencies]
-anyhow = "1.0"
-app_dirs2 = "2.5"
+anyhow.workspace = true
+app_dirs2.workspace = true
+cbindgen.workspace = true
diff --git a/eucalyptus-core/README.md b/eucalyptus-core/README.md
index 93da694..b73f4cd 100644
--- a/eucalyptus-core/README.md
+++ b/eucalyptus-core/README.md
@@ -2,13 +2,39 @@
 
 The core libraries of the eucalyptus editor. Great for embedding into `redback-runtime` and `eucalyptus-editor` as one big change instead of a bunch of features.
 
-This is a library, so if tools are wished to be made, this is the perfect library for you.
+It also produces a shared library for Kotlin/Native and the JVM.
 
-it also produces a shared library for Kotlin/Native and the JVM :)
+Kotlin Multiplatform is prioritised and fully supported on this library, however that is not to say that you (yes you 
+the reader) could implement this for another language (as really it just reads from a .dll or a .jar file). 
 
-## Features
+[//]: # (## Export requirements)
 
-- `editor` - Enables editor only features that the redback-runtime would not be able to access
-- `jvm` - Enables the JVM as a ScriptTarget and running the Java Virtual Machine (not possible with non-desktop targets)
-- `jvm_debug` - Enables debugging of the JVM through the java debugger. Can pose a risk to tampering, so is disabled by default unless
-    want to be enabled by developer or enabled by default by the `editor` feature. 
\ No newline at end of file
+[//]: # ()
+[//]: # (To have your own scripting module to be able to be run, you will have to implement the following exports into)
+
+[//]: # (your own app:)
+
+[//]: # ()
+[//]: # (- `dropbear_init`)
+
+[//]: # (  - Args: `pointerContext: DropbearContext*`)
+
+[//]: # (  - Returns: `int`)
+
+[//]: # (- `dropbear_load_tagged`)
+
+[//]: # (  - Args: `tag: *mut c_char`)
+
+[//]: # (  - Returns: `int`)
+
+[//]: # (- `dropbear_update_all`)
+
+[//]: # (  - Args: `dt: float`)
+
+[//]: # (  - Returns: `int`)
+
+[//]: # (- `dropbear_update_tagged`)
+
+[//]: # (  - Args: `tag: *mut c_char, `)
+
+[//]: # (  - )
\ No newline at end of file
diff --git a/eucalyptus-core/build.rs b/eucalyptus-core/build.rs
index 2e3421e..9d23613 100644
--- a/eucalyptus-core/build.rs
+++ b/eucalyptus-core/build.rs
@@ -1,4 +1,5 @@
 fn main() -> anyhow::Result<()> {
+
     // fuck you windows :(
     #[cfg(target_os = "windows")]
     {
@@ -7,5 +8,6 @@ fn main() -> anyhow::Result<()> {
     }
 
     println!("cargo:rerun-if-changed=build.rs");
+    println!("cargo:rerun-if-changed=src/lib.rs");
     Ok(())
 }
diff --git a/eucalyptus-core/src/asset.rs b/eucalyptus-core/src/asset.rs
new file mode 100644
index 0000000..fa00e93
--- /dev/null
+++ b/eucalyptus-core/src/asset.rs
@@ -0,0 +1,93 @@
+pub mod texture;
+
+pub mod shared {
+    use dropbear_engine::asset::{AssetHandle, AssetRegistry};
+    use crate::scripting::result::DropbearNativeResult;
+
+    pub fn is_model_handle(
+        registry: &AssetRegistry,
+        handle: u64,
+    ) -> DropbearNativeResult<bool> {
+        let handle = AssetHandle::new(handle);
+        let result = registry.is_model(handle);
+        Ok(result)
+    }
+
+    pub fn is_texture_handle(
+        registry: &AssetRegistry,
+        handle: u64,
+    ) -> DropbearNativeResult<bool> {
+        let handle = AssetHandle::new(handle);
+        let result = registry.is_material(handle);
+        Ok(result)
+    }
+}
+
+pub mod jni {
+    #![allow(non_snake_case)]
+    use jni::sys::{jboolean, jlong};
+    use jni::objects::JClass;
+    use dropbear_engine::asset::AssetRegistry;
+    use jni::JNIEnv;
+    use crate::asset::shared::{is_model_handle, is_texture_handle};
+    use crate::convert_ptr;
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_asset_AssetHandleNative_isModelHandle(
+        _env: JNIEnv,
+        _class: JClass,
+        asset_registry_ptr: jlong,
+        handle: jlong,
+    ) -> jboolean {
+        let asset = convert_ptr!(asset_registry_ptr => AssetRegistry);
+        let result = is_model_handle(asset, handle as u64);
+        match result {
+            Ok(val) => val as jboolean,
+            Err(e) => {
+                crate::ffi_error_return!("[ERROR] {}", e)
+            }
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_asset_AssetHandleNative_isTextureHandle(
+        _env: JNIEnv,
+        _class: JClass,
+        asset_registry_ptr: jlong,
+        handle: jlong,
+    ) -> jboolean {
+        let asset = convert_ptr!(asset_registry_ptr => AssetRegistry);
+        let result = is_texture_handle(asset, handle as u64);
+        match result {
+            Ok(val) => val as jboolean,
+            Err(e) => {
+                crate::ffi_error_return!("[ERROR] {}", e)
+            }
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+    use dropbear_engine::asset::AssetRegistry;
+    use crate::asset::shared::{is_model_handle, is_texture_handle};
+    use crate::convert_ptr;
+    use crate::ptr::AssetRegistryPtr;
+    use crate::scripting::result::DropbearNativeResult;
+
+    pub fn dropbear_is_model_handle(
+        asset_registry_ptr: AssetRegistryPtr,
+        handle: u64,
+    ) -> DropbearNativeResult<bool> {
+        let asset = convert_ptr!(asset_registry_ptr => AssetRegistry);
+        is_model_handle(asset, handle)
+    }
+
+    pub fn dropbear_is_texture_handle(
+        asset_registry_ptr: AssetRegistryPtr,
+        handle: u64,
+    ) -> DropbearNativeResult<bool> {
+        let asset = convert_ptr!(asset_registry_ptr => AssetRegistry);
+        is_texture_handle(asset, handle)
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/asset/texture.rs b/eucalyptus-core/src/asset/texture.rs
new file mode 100644
index 0000000..0e30f30
--- /dev/null
+++ b/eucalyptus-core/src/asset/texture.rs
@@ -0,0 +1,83 @@
+pub mod shared {
+    use dropbear_engine::asset::{AssetHandle, AssetRegistry};
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+
+    pub fn get_texture_name(
+        asset_registry: &AssetRegistry,
+        handle: u64,
+    ) -> DropbearNativeResult<String> {
+        let texture = asset_registry
+            .get_material(AssetHandle::new(handle))
+            .ok_or_else(|| DropbearNativeError::NoSuchHandle)?;
+
+        Ok(texture.name.clone())
+    }
+}
+
+pub mod jni {
+    #![allow(non_snake_case)]
+    use jni::JNIEnv;
+    use jni::sys::{jlong, jstring};
+    use dropbear_engine::asset::AssetRegistry;
+    use jni::objects::JClass;
+    use crate::asset::texture::shared::get_texture_name;
+    use crate::scripting::native::DropbearNativeError;
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_asset_TextureHandleNative_getTextureName(
+        env: JNIEnv,
+        _class: JClass,
+        asset_registry_ptr: jlong,
+        handle: jlong,
+    ) -> jstring {
+        let asset_registry = crate::convert_ptr!(asset_registry_ptr => AssetRegistry);
+        let result = get_texture_name(asset_registry, handle as u64);
+        match result {
+            Ok(name) => {
+                let output = env.new_string(name).map_err(|e| DropbearNativeError::JNIFailedToCreateObject);
+                match output {
+                    Ok(jstr) => jstr.into_raw(),
+                    Err(e) => {
+                        crate::ffi_error_return!("[ERROR] Failed to create Java string: {}", e)
+                    }
+                }
+            }
+            Err(e) => {
+                crate::ffi_error_return!("[ERROR] {}", e)
+            }
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+    use std::ffi::CString;
+    use crate::ptr::AssetRegistryPtr;
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+    use std::ffi::c_char;
+    use dropbear_engine::asset::AssetRegistry;
+    use crate::asset::texture::shared::get_texture_name;
+
+    pub fn dropbear_get_texture_name(
+        asset_registry_ptr: AssetRegistryPtr,
+        handle: u64,
+    ) -> DropbearNativeResult<*mut c_char> {
+        let asset_registry = crate::convert_ptr!(asset_registry_ptr => AssetRegistry);
+
+        let result = get_texture_name(asset_registry, handle)
+            .map(|name| {
+                match CString::new(name) {
+                    Ok(c_str) => {
+                        Ok(c_str.into_raw())
+                    },
+                    Err(_) => {
+                        Err(DropbearNativeError::CStringError)
+                    }
+                }
+            })?;
+        
+        result
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/camera.rs b/eucalyptus-core/src/camera.rs
index df3fff2..94c71d5 100644
--- a/eucalyptus-core/src/camera.rs
+++ b/eucalyptus-core/src/camera.rs
@@ -97,12 +97,6 @@ impl DebugCamera {
     }
 }
 
-// #[derive(Debug, Default, Clone)]
-// pub struct CameraFollowTarget {
-//     pub follow_target: String,
-//     pub offset: DVec3,
-// }
-
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
 pub enum CameraType {
     Normal,
@@ -122,3 +116,739 @@ pub enum CameraAction {
     ClearPlayerTarget,
     SetCurrentPositionAsOffset(hecs::Entity),
 }
+
+pub mod shared {
+    use dropbear_engine::camera::Camera;
+
+    pub fn camera_exists_for_entity(world: &hecs::World, entity: hecs::Entity) -> bool {
+        world.get::<&Camera>(entity).is_ok()
+    }
+}
+
+pub mod jni {
+    #![allow(non_snake_case)]
+
+    use glam::DVec3;
+    use jni::JNIEnv;
+    use jni::objects::{JClass, JObject};
+    use jni::sys::{jboolean, jdouble, jlong, jobject};
+    use dropbear_engine::camera::Camera;
+    use crate::convert_jlong_to_entity;
+    use crate::scripting::jni::utils::{FromJObject, ToJObject};
+    use crate::types::Vector3;
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_cameraExistsForEntity(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jboolean {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if world.get::<&Camera>(entity).is_ok() {
+            true.into()
+        } else {
+            false.into()
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_getCameraEye(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(camera) = world.get::<&Camera>(entity) {
+            let eye: Vector3 = Vector3::from(camera.eye);
+            return match eye.to_jobject(&mut env) {
+                Ok(val) => val.into_raw(),
+                Err(_) => std::ptr::null_mut()
+            };
+        } else {
+            let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Entity {} missing Camera", entity_id));
+            std::ptr::null_mut()
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_setCameraEye(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        eye_obj: JObject,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        let new_eye = match Vector3::from_jobject(&mut env, &eye_obj) {
+            Ok(v) => v,
+            Err(e) => {
+                let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Invalid Vector3d: {:?}", e));
+                return;
+            }
+        };
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.eye = DVec3::from(new_eye);
+        } else {
+            let _ = env.throw_new("java/lang/IllegalArgumentException", "Entity missing Camera component");
+        }
+    }
+
+    // TARGET
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_getCameraTarget(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(camera) = world.get::<&Camera>(entity) {
+            let target: Vector3 = Vector3::from(camera.target);
+            return match target.to_jobject(&mut env) {
+                Ok(val) => val.into_raw(),
+                Err(_) => std::ptr::null_mut()
+            };
+        } else {
+            let _ = env.throw_new("java/lang/IllegalArgumentException", "Entity missing Camera component");
+            std::ptr::null_mut()
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_setCameraTarget(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        target_obj: JObject,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        let new_target = match Vector3::from_jobject(&mut env, &target_obj) {
+            Ok(v) => v,
+            Err(_) => return,
+        };
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.target = DVec3::from(new_target);
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_getCameraUp(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(camera) = world.get::<&Camera>(entity) {
+            let up = Vector3::from(camera.up);
+            return match up.to_jobject(&mut env) {
+                Ok(val) => val.into_raw(),
+                Err(_) => std::ptr::null_mut()
+            };
+        } else {
+            let _ = env.throw_new("java/lang/IllegalArgumentException", "Entity missing Camera component");
+            std::ptr::null_mut()
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_setCameraUp(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        up_obj: JObject,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        let new_up = match Vector3::from_jobject(&mut env, &up_obj) {
+            Ok(v) => v,
+            Err(_) => return,
+        };
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.up = DVec3::from(new_up);
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_getCameraAspect(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jdouble {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(camera) = world.get::<&Camera>(entity) {
+            camera.aspect
+        } else {
+            0.0
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_getCameraFovY(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jdouble {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(camera) = world.get::<&Camera>(entity) {
+            camera.settings.fov_y
+        } else {
+            0.0
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_setCameraFovY(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        value: jdouble,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.settings.fov_y = value;
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_getCameraZNear(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jdouble {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(camera) = world.get::<&Camera>(entity) {
+            camera.znear
+        } else {
+            0.0
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_setCameraZNear(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        value: jdouble,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.znear = value;
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_getCameraZFar(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jdouble {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(camera) = world.get::<&Camera>(entity) {
+            camera.zfar
+        } else {
+            0.0
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_setCameraZFar(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        value: jdouble,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.zfar = value;
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_getCameraYaw(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jdouble {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(camera) = world.get::<&Camera>(entity) {
+            camera.yaw
+        } else {
+            0.0
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_setCameraYaw(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        value: jdouble,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.yaw = value;
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_getCameraPitch(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jdouble {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(camera) = world.get::<&Camera>(entity) {
+            camera.pitch
+        } else {
+            0.0
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_setCameraPitch(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        value: jdouble,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.pitch = value;
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_getCameraSpeed(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jdouble {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(camera) = world.get::<&Camera>(entity) {
+            camera.settings.speed
+        } else {
+            0.0
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_setCameraSpeed(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        value: jdouble,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.settings.speed = value;
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_getCameraSensitivity(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jdouble {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(camera) = world.get::<&Camera>(entity) {
+            camera.settings.sensitivity
+        } else {
+            0.0
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_setCameraSensitivity(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        value: jdouble,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.settings.sensitivity = value;
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+    use crate::ptr::WorldPtr;
+    use crate::convert_ptr;
+    use crate::scripting::native::{DropbearNativeError};
+    use hecs::Entity;
+    use glam::DVec3;
+    use dropbear_engine::camera::Camera;
+    use crate::scripting::result::DropbearNativeResult;
+    use crate::types::Vector3;
+
+    pub fn dropbear_camera_exists_for_entity(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<bool> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if world.get::<&Camera>(entity).is_ok() {
+            DropbearNativeResult::Ok(true)
+        } else {
+            DropbearNativeResult::Ok(false)
+        }
+    }
+
+    pub fn dropbear_get_camera_eye(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<Vector3> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(camera) = world.get::<&Camera>(entity) {
+            DropbearNativeResult::Ok(Vector3::from(camera.eye))
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_camera_eye(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: Vector3,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.eye = DVec3::from(value);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_get_camera_target(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<Vector3> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(camera) = world.get::<&Camera>(entity) {
+            DropbearNativeResult::Ok(Vector3::from(camera.target))
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_camera_target(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: Vector3,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.target = DVec3::from(value);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_get_camera_up(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<Vector3> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(camera) = world.get::<&Camera>(entity) {
+            DropbearNativeResult::Ok(Vector3::from(camera.up))
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_camera_up(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: Vector3,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.up = DVec3::from(value);
+            Ok(())
+        } else {
+            Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_get_camera_aspect(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<f64> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(camera) = world.get::<&Camera>(entity) {
+            DropbearNativeResult::Ok(camera.aspect)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_get_camera_fov_y(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<f64> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(camera) = world.get::<&Camera>(entity) {
+            DropbearNativeResult::Ok(camera.settings.fov_y)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_camera_fov_y(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: f64,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.settings.fov_y = value;
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_get_camera_znear(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<f64> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(camera) = world.get::<&Camera>(entity) {
+            DropbearNativeResult::Ok(camera.znear)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_camera_znear(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: f64,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.znear = value;
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_get_camera_zfar(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<f64> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(camera) = world.get::<&Camera>(entity) {
+            DropbearNativeResult::Ok(camera.zfar)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_camera_zfar(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: f64,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.zfar = value;
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_get_camera_yaw(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<f64> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(camera) = world.get::<&Camera>(entity) {
+            DropbearNativeResult::Ok(camera.yaw)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_camera_yaw(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: f64,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.yaw = value;
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_get_camera_pitch(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<f64> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(camera) = world.get::<&Camera>(entity) {
+            DropbearNativeResult::Ok(camera.pitch)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_camera_pitch(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: f64,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.pitch = value;
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_get_camera_speed(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<f64> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(camera) = world.get::<&Camera>(entity) {
+            DropbearNativeResult::Ok(camera.settings.speed)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_camera_speed(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: f64,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.settings.speed = value;
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_get_camera_sensitivity(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<f64> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(camera) = world.get::<&Camera>(entity) {
+            DropbearNativeResult::Ok(camera.settings.sensitivity)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_camera_sensitivity(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: f64,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.settings.sensitivity = value;
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+}
diff --git a/eucalyptus-core/src/engine.rs b/eucalyptus-core/src/engine.rs
new file mode 100644
index 0000000..d946533
--- /dev/null
+++ b/eucalyptus-core/src/engine.rs
@@ -0,0 +1,165 @@
+pub mod shared {
+    use crate::command::CommandBuffer;
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+    use crate::states::Label;
+    use dropbear_engine::asset::AssetRegistry;
+    use dropbear_engine::utils::ResourceReference;
+    use hecs::World;
+    use std::ffi::CStr;
+    use std::os::raw::c_char;
+
+    pub fn get_entity(world: &World, label: &str) -> DropbearNativeResult<u64> {
+        for (id, entity_label) in world.query::<&Label>().iter() {
+            if entity_label.as_str() == label {
+                return Ok(id.to_bits().get());
+            }
+        }
+        Err(DropbearNativeError::EntityNotFound)
+    }
+
+    pub fn get_asset(registry: &AssetRegistry, uri: &str) -> DropbearNativeResult<u64> {
+        let reference = ResourceReference::from_euca_uri(uri)
+            .map_err(|_| DropbearNativeError::InvalidURI)?;
+
+        match registry.get_handle_from_reference(&reference) {
+            Some(handle) => Ok(handle.raw()),
+            None => Err(DropbearNativeError::AssetNotFound),
+        }
+    }
+
+    pub fn quit(command_buffer: &crossbeam_channel::Sender<CommandBuffer>) -> DropbearNativeResult<()> {
+        command_buffer.send(CommandBuffer::Quit)
+            .map_err(|_| DropbearNativeError::SendError)
+    }
+
+    pub unsafe fn read_str(ptr: *const c_char) -> DropbearNativeResult<String> {
+        if ptr.is_null() { return Err(DropbearNativeError::NullPointer); }
+        unsafe { CStr::from_ptr(ptr) }.to_str()
+            .map(|s| s.to_string())
+            .map_err(|_| DropbearNativeError::InvalidUTF8)
+    }
+}
+
+pub mod jni {
+    #![allow(non_snake_case)]
+    use hecs::World;
+    use jni::objects::{JClass, JString};
+    use jni::sys::{jlong, jobject};
+    use jni::JNIEnv;
+    use dropbear_engine::asset::AssetRegistry;
+    use crate::command::CommandBuffer;
+    use crate::return_boxed;
+
+    macro_rules! return_nullable_long {
+        ($env:expr, $res:expr) => {
+            match $res {
+                Ok(val) => {
+                    let cls = $env.find_class("java/lang/Long").unwrap();
+                    let method_id = $env.get_static_method_id(cls, "valueOf", "(J)Ljava/lang/Long;").unwrap();
+                    $env.call_static_method_unchecked(cls, method_id, jni::signature::JavaType::Object, &[JValue::Long(val as i64)])
+                        .unwrap().l().unwrap().into_raw()
+                },
+                Err(_) => std::ptr::null_mut(),
+            }
+        };
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_DropbearEngineNative_getEntity(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        label: JString,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_handle => World);
+        let label_str = crate::convert_jstring!(env, label);
+
+        let value_opt = super::shared::get_entity(&world, &label_str)
+            .ok()
+            .map(|id| id as i64);
+
+        return_boxed!(
+            &mut env,
+            value_opt,
+            "(J)Ljava/lang/Long;",
+            "java/lang/Long"
+        )
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_DropbearEngineNative_getAsset(
+        mut env: JNIEnv,
+        _class: JClass,
+        asset_handle: jlong,
+        label: JString,
+    ) -> jobject {
+        let asset = crate::convert_ptr!(asset_handle => AssetRegistry);
+        let label_str = crate::convert_jstring!(env, label);
+
+        let value_opt = super::shared::get_asset(&asset, &label_str)
+            .ok()
+            .map(|id| id as i64);
+
+        return_boxed!(
+            &mut env,
+            value_opt,
+            "(J)Ljava/lang/Long;",
+            "java/lang/Long"
+        )
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_DropbearEngineNative_quit(
+        mut env: JNIEnv,
+        _class: JClass,
+        command_buffer_ptr: jlong,
+    ) {
+        let sender = crate::convert_ptr!(command_buffer_ptr => crossbeam_channel::Sender<CommandBuffer>);
+
+        if let Err(e) = super::shared::quit(sender) {
+            let _ = env.throw_new(
+                "java/lang/RuntimeException",
+                format!("Failed to send quit command: {:?}", e)
+            );
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+    use crate::command::CommandBuffer;
+    use crate::convert_ptr;
+    use crate::engine::shared::read_str;
+    use crate::ptr::{AssetRegistryPtr, CommandBufferPtr, WorldPtr};
+    use crate::scripting::result::DropbearNativeResult;
+    use dropbear_engine::asset::AssetRegistry;
+    use hecs::World;
+    use std::os::raw::c_char;
+
+    pub fn dropbear_get_entity(
+        world_ptr: WorldPtr,
+        label: *const c_char,
+    ) -> DropbearNativeResult<u64> {
+        let world = convert_ptr!(world_ptr => World);
+        let label_str = unsafe { read_str(label)? };
+
+        super::shared::get_entity(world, &label_str)
+    }
+
+    pub fn dropbear_get_asset(
+        asset_ptr: AssetRegistryPtr,
+        uri: *const c_char,
+    ) -> DropbearNativeResult<u64> {
+        let asset_registry = convert_ptr!(asset_ptr => AssetRegistry);
+        let uri_str = unsafe { read_str(uri)? };
+        super::shared::get_asset(&asset_registry, &uri_str)
+    }
+
+    pub fn dropbear_quit(
+        command_ptr: CommandBufferPtr,
+    ) -> DropbearNativeResult<()> {
+        let sender = convert_ptr!(command_ptr => crossbeam_channel::Sender<CommandBuffer>);
+        super::shared::quit(sender)
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/entity.rs b/eucalyptus-core/src/entity.rs
new file mode 100644
index 0000000..bb9a87e
--- /dev/null
+++ b/eucalyptus-core/src/entity.rs
@@ -0,0 +1,125 @@
+pub mod jni {
+    #![allow(non_snake_case)]
+    use crate::hierarchy::{Children, Parent};
+    use crate::states::Label;
+    use crate::{convert_jlong_to_entity, convert_jstring, convert_ptr};
+    use hecs::World;
+    use jni::objects::{JClass, JString, JValue};
+    use jni::sys::{jlong, jlongArray, jobject, jsize, jstring};
+    use jni::JNIEnv;
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_EntityRefNative_getEntityLabel(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jstring {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+
+        let Ok(label) = world.get::<&Label>(entity) else {
+            let _ = env.throw_new("java/lang/InvalidArgumentException", "Unable to locate label entity");
+            return std::ptr::null_mut();
+        };
+
+        let label = label.as_str();
+
+        let Ok(string) = env.new_string(label) else {
+            let _ = env.throw_new("java/lang/OutOfMemoryException", "Unable to create new string");
+            return std::ptr::null_mut();
+        };
+
+        string.into_raw()
+    }
+
+    pub fn Java_com_dropbear_EntityRefNative_getChildren(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jlongArray {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+
+        if let Ok(mut q) = world.query_one::<&Children>(entity)
+            && let Some(children) = q.get()
+        {
+            let entity_bytes = children.children().iter().map(|c| c.to_bits().get() as i64).collect::<Vec<_>>();
+
+            let Ok(jarray) = env.new_long_array(entity_bytes.len() as jsize) else {
+                let _ = env.throw_new("java/lang/OutOfMemoryException", "Unable to create new long array");
+                return std::ptr::null_mut();
+            };
+
+            if let Err(e) = env.set_long_array_region(&jarray, 0, entity_bytes.as_slice()) {
+                let _ = env.throw_new("java/lang/RuntimeException", format!("Unable to populate long array: {}", e));
+                return std::ptr::null_mut()
+            }
+
+            jarray.into_raw()
+        } else {
+            // could be that the entity just doesn't have any children.
+            std::ptr::null_mut()
+        }
+    }
+
+    pub fn Java_com_dropbear_EntityRefNative_getChildByLabel(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        label: JString,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let target = convert_jstring!(env, label);
+
+        if let Ok(mut q) = world.query_one::<&Children>(entity)
+            && let Some(children) = q.get()
+        {
+            for child in children.children() {
+                if let Ok(label) = world.get::<&Label>(entity) {
+                    if label.as_str() == target {
+                        let found = child.clone();
+
+                        return crate::return_boxed!(&mut env, Some(JValue::Long(found.to_bits().get() as i64)), "(J)Ljava/lang/Long", "java/lang/Long");
+                    }
+                } else {
+                    // skip if error or no entity
+                    continue;
+                }
+            }
+            std::ptr::null_mut()
+        } else {
+            // could be that the entity just doesn't have any children.
+            std::ptr::null_mut()
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_EntityRefNative_getParent(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jobject {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = convert_jlong_to_entity!(entity_id);
+
+        if let Ok(mut q) = world.query_one::<&Parent>(entity) {
+            if let Some(parent) = q.get() {
+                crate::return_boxed!(&mut env, Some(JValue::Long(parent.parent().to_bits().get() as jlong)), "(J)Ljava/lang/Long", "java/lang/Long")
+            } else {
+                std::ptr::null_mut()
+            }
+        } else {
+            crate::ffi_error_return!("No entity exists")
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/input.rs b/eucalyptus-core/src/input.rs
index a66f719..a34adac 100644
--- a/eucalyptus-core/src/input.rs
+++ b/eucalyptus-core/src/input.rs
@@ -1,13 +1,23 @@
-//! Input management and input state. 
+//! Input management and input state.
+
+pub mod gamepad;
+
 use dropbear_engine::gilrs::{Button, GamepadId};
 use std::sync::Arc;
 use std::{
     collections::{HashMap, HashSet},
     time::{Duration, Instant},
 };
+use glam::Vec2;
 use winit::window::Window;
 use winit::{event::MouseButton, keyboard::KeyCode};
-use crate::scripting::native::exports::dropbear_input::Gamepad;
+
+#[derive(Clone, Debug)]
+pub struct Gamepad {
+    id: i32,
+    left_stick_pos: Vec2,
+    right_stick_pos: Vec2,
+}
 
 /// Shows the information about the input at that current time. 
 #[derive(Clone, Debug)]
@@ -97,3 +107,349 @@ impl InputState {
         self.connected_gamepads.contains(&gamepad_id)
     }
 }
+
+pub mod shared {
+    use crossbeam_channel::Sender;
+    use crate::command::{CommandBuffer, WindowCommand};
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+    use crate::types::Vector2;
+    use super::*;
+
+    pub fn map_ordinal_to_mouse_button(ordinal: i32) -> Option<MouseButton> {
+        match ordinal {
+            0 => Some(MouseButton::Left),
+            1 => Some(MouseButton::Right),
+            2 => Some(MouseButton::Middle),
+            3 => Some(MouseButton::Back),
+            4 => Some(MouseButton::Forward),
+            ordinal if ordinal >= 0 => Some(MouseButton::Other(ordinal as u16)),
+            _ => None,
+        }
+    }
+
+    pub fn is_key_pressed(input: &InputState, key_ordinal: i32) -> bool {
+        if let Some(key) = crate::utils::keycode_from_ordinal(key_ordinal) {
+            input.is_key_pressed(key)
+        } else {
+            false
+        }
+    }
+
+    pub fn is_mouse_button_pressed(input: &InputState, btn_ordinal: i32) -> bool {
+        if let Some(btn) = map_ordinal_to_mouse_button(btn_ordinal) {
+            input.mouse_button.contains(&btn)
+        } else {
+            false
+        }
+    }
+
+    pub fn get_mouse_position(input: &InputState) -> Vector2 {
+        Vector2 {
+            x: input.mouse_pos.0,
+            y: input.mouse_pos.1,
+        }
+    }
+
+    pub fn get_mouse_delta(input: &InputState) -> Vector2 {
+        input.mouse_delta.map(Vector2::from).unwrap_or(Vector2 { x: 0.0, y: 0.0 })
+    }
+
+    pub fn get_last_mouse_pos(input: &InputState) -> Vector2 {
+        input.last_mouse_pos.map(Vector2::from).unwrap_or(Vector2 { x: 0.0, y: 0.0 })
+    }
+
+    pub fn set_cursor_locked(
+        input: &mut InputState,
+        sender: &Sender<CommandBuffer>,
+        locked: bool
+    ) -> DropbearNativeResult<()> {
+        input.is_cursor_locked = locked;
+
+        sender.send(CommandBuffer::WindowCommand(WindowCommand::WindowGrab(locked)))
+            .map_err(|_| DropbearNativeError::SendError)?;
+
+        Ok(())
+    }
+
+    pub fn set_cursor_hidden(
+        input: &mut InputState,
+        sender: &Sender<CommandBuffer>,
+        hidden: bool
+    ) -> DropbearNativeResult<()> {
+        input.is_cursor_hidden = hidden;
+        sender.send(CommandBuffer::WindowCommand(WindowCommand::HideCursor(hidden)))
+            .map_err(|_| DropbearNativeError::SendError)?;
+        Ok(())
+    }
+
+    pub fn get_connected_gamepads(input: &InputState) -> Vec<u64> {
+        input.connected_gamepads.iter()
+            .map(|id| Into::<usize>::into(*id) as u64)
+            .collect()
+    }
+}
+
+pub mod jni {
+    #![allow(non_snake_case)]
+    use jni::JNIEnv;
+    use jni::objects::{JClass, JObject};
+    use jni::sys::{jboolean, jint, jlong, jlongArray, jobject};
+    use crate::input::InputState;
+    use crate::command::CommandBuffer;
+    use crate::scripting::jni::utils::ToJObject;
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_InputStateNative_printInputState(
+        _env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+    ) {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+        println!("Input State: {:?}", input);
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_InputStateNative_isKeyPressed(
+        _env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+        key_code: jint,
+    ) -> jboolean {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+        if super::shared::is_key_pressed(&input, key_code) { 1 } else { 0 }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_InputStateNative_getMousePosition(
+        mut env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+    ) -> jobject {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+        let vec = super::shared::get_mouse_position(&input);
+        match vec.to_jobject(&mut env) {
+            Ok(obj) => obj.into_raw(),
+            Err(_) => std::ptr::null_mut(),
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_InputStateNative_isMouseButtonPressed(
+        mut env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+        mouse_button_obj: JObject,
+    ) -> jboolean {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+
+        let ordinal_res = env.call_method(&mouse_button_obj, "ordinal", "()I", &[]);
+
+        let ordinal = match ordinal_res.and_then(|v| v.i()) {
+            Ok(i) => i,
+            Err(e) => {
+                eprintln!("Failed to get MouseButton ordinal: {:?}", e);
+                return 0;
+            }
+        };
+
+        if super::shared::is_mouse_button_pressed(&input, ordinal) { 1 } else { 0 }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_InputStateNative_getMouseDelta(
+        mut env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+    ) -> jobject {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+        let vec = super::shared::get_mouse_delta(&input);
+        match vec.to_jobject(&mut env) {
+            Ok(obj) => obj.into_raw(),
+            Err(_) => std::ptr::null_mut(),
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_InputStateNative_isCursorLocked(
+        _env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+    ) -> jboolean {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+        if input.is_cursor_locked { 1 } else { 0 }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_InputStateNative_setCursorLocked(
+        mut env: JNIEnv,
+        _class: JClass,
+        cmd_ptr: jlong,
+        input_ptr: jlong,
+        locked: jboolean,
+    ) {
+        let input = crate::convert_ptr!(mut input_ptr => InputState);
+        let sender = crate::convert_ptr!(cmd_ptr => crossbeam_channel::Sender<CommandBuffer>);
+
+        if let Err(e) = super::shared::set_cursor_locked(input, sender, locked != 0) {
+            let _ = env.throw_new("java/lang/RuntimeException", format!("{:?}", e));
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_InputStateNative_getLastMousePos(
+        mut env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+    ) -> jobject {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+        let vec = super::shared::get_last_mouse_pos(&input);
+        match vec.to_jobject(&mut env) {
+            Ok(obj) => obj.into_raw(),
+            Err(_) => std::ptr::null_mut(),
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_InputStateNative_isCursorHidden(
+        _env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+    ) -> jboolean {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+        if input.is_cursor_hidden { 1 } else { 0 }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_InputStateNative_setCursorHidden(
+        mut env: JNIEnv,
+        _class: JClass,
+        cmd_ptr: jlong,
+        input_ptr: jlong,
+        hidden: jboolean,
+    ) {
+        let input = crate::convert_ptr!(mut input_ptr => InputState);
+        let sender = crate::convert_ptr!(cmd_ptr => crossbeam_channel::Sender<CommandBuffer>);
+
+        if let Err(e) = super::shared::set_cursor_hidden(input, sender, hidden != 0) {
+            let _ = env.throw_new("java/lang/RuntimeException", format!("{:?}", e));
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_InputStateNative_getConnectedGamepads(
+        env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+    ) -> jlongArray {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+        let ids = super::shared::get_connected_gamepads(&input);
+
+        let long_ids: Vec<i64> = ids.iter().map(|&id| id as i64).collect();
+
+        let output = match env.new_long_array(long_ids.len() as i32) {
+            Ok(arr) => arr,
+            Err(_) => return std::ptr::null_mut(),
+        };
+
+        if env.set_long_array_region(&output, 0, &long_ids).is_ok() {
+            output.into_raw()
+        } else {
+            std::ptr::null_mut()
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+    use crate::ptr::{InputStatePtr, CommandBufferPtr};
+    use crate::input::{InputState};
+    use crate::command::CommandBuffer;
+    use crate::convert_ptr;
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+    use crate::types::Vector2;
+
+    pub fn dropbear_print_input_state(input_ptr: InputStatePtr) -> DropbearNativeResult<()> {
+        let input = convert_ptr!(input_ptr => InputState);
+        println!("Input State: {:?}", input);
+        DropbearNativeResult::Ok(())
+    }
+
+    pub fn dropbear_is_key_pressed(input_ptr: InputStatePtr, key_ordinal: i32) -> DropbearNativeResult<bool> {
+        let input = convert_ptr!(input_ptr => InputState);
+        DropbearNativeResult::Ok(super::shared::is_key_pressed(input, key_ordinal))
+    }
+
+    pub fn dropbear_get_mouse_position(input_ptr: InputStatePtr) -> DropbearNativeResult<Vector2> {
+        let input = convert_ptr!(input_ptr => InputState);
+        DropbearNativeResult::Ok(super::shared::get_mouse_position(input))
+    }
+
+    pub fn dropbear_is_mouse_button_pressed(input_ptr: InputStatePtr, btn_ordinal: i32) -> DropbearNativeResult<bool> {
+        let input = convert_ptr!(input_ptr => InputState);
+        DropbearNativeResult::Ok(super::shared::is_mouse_button_pressed(input, btn_ordinal))
+    }
+
+    pub fn dropbear_get_mouse_delta(input_ptr: InputStatePtr) -> DropbearNativeResult<Vector2> {
+        let input = convert_ptr!(input_ptr => InputState);
+        DropbearNativeResult::Ok(super::shared::get_mouse_delta(input))
+    }
+
+    pub fn dropbear_is_cursor_locked(input_ptr: InputStatePtr) -> DropbearNativeResult<bool> {
+        let input = convert_ptr!(input_ptr => InputState);
+        DropbearNativeResult::Ok(input.is_cursor_locked)
+    }
+
+    pub fn dropbear_set_cursor_locked(
+        cmd_ptr: CommandBufferPtr,
+        input_ptr: InputStatePtr,
+        locked: bool
+    ) -> DropbearNativeResult<()> {
+        let input = convert_ptr!(mut input_ptr => InputState);
+        let sender = convert_ptr!(cmd_ptr => crossbeam_channel::Sender<CommandBuffer>);
+        super::shared::set_cursor_locked(input, sender, locked)
+    }
+
+    pub fn dropbear_get_last_mouse_pos(input_ptr: InputStatePtr) -> DropbearNativeResult<Vector2> {
+        let input = convert_ptr!(input_ptr => InputState);
+        DropbearNativeResult::Ok(super::shared::get_last_mouse_pos(input))
+    }
+
+    pub fn dropbear_is_cursor_hidden(input_ptr: InputStatePtr) -> DropbearNativeResult<bool> {
+        let input = convert_ptr!(input_ptr => InputState);
+        DropbearNativeResult::Ok(input.is_cursor_hidden)
+    }
+
+    pub fn dropbear_set_cursor_hidden(
+        cmd_ptr: CommandBufferPtr,
+        input_ptr: InputStatePtr,
+        hidden: bool
+    ) -> DropbearNativeResult<()> {
+        let input = convert_ptr!(mut input_ptr, InputStatePtr => InputState);
+        let sender = convert_ptr!(cmd_ptr => crossbeam_channel::Sender<CommandBuffer>);
+        super::shared::set_cursor_hidden(input, sender, hidden)
+    }
+
+    pub fn dropbear_get_connected_gamepads(
+        input_ptr: InputStatePtr,
+        out_count: *mut usize,
+    ) -> DropbearNativeResult<*mut u64> {
+        let input = convert_ptr!(input_ptr => InputState);
+
+        let mut ids = super::shared::get_connected_gamepads(input);
+
+        ids.shrink_to_fit();
+
+        if out_count.is_null() {
+            return DropbearNativeResult::Err(DropbearNativeError::NullPointer);
+        }
+
+        unsafe { *out_count = ids.len(); }
+
+        let ptr = ids.as_mut_ptr();
+        std::mem::forget(ids);
+
+        DropbearNativeResult::Ok(ptr)
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/input/gamepad.rs b/eucalyptus-core/src/input/gamepad.rs
new file mode 100644
index 0000000..0455131
--- /dev/null
+++ b/eucalyptus-core/src/input/gamepad.rs
@@ -0,0 +1,233 @@
+pub mod shared {
+    use dropbear_engine::gilrs::{Button, GamepadId};
+    use jni::JNIEnv;
+    use jni::objects::{JObject, JValue};
+    use dropbear_engine::gilrs;
+    use crate::input::InputState;
+    use crate::scripting::jni::utils::{FromJObject, ToJObject};
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+    use crate::types::Vector2;
+
+    fn map_int_to_gamepad_button(ordinal: i32) -> Option<Button> {
+        match ordinal {
+            0 => Some(gilrs::Button::Unknown),
+            1 => Some(gilrs::Button::South),
+            2 => Some(gilrs::Button::East),
+            3 => Some(gilrs::Button::North),
+            4 => Some(gilrs::Button::West),
+            5 => Some(gilrs::Button::C),
+            6 => Some(gilrs::Button::Z),
+            7 => Some(gilrs::Button::LeftTrigger),
+            8 => Some(gilrs::Button::RightTrigger),
+            9 => Some(gilrs::Button::LeftTrigger2),
+            10 => Some(gilrs::Button::RightTrigger2),
+            11 => Some(gilrs::Button::Select),
+            12 => Some(gilrs::Button::Start),
+            13 => Some(gilrs::Button::Mode),
+            14 => Some(gilrs::Button::LeftThumb),
+            15 => Some(gilrs::Button::RightThumb),
+            16 => Some(gilrs::Button::DPadUp),
+            17 => Some(gilrs::Button::DPadDown),
+            18 => Some(gilrs::Button::DPadLeft),
+            19 => Some(gilrs::Button::DPadRight),
+            _ => None,
+        }
+    }
+
+    pub fn get_gamepad_id(input: &InputState, target: usize) -> Option<GamepadId> {
+        input.connected_gamepads.iter().find(|g| usize::from(**g) == target).copied()
+    }
+
+    pub fn is_gamepad_button_pressed(input: &InputState, gamepad_id: u64, button_ordinal: i32) -> bool {
+        let Some(id) = get_gamepad_id(input, gamepad_id as usize) else {
+            return false;
+        };
+
+        if let Some(btn) = map_int_to_gamepad_button(button_ordinal) {
+            input.is_button_pressed(id, btn)
+        } else {
+            false
+        }
+    }
+
+    pub fn get_left_stick(input: &InputState, gamepad_id: u64) -> Vector2 {
+        let Some(id) = get_gamepad_id(input, gamepad_id as usize) else {
+            return Vector2 {
+                x: 0.0,
+                y: 0.0
+            }
+        };
+        let (x, y) = input.get_left_stick(id);
+        Vector2 { x: x as f64, y: y as f64 }
+    }
+
+    pub fn get_right_stick(input: &InputState, gamepad_id: u64) -> Vector2 {
+        let Some(id) = get_gamepad_id(input, gamepad_id as usize) else {
+            return Vector2 {
+                x: 0.0,
+                y: 0.0
+            }
+        };
+        let (x, y) = input.get_right_stick(id);
+        Vector2 { x: x as f64, y: y as f64 }
+    }
+
+    impl ToJObject for Vector2 {
+        fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
+            let cls = env.find_class("com/dropbear/math/Vector2d")
+                .map_err(|e| {
+                    eprintln!("Could not find Vector2d class: {:?}", e);
+                    DropbearNativeError::GenericError
+                })?;
+
+            let obj = env.new_object(
+                cls,
+                "(DD)V",
+                &[
+                    JValue::Double(self.x),
+                    JValue::Double(self.y)
+                ]
+            ).map_err(|e| {
+                eprintln!("Failed to create Vector2d object: {:?}", e);
+                DropbearNativeError::GenericError
+            })?;
+
+            Ok(obj)
+        }
+    }
+
+    impl FromJObject for Vector2 {
+        fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self>
+        where
+            Self: Sized
+        {
+            let x_val = env
+                .get_field(obj, "x", "D")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+                .d()
+                .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+            let y_val = env
+                .get_field(obj, "y", "D")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+                .d()
+                .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+            Ok(Vector2 {
+                x: x_val,
+                y: y_val,
+            })
+        }
+    }
+}
+
+pub mod jni {
+    #![allow(non_snake_case)]
+
+    use jni::JNIEnv;
+    use jni::objects::JClass;
+    use jni::sys::{jboolean, jint, jlong, jobject};
+    use crate::input::InputState;
+    use crate::scripting::jni::utils::ToJObject;
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_GamepadNative_isGamepadButtonPressed(
+        _env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+        gamepad_id: jlong,
+        button_ordinal: jint,
+    ) -> jboolean {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+        if super::shared::is_gamepad_button_pressed(&input, gamepad_id as u64, button_ordinal) {
+            1
+        } else {
+            0
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_GamepadNative_getLeftStickPosition(
+        mut env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+        gamepad_id: jlong,
+    ) -> jobject {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+        let vec = super::shared::get_left_stick(&input, gamepad_id as u64);
+
+        match vec.to_jobject(&mut env) {
+            Ok(obj) => obj.into_raw(),
+            Err(_) => std::ptr::null_mut(),
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_GamepadNative_getRightStickPosition(
+        mut env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+        gamepad_id: jlong,
+    ) -> jobject {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+        let vec = super::shared::get_right_stick(&input, gamepad_id as u64);
+
+        match vec.to_jobject(&mut env) {
+            Ok(obj) => obj.into_raw(),
+            Err(_) => std::ptr::null_mut(),
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+    use crate::ptr::InputStatePtr;
+    use crate::input::{InputState};
+    use crate::convert_ptr;
+    use crate::scripting::result::DropbearNativeResult;
+    use crate::types::Vector2;
+
+    pub fn dropbear_is_gamepad_button_pressed(
+        input_ptr: InputStatePtr,
+        gamepad_id: u64,
+        button_ordinal: i32
+    ) -> DropbearNativeResult<bool> {
+        let input = convert_ptr!(input_ptr => InputState);
+        let result = super::shared::is_gamepad_button_pressed(input, gamepad_id, button_ordinal);
+        DropbearNativeResult::Ok(result)
+    }
+
+    pub fn dropbear_get_left_stick_position(
+        input_ptr: InputStatePtr,
+        gamepad_id: u64
+    ) -> DropbearNativeResult<Vector2> {
+        let input = convert_ptr!(input_ptr => InputState);
+        let vec = super::shared::get_left_stick(input, gamepad_id);
+        DropbearNativeResult::Ok(vec)
+    }
+
+    pub fn dropbear_get_right_stick_position(
+        input_ptr: InputStatePtr,
+        gamepad_id: u64
+    ) -> DropbearNativeResult<Vector2> {
+        let input = convert_ptr!(input_ptr => InputState);
+        let vec = super::shared::get_right_stick(input, gamepad_id);
+        DropbearNativeResult::Ok(vec)
+    }
+
+    pub fn dropbear_free_gamepads_array(
+        ptr: *mut u64,
+        count: usize,
+    ) -> DropbearNativeResult<()> {
+        if ptr.is_null() {
+            return DropbearNativeResult::Ok(());
+        }
+
+        unsafe {
+            let _ = Vec::from_raw_parts(ptr, count, count);
+        }
+
+        DropbearNativeResult::Ok(())
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/lib.rs b/eucalyptus-core/src/lib.rs
index 6d0b3cb..8ade930 100644
--- a/eucalyptus-core/src/lib.rs
+++ b/eucalyptus-core/src/lib.rs
@@ -14,6 +14,13 @@ pub mod states;
 pub mod utils;
 pub mod command;
 pub mod physics;
+pub mod asset;
+pub mod types;
+pub mod properties;
+pub mod mesh;
+pub mod entity;
+pub mod engine;
+pub mod transform;
 
 pub use dropbear_macro as macros;
 pub use dropbear_traits as traits;
@@ -23,10 +30,11 @@ pub use rapier3d;
 use dropbear_engine::camera::Camera;
 use dropbear_engine::entity::{EntityTransform, MeshRenderer};
 use dropbear_traits::registry::ComponentRegistry;
+use properties::CustomProperties;
 use crate::camera::CameraComponent;
-use crate::physics::collider::{ColliderGroup};
+use crate::physics::collider::ColliderGroup;
 use crate::physics::rigidbody::RigidBody;
-use crate::states::{Camera3D, CustomProperties, Light, Script, SerializedMeshRenderer};
+use crate::states::{Camera3D, Light, Script, SerializedMeshRenderer};
 
 /// The appdata directory for storing any information.
 ///
@@ -53,6 +61,8 @@ pub fn register_components(
     component_registry.register_with_default::<Script>();
     component_registry.register_with_default::<SerializedMeshRenderer>();
     component_registry.register_with_default::<Camera3D>();
+    component_registry.register_with_default::<RigidBody>();
+    component_registry.register_with_default::<ColliderGroup>();
 
     component_registry.register_converter::<MeshRenderer, SerializedMeshRenderer, _>(
         |_, _, renderer| {
@@ -77,9 +87,6 @@ pub fn register_components(
         },
     );
 
-    component_registry.register_with_default::<RigidBody>();
-    component_registry.register_with_default::<ColliderGroup>();
-
     // // register plugin defined structs
     // if let Err(e) = plugin_registry.load_plugins() {
     //     fatal!("Failed to load plugins: {}", e);
diff --git a/eucalyptus-core/src/mesh.rs b/eucalyptus-core/src/mesh.rs
new file mode 100644
index 0000000..5eda4a0
--- /dev/null
+++ b/eucalyptus-core/src/mesh.rs
@@ -0,0 +1,427 @@
+pub mod shared {
+    use dropbear_engine::entity::MeshRenderer;
+    use hecs::{Entity, World};
+
+    pub fn mesh_renderer_exists_for_entity(world: &World, entity: Entity) -> bool {
+        world.get::<&MeshRenderer>(entity).is_ok()
+    }
+
+    pub fn resolve_target_material_name(
+        renderer: &MeshRenderer,
+        target_identifier: &str
+    ) -> Option<String> {
+        if let Some(cached) = renderer.resolve_texture_identifier(target_identifier) {
+            return Some(cached.to_string());
+        }
+
+        let model = renderer.model();
+
+        model.materials.iter().find_map(|mat| {
+            if mat.name == target_identifier { return Some(mat.name.clone()); }
+            if let Some(tag) = &mat.texture_tag { if tag == target_identifier { return Some(mat.name.clone()); } }
+            None
+        })
+    }
+}
+
+pub mod jni {
+    #![allow(non_snake_case)]
+
+    use crate::return_boxed;
+    use dropbear_engine::asset::PointerKind::Const;
+    use dropbear_engine::asset::{AssetHandle, AssetRegistry};
+    use dropbear_engine::entity::MeshRenderer;
+    use dropbear_engine::model::Model;
+    use hecs::World;
+    use jni::objects::{JClass, JString};
+    use jni::sys::{jboolean, jlong, jlongArray, jobject, jsize};
+    use jni::JNIEnv;
+    use parking_lot::Mutex;
+    use std::collections::HashMap;
+    use std::sync::Arc;
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_MeshRendererNative_meshRendererExistsForEntity(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jboolean {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        super::shared::mesh_renderer_exists_for_entity(&world, entity) as jboolean
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_MeshRendererNative_getModel(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jlong {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+
+        if let Ok(mesh) = world.get::<&MeshRenderer>(entity) {
+            mesh.model_id().raw() as jlong
+        } else {
+            let _ = env.throw_new("java/lang/RuntimeException", "Entity does not have a MeshRenderer component");
+            -1
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_MeshRendererNative_setModel(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        asset_ptr: jlong,
+        entity_id: jlong,
+        model_id: jlong,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let asset = crate::convert_ptr!(asset_ptr => AssetRegistry);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+
+        if let Ok(mut mesh) = world.get::<&mut MeshRenderer>(entity) {
+            match mesh.set_asset_handle_raw(asset, AssetHandle::new(model_id as u32)) {
+                Ok(_) => {}
+                Err(e) => {
+                    let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to set model: {}", e));
+                }
+            }
+        } else {
+            let _ = env.throw_new("java/lang/RuntimeException", "Entity does not have a MeshRenderer component");
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_MeshRendererNative_getAllTextureIds(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        asset_ptr: jlong,
+        entity_id: jlong,
+    ) -> jlongArray {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let asset = crate::convert_ptr!(asset_ptr => AssetRegistry);
+
+        if let Ok(mut renderer) = world.get::<&mut MeshRenderer>(entity) {
+            let handles = renderer.collect_all_material_handles_raw(&asset);
+            let jarray = match env.new_long_array(handles.len() as jsize) {
+                Ok(val) => val,
+                Err(_) => {
+                    let _ = env.throw_new("java/lang/OutOfMemoryError", "Could not allocate texture ID array");
+                    return std::ptr::null_mut();
+                }
+            };
+
+            if let Err(e) = env.set_long_array_region(&jarray, 0, handles.iter().map(|v| v.raw() as jlong).collect::<Vec<_>>().as_slice()) {
+                let _ = env.throw_new("java/lang/RuntimeException", format!("{:?}", e));
+                return std::ptr::null_mut();
+            }
+
+            jarray.into_raw()
+        } else {
+            let _ = env.throw_new("java/lang/RuntimeException", "Entity does not have a MeshRenderer component");
+            std::ptr::null_mut()
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_MeshRendererNative_getTexture(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        asset_ptr: jlong,
+        entity_id: jlong,
+        material_name: JString,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let asset = crate::convert_ptr!(asset_ptr => dropbear_engine::asset::AssetRegistry);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let name = crate::convert_jstring!(env, material_name);
+
+        let handle_value: Option<i64> = if let Ok(renderer) = world.get::<&dropbear_engine::entity::MeshRenderer>(entity) {
+            renderer.material_handle_raw(asset, &name)
+                .map(|h| h.raw() as i64)
+        } else {
+            None
+        };
+
+        return_boxed!(&mut env, handle_value, "(J)Ljava/lang/Long;", "java/lang/Long")
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_MeshRendererNative_setTextureOverride(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        asset_handle: jlong,
+        entity_id: jlong,
+        material_name: JString,
+        texture_handle: jlong,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let asset = crate::convert_ptr!(asset_handle => dropbear_engine::asset::AssetRegistry);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let target_identifier = crate::convert_jstring!(env, material_name);
+        let new_texture_handle = dropbear_engine::asset::AssetHandle::new(texture_handle as u64);
+
+        if let Ok(mut mesh) = world.get::<&mut dropbear_engine::entity::MeshRenderer>(entity) {
+            let resolved_target = super::shared::resolve_target_material_name(&mesh, &target_identifier);
+
+            let Some(target_material_name) = resolved_target else {
+                let _ = env.throw_new(
+                    "java/lang/IllegalArgumentException",
+                    format!(
+                        "Could not resolve material identifier '{}' on model '{}'",
+                        target_identifier,
+                        mesh.model().label
+                    ),
+                );
+                return;
+            };
+
+            if !asset.contains_handle(new_texture_handle) {
+                let _ = env.throw_new("java/lang/IllegalArgumentException", "Invalid texture handle provided");
+                return;
+            }
+
+            if !asset.is_material(new_texture_handle) {
+                let _ = env.throw_new("java/lang/IllegalArgumentException", "Handle provided is not a material/texture");
+                return;
+            }
+
+            let Some(source_material) = asset.get_material(new_texture_handle) else { return; };
+
+            let Some(owner_model_id) = asset.material_owner(new_texture_handle) else {
+                let _ = env.throw_new("java/lang/IllegalStateException", "Texture handle has no owner model in registry");
+                return;
+            };
+
+            let Some(owner_model_handle) = asset.model_handle_from_id(owner_model_id) else {
+                let _ = env.throw_new("java/lang/IllegalStateException", "Texture owner model not found in registry");
+                return;
+            };
+
+            let Some(source_reference) = asset.model_reference_for_handle(owner_model_handle) else {
+                let _ = env.throw_new("java/lang/IllegalStateException", "Texture owner model has no resource reference");
+                return;
+            };
+
+            let Some(model_cache) = asset.get_pointer(Const("model_cache")) else {
+                let _ = env.throw_new("java/lang/IllegalArgumentException", "Unable to locate model_cache pointer within the asset registry");
+                return;
+            };
+
+            let model_cache = model_cache as *const Mutex<HashMap<String, Arc<Model>>>;
+            if model_cache.is_null() {
+                let _ = env.throw_new("java/lang/IllegalStateException", "Model cache cannot be null");
+                return;
+            }
+
+            let model_cache = unsafe { &*model_cache };
+
+            if let Err(e) = mesh.apply_material_override_raw(
+                asset,
+                model_cache,
+                &target_material_name,
+                source_reference,
+                &source_material.name,
+            ) {
+                let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to apply texture override: {}", e));
+            }
+
+        } else {
+            let _ = env.throw_new("java/lang/RuntimeException", "Entity does not have a MeshRenderer component");
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+    use parking_lot::Mutex;
+    use std::collections::HashMap;
+    use std::os::raw::c_char;
+    use std::sync::Arc;
+
+    use hecs::{Entity, World};
+
+    use dropbear_engine::asset::{AssetHandle, AssetRegistry, PointerKind};
+    use dropbear_engine::entity::MeshRenderer;
+    use dropbear_engine::model::Model;
+
+    use crate::convert_ptr;
+    use crate::engine::shared::read_str;
+    use crate::ptr::{AssetRegistryPtr, WorldPtr};
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+
+    pub fn dropbear_mesh_renderer_exists_for_entity(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<bool> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+        DropbearNativeResult::Ok(super::shared::mesh_renderer_exists_for_entity(world, entity))
+    }
+    
+    pub fn dropbear_get_model(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<u64> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mesh) = world.get::<&MeshRenderer>(entity) {
+            DropbearNativeResult::Ok(mesh.model_id().raw())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+    
+    pub fn dropbear_set_model(
+        world_ptr: WorldPtr,
+        asset_ptr: AssetRegistryPtr,
+        entity_id: u64,
+        model_id: u64,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => World);
+        let asset = convert_ptr!(asset_ptr => AssetRegistry);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut mesh) = world.get::<&mut MeshRenderer>(entity) {
+            match mesh.set_asset_handle_raw(asset, AssetHandle::new(model_id)) {
+                Ok(_) => DropbearNativeResult::Ok(()),
+                Err(_) => DropbearNativeResult::Err(DropbearNativeError::UnknownError),
+            }
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+    
+    pub fn dropbear_get_all_texture_ids(
+        world_ptr: WorldPtr,
+        asset_ptr: AssetRegistryPtr,
+        entity_id: u64,
+        out_count: *mut usize,
+    ) -> DropbearNativeResult<*mut u64> {
+        let world = convert_ptr!(world_ptr => World);
+        let asset = convert_ptr!(asset_ptr => AssetRegistry);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if out_count.is_null() {
+            return DropbearNativeResult::Err(DropbearNativeError::NullPointer);
+        }
+
+        if let Ok(renderer) = world.get::<&MeshRenderer>(entity) {
+            let handles = renderer.collect_all_material_handles_raw(asset);
+
+            let mut raw_ids: Vec<u64> = handles.iter().map(|h| h.raw()).collect();
+
+            unsafe { *out_count = raw_ids.len(); }
+
+            let ptr = raw_ids.as_mut_ptr();
+            std::mem::forget(raw_ids);
+
+            DropbearNativeResult::Ok(ptr)
+        } else {
+            unsafe { *out_count = 0; }
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+    
+    pub fn dropbear_get_texture(
+        world_ptr: WorldPtr,
+        asset_ptr: AssetRegistryPtr,
+        entity_id: u64,
+        material_name: *const c_char,
+    ) -> DropbearNativeResult<u64> {
+        let world = convert_ptr!(world_ptr => World);
+        let asset = convert_ptr!(asset_ptr => AssetRegistry);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+        let target = unsafe { read_str(material_name)? };
+
+        if let Ok(renderer) = world.get::<&MeshRenderer>(entity) {
+            match renderer.material_handle_raw(asset, &target) {
+                Some(handle) => DropbearNativeResult::Ok(handle.raw()),
+                None => DropbearNativeResult::Err(DropbearNativeError::UnknownError),
+            }
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+    
+    pub fn dropbear_set_texture_override(
+        world_ptr: WorldPtr,
+        asset_ptr: AssetRegistryPtr,
+        entity_id: u64,
+        material_name: *const c_char,
+        texture_handle: u64,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => World);
+        let asset = convert_ptr!(asset_ptr => AssetRegistry);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+        let target_identifier = unsafe { read_str(material_name)? };
+        let new_texture_handle = AssetHandle::new(texture_handle);
+
+        if let Ok(mut mesh) = world.get::<&mut MeshRenderer>(entity) {
+            let resolved_target = super::shared::resolve_target_material_name(&mesh, &target_identifier);
+            let target_material_name = match resolved_target {
+                Some(name) => name,
+                None => return DropbearNativeResult::Err(DropbearNativeError::InvalidArgument),
+            };
+
+            if !asset.contains_handle(new_texture_handle) || !asset.is_material(new_texture_handle) {
+                return DropbearNativeResult::Err(DropbearNativeError::InvalidArgument);
+            }
+
+            let source_material = match asset.get_material(new_texture_handle) {
+                Some(m) => m,
+                None => return DropbearNativeResult::Err(DropbearNativeError::UnknownError),
+            };
+
+            let owner_model_id = match asset.material_owner(new_texture_handle) {
+                Some(id) => id,
+                None => return DropbearNativeResult::Err(DropbearNativeError::QueryFailed),
+            };
+
+            let owner_model_handle = match asset.model_handle_from_id(owner_model_id) {
+                Some(h) => h,
+                None => return DropbearNativeResult::Err(DropbearNativeError::QueryFailed),
+            };
+
+            let source_reference = match asset.model_reference_for_handle(owner_model_handle) {
+                Some(r) => r,
+                None => return DropbearNativeResult::Err(DropbearNativeError::QueryFailed),
+            };
+
+            let model_cache_ptr = match asset.get_pointer(PointerKind::Const("model_cache")) {
+                Some(ptr) => ptr as *const Mutex<HashMap<String, Arc<Model>>>,
+                None => return DropbearNativeResult::Err(DropbearNativeError::NullPointer), // Cache not found
+            };
+
+            if model_cache_ptr.is_null() {
+                return DropbearNativeResult::Err(DropbearNativeError::NullPointer);
+            }
+
+            let model_cache = unsafe { &*model_cache_ptr };
+
+            match mesh.apply_material_override_raw(
+                asset,
+                model_cache,
+                &target_material_name,
+                source_reference,
+                &source_material.name,
+            ) {
+                Ok(_) => DropbearNativeResult::Ok(()),
+                Err(_) => DropbearNativeResult::Err(DropbearNativeError::WorldInsertError),
+            }
+
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/physics.rs b/eucalyptus-core/src/physics.rs
index 7edb8e8..2455c43 100644
--- a/eucalyptus-core/src/physics.rs
+++ b/eucalyptus-core/src/physics.rs
@@ -1,11 +1,13 @@
 //! Components in the eucalyptus-editor and redback-runtime that relate to rapier3d based physics.
 
 use std::collections::HashMap;
+use std::ops::AddAssign;
 use hecs::Entity;
 use dropbear_engine::entity::Transform;
 use rapier3d::na::{Quaternion, UnitQuaternion, Vector3};
 use rapier3d::prelude::*;
 use serde::{Deserialize, Serialize};
+use crate::physics::collider::NEXT_ID;
 use crate::physics::rigidbody::RigidBodyMode;
 use crate::states::Label;
 
@@ -16,19 +18,32 @@ pub mod collider;
 /// to physics rendering.
 #[derive(Serialize, Deserialize, Clone)]
 pub struct PhysicsState {
+    #[serde(default)]
     pub islands: IslandManager,
+    #[serde(default)]
     pub broad_phase: DefaultBroadPhase,
+    #[serde(default)]
     pub narrow_phase: NarrowPhase,
+    #[serde(default)]
     pub bodies: RigidBodySet,
+    #[serde(default)]
     pub colliders: ColliderSet,
+    #[serde(default)]
     pub impulse_joints: ImpulseJointSet,
+    #[serde(default)]
     pub multibody_joints: MultibodyJointSet,
+    #[serde(default)]
     pub ccd_solver: CCDSolver,
+    #[serde(default)]
     pub integration_parameters: IntegrationParameters,
+
     pub gravity: [f32; 3],
 
+    #[serde(default)]
     pub bodies_entity_map: HashMap<Label, RigidBodyHandle>,
-    pub colliders_entity_map: HashMap<Label, Vec<ColliderHandle>>,
+    #[serde(default)]
+    pub colliders_entity_map: HashMap<Label, Vec<(u32, ColliderHandle)>>,
+    #[serde(default)]
     pub entity_label_map: HashMap<Entity, Label>,
 }
 
@@ -79,14 +94,14 @@ impl PhysicsState {
 
         let pos = transform.position.as_vec3().to_array();
         let rot = transform.rotation.as_quat().to_array();
-        let scale = transform.scale;
-        
+
         let body = RigidBodyBuilder::new(mode)
-            .translation(vector![pos[0] * scale.x as f32, pos[1] * scale.y as f32, pos[2] * scale.z as f32])
+            .translation(vector![pos[0], pos[1], pos[2]])
             .rotation(UnitQuaternion::from_quaternion(Quaternion::new(
                 rot[3] as f32, rot[0] as f32, rot[1] as f32, rot[2] as f32
             )).scaled_axis())
             .gravity_scale(rigid_body.gravity_scale)
+            .sleeping(rigid_body.sleeping)
             .can_sleep(rigid_body.can_sleep)
             .ccd_enabled(rigid_body.ccd_enabled)
             .linvel(Vector3::from_column_slice(&rigid_body.linvel))
@@ -103,7 +118,7 @@ impl PhysicsState {
         if let Some(collider_handles) = self.colliders_entity_map.get(&rigid_body.entity) {
             let handles_to_attach = collider_handles.clone();
 
-            for handle in handles_to_attach {
+            for (id, handle) in handles_to_attach {
                 self.colliders.set_parent(handle, Some(body_handle), &mut self.bodies);
             }
         }
@@ -145,23 +160,23 @@ impl PhysicsState {
         );
         builder = builder.rotation(rotation.scaled_axis());
 
-        // check if entity has rigid body
         let handle = if let Some(&rigid_body_handle) = self.bodies_entity_map.get(&collider_component.entity) {
-            // attach
             self.colliders.insert_with_parent(
                 builder.build(),
                 rigid_body_handle,
                 &mut self.bodies
             )
         } else {
-            // create a static collider if it doesn't exist
             self.colliders.insert(builder.build())
         };
 
+        let mut next_id = *NEXT_ID.get_mut();
+        next_id.add_assign(1);
+
         self.colliders_entity_map
             .entry(collider_component.entity.clone())
             .or_insert_with(Vec::new)
-            .push(handle);
+            .push((next_id as u32, handle));
 
         handle
     }
@@ -169,12 +184,12 @@ impl PhysicsState {
     /// Remove all colliders associated with an entity
     pub fn remove_colliders(&mut self, entity: &Label) {
         if let Some(handles) = self.colliders_entity_map.remove(entity) {
-            for handle in handles {
+            for (_id, handle) in handles {
                 self.colliders.remove(
                     handle,
                     &mut self.islands,
                     &mut self.bodies,
-                    false // wake_up
+                    false
                 );
             }
         }
@@ -189,7 +204,7 @@ impl PhysicsState {
                 &mut self.colliders,
                 &mut self.impulse_joints,
                 &mut self.multibody_joints,
-                false // wake_up
+                false
             );
         }
         self.colliders_entity_map.remove(entity);
@@ -200,4 +215,70 @@ impl Default for PhysicsState {
     fn default() -> Self {
         Self::new()
     }
+}
+
+pub mod shared {
+    use crate::physics::PhysicsState;
+    use crate::types::Vector3;
+
+    pub fn get_gravity(physics: &PhysicsState) -> Vector3 {
+        Vector3::from(physics.gravity)
+    }
+
+    pub fn set_gravity(physics: &mut PhysicsState, new: Vector3) {
+        physics.gravity = new.to_float_array();
+    }
+}
+
+pub mod jni {
+    #![allow(non_snake_case)]
+
+    use jni::JNIEnv;
+    use jni::sys::{jlong, jobject};
+    use jni::objects::{JClass, JObject};
+    use crate::physics::PhysicsState;
+    use crate::scripting::jni::utils::{FromJObject, ToJObject};
+    use crate::scripting::result::DropbearNativeResult;
+    use crate::types::Vector3;
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_PhysicsNative_getGravity(
+        mut env: JNIEnv,
+        _: JClass,
+        physics_handle: jlong,
+    ) -> jobject {
+        let physics = crate::convert_ptr!(physics_handle => PhysicsState);
+
+        match super::shared::get_gravity(&physics).to_jobject(&mut env) {
+            Ok(v) => v.into_raw(),
+            Err(e) => {
+                let _ = env.throw_new("java/lang/RuntimeException", format!("Unable to create new Vector3d object for gravity: {}", e));
+                std::ptr::null_mut()
+            }
+        }
+
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_PhysicsNative_setGravity(
+        mut env: JNIEnv,
+        _: JClass,
+        physics_handle: jlong,
+        new_gravity: JObject,
+    ) {
+        let mut physics = crate::convert_ptr!(mut physics_handle => PhysicsState);
+        let vec3 = match Vector3::from_jobject(&mut env, &new_gravity) {
+            Ok(v) => v,
+            Err(e) => {
+                let _ = env.throw_new("java/lang/RuntimeException", format!("Unable to create new Vector3d object for gravity: {}", e));
+                return;
+            }
+        };
+
+        super::shared::set_gravity(&mut physics, vec3);
+    }
+}
+
+pub mod native {
+
 }
\ No newline at end of file
diff --git a/eucalyptus-core/src/physics/collider.rs b/eucalyptus-core/src/physics/collider.rs
index 087b046..d6e4a9b 100644
--- a/eucalyptus-core/src/physics/collider.rs
+++ b/eucalyptus-core/src/physics/collider.rs
@@ -14,17 +14,25 @@
 //!     - `-colonly` (invisible collision mesh)
 
 pub mod shader;
+pub mod collidergroup;
 
-use std::sync::Arc;
-use rapier3d::na::Vector3;
-use rapier3d::prelude::{ColliderBuilder};
-use serde::{Deserialize, Serialize};
+use crate::scripting::jni::utils::{FromJObject, ToJObject};
+use crate::scripting::result::DropbearNativeResult;
+use crate::states::Label;
 use dropbear_engine::graphics::SharedGraphicsContext;
-use dropbear_engine::wgpu::{Buffer, BufferUsages};
 use dropbear_engine::wgpu::util::{BufferInitDescriptor, DeviceExt};
+use dropbear_engine::wgpu::{Buffer, BufferUsages};
 use dropbear_macro::SerializableComponent;
 use dropbear_traits::SerializableComponent;
-use crate::states::Label;
+use ::jni::objects::{JObject, JValue};
+use ::jni::JNIEnv;
+use rapier3d::na::Vector3;
+use rapier3d::prelude::ColliderBuilder;
+use serde::{Deserialize, Serialize};
+use std::ops::AddAssign;
+use std::sync::atomic::AtomicUsize;
+use std::sync::Arc;
+use crate::scripting::native::DropbearNativeError;
 
 #[derive(Debug, Default, Serialize, Deserialize, Clone, SerializableComponent)]
 pub struct ColliderGroup {
@@ -90,6 +98,7 @@ pub enum ColliderShapeType {
     Cone,
 }
 
+#[repr(C)]
 #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
 pub enum ColliderShape {
     /// Box shape with half-extents (half-width, half-height, half-depth).
@@ -106,39 +115,181 @@ pub enum ColliderShape {
 
     /// Cone shape along Y-axis.
     Cone { half_height: f32, radius: f32 },
+}
 
-    // /// Convex hull from a mesh.
-    // ConvexHull { mesh_path: String },
-    //
-    // /// Triangle mesh for static/complex geometry.
-    // TriMesh { mesh_path: String },
-    //
-    // /// Heightfield for terrain.
-    // HeightField {
-    //     heights: Vec<f32>,
-    //     num_rows: usize,
-    //     num_cols: usize,
-    //     scale: [f32; 3],
-    // },
-    //
-    // /// Compound shape (multiple shapes combined).
-    // Compound { shapes: Vec<CompoundShape> },
+impl Default for ColliderShape {
+    fn default() -> Self {
+        ColliderShape::Box {
+            half_extents: [0.5, 0.5, 0.5]
+        }
+    }
 }
 
-// #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
-// pub struct CompoundShape {
-//     pub shape: ColliderShape,
-//     pub translation: [f32; 3],
-//     pub rotation: [f32; 3],
-// }
+impl ToJObject for ColliderShape {
+    fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
+        match self {
+            ColliderShape::Box { half_extents } => {
+                let vec_cls = env.find_class("com/dropbear/math/Vector3d")
+                    .map_err(|e| {
+                        eprintln!("[JNI Error] Vector3d class not found: {:?}", e);
+                        DropbearNativeError::JNIClassNotFound
+                    })?;
+
+                let vec_obj = env.new_object(
+                    &vec_cls,
+                    "(DDD)V",
+                    &[
+                        JValue::Double(half_extents[0] as f64),
+                        JValue::Double(half_extents[1] as f64),
+                        JValue::Double(half_extents[2] as f64)
+                    ]
+                ).map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
+
+                let cls = env.find_class("com/dropbear/physics/ColliderShape$Box")
+                    .map_err(|e| {
+                        eprintln!("[JNI Error] ColliderShape$Box class not found: {:?}", e);
+                        DropbearNativeError::JNIClassNotFound
+                    })?;
+
+                let obj = env.new_object(
+                    &cls,
+                    "(Lcom/dropbear/math/Vector3d;)V",
+                    &[JValue::Object(&vec_obj)]
+                ).map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
+
+                Ok(obj)
+            },
+            ColliderShape::Sphere { radius } => {
+                let cls = env.find_class("com/dropbear/physics/ColliderShape$Sphere")
+                    .map_err(|_| DropbearNativeError::JNIClassNotFound)?;
+
+                let obj = env.new_object(
+                    &cls,
+                    "(F)V",
+                    &[JValue::Float(*radius)]
+                ).map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
+
+                Ok(obj)
+            },
+            ColliderShape::Capsule { half_height, radius } => {
+                let cls = env.find_class("com/dropbear/physics/ColliderShape$Capsule")
+                    .map_err(|_| DropbearNativeError::JNIClassNotFound)?;
+
+                let obj = env.new_object(
+                    &cls,
+                    "(FF)V",
+                    &[JValue::Float(*half_height), JValue::Float(*radius)]
+                ).map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
+
+                Ok(obj)
+            },
+            ColliderShape::Cylinder { half_height, radius } => {
+                let cls = env.find_class("com/dropbear/physics/ColliderShape$Cylinder")
+                    .map_err(|_| DropbearNativeError::JNIClassNotFound)?;
+
+                // let ctor = env.get_method_id(&cls, "<init>", "(FF)V")
+                //     .map_err(|_| DropbearNativeError::JNIMethodNotFound)?;
+
+                let obj = env.new_object(
+                    &cls,
+                    "(FF)V",
+                    &[JValue::Float(*half_height), JValue::Float(*radius)]
+                ).map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
+
+                Ok(obj)
+            },
+            ColliderShape::Cone { half_height, radius } => {
+                let cls = env.find_class("com/dropbear/physics/ColliderShape$Cone")
+                    .map_err(|_| DropbearNativeError::JNIClassNotFound)?;
+
+                let obj = env.new_object(
+                    &cls,
+                    "(FF)V",
+                    &[JValue::Float(*half_height), JValue::Float(*radius)]
+                ).map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
+
+                Ok(obj)
+            },
+        }
+    }
+}
+
+impl FromJObject for ColliderShape {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self>
+    where
+        Self: Sized
+    {
+        let is_instance = |env: &mut JNIEnv, obj: &JObject, class_name: &str| -> bool {
+            env.is_instance_of(obj, class_name).unwrap_or(false)
+        };
+
+        if is_instance(env, obj, "com/dropbear/physics/ColliderShape$Box") {
+            let vec_obj_val = env.get_field(obj, "halfExtents", "Lcom/dropbear/math/Vector3d;")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?;
+            let vec_obj = vec_obj_val.l()
+                .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+            let x = env.get_field(&vec_obj, "x", "D")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?.d().unwrap_or(0.0);
+            let y = env.get_field(&vec_obj, "y", "D")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?.d().unwrap_or(0.0);
+            let z = env.get_field(&vec_obj, "z", "D")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?.d().unwrap_or(0.0);
+
+            return Ok(ColliderShape::Box {
+                half_extents: [x as f32, y as f32, z as f32]
+            });
+        }
+
+        if is_instance(env, obj, "com/dropbear/physics/ColliderShape$Sphere") {
+            let radius = env.get_field(obj, "radius", "F")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?.f().unwrap_or(0.0);
+
+            return Ok(ColliderShape::Sphere { radius });
+        }
+
+        if is_instance(env, obj, "com/dropbear/physics/ColliderShape$Capsule") {
+            let hh = env.get_field(obj, "halfHeight", "F")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?.f().unwrap_or(0.0);
+            let r = env.get_field(obj, "radius", "F")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?.f().unwrap_or(0.0);
+
+            return Ok(ColliderShape::Capsule { half_height: hh, radius: r });
+        }
+
+        if is_instance(env, obj, "com/dropbear/physics/ColliderShape$Cylinder") {
+            let hh = env.get_field(obj, "halfHeight", "F")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?.f().unwrap_or(0.0);
+            let r = env.get_field(obj, "radius", "F")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?.f().unwrap_or(0.0);
+
+            return Ok(ColliderShape::Cylinder { half_height: hh, radius: r });
+        }
+
+        if is_instance(env, obj, "com/dropbear/physics/ColliderShape$Cone") {
+            let hh = env.get_field(obj, "halfHeight", "F")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?.f().unwrap_or(0.0);
+            let r = env.get_field(obj, "radius", "F")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?.f().unwrap_or(0.0);
+
+            return Ok(ColliderShape::Cone { half_height: hh, radius: r });
+        }
+
+        Err(DropbearNativeError::GenericError)
+    }
+}
+
+pub const NEXT_ID: AtomicUsize = AtomicUsize::new(0);
 
 impl Collider {
     fn default_density() -> f32 { 1.0 }
     fn default_friction() -> f32 { 0.5 }
 
     pub fn new() -> Self {
+        { NEXT_ID.get_mut().add_assign(1); }
+        let id = { NEXT_ID.get_mut().clone() };
         Self {
-            id: 0,
+            id: id as u32,
             entity: Label::default(),
             shape: ColliderShape::default(),
             density: Self::default_density(),
@@ -235,21 +386,6 @@ impl Collider {
             ColliderShape::Cone { half_height, radius } => {
                 ColliderBuilder::cone(*half_height, *radius)
             }
-            // ColliderShape::ConvexHull { .. } => {
-            //     todo!("Load mesh from path and create convex hull")
-            // }
-            // ColliderShape::TriMesh { .. } => {
-            //     todo!("Load mesh from path and create trimesh")
-            // }
-            // ColliderShape::HeightField { heights, num_rows, num_cols, scale } => {
-            //     ColliderBuilder::heightfield(
-            //         DMatrix::from_vec(*num_rows, *num_cols, heights.clone()),
-            //         Vector3::new(scale[0], scale[1], scale[2])
-            //     )
-            // }
-            // ColliderShape::Compound { .. } => {
-            //     todo!("Build compound collider")
-            // }
         };
 
         shape
@@ -277,14 +413,6 @@ impl Collider {
     }
 }
 
-impl Default for ColliderShape {
-    fn default() -> Self {
-        ColliderShape::Box {
-            half_extents: [0.5, 0.5, 0.5]
-        }
-    }
-}
-
 pub struct WireframeGeometry {
     pub vertex_buffer: Buffer,
     pub index_buffer: Buffer,
@@ -450,105 +578,676 @@ impl WireframeGeometry {
     }
 }
 
+pub mod shared {
+    use crate::physics::PhysicsState;
+    use crate::types::ColliderFFI;
+    use rapier3d::prelude::ColliderHandle;
+
+    pub fn get_collider_mut<'a>(
+        physics: &'a mut PhysicsState,
+        ffi: &ColliderFFI
+    ) -> Option<&'a mut rapier3d::prelude::Collider> {
+        let handle = ColliderHandle::from_raw_parts(ffi.index.index, ffi.index.generation);
+        physics.colliders.get_mut(handle)
+    }
+
+    pub fn get_collider<'a>(
+        physics: &'a PhysicsState,
+        ffi: &ColliderFFI
+    ) -> Option<&'a rapier3d::prelude::Collider> {
+        let handle = ColliderHandle::from_raw_parts(ffi.index.index, ffi.index.generation);
+        physics.colliders.get(handle)
+    }
+}
+
 pub mod jni {
-    use glam::DVec3;
-    use jni::objects::{JObject, JString};
+    #![allow(non_snake_case)]
+    use crate::physics::collider::shared::{get_collider, get_collider_mut};
+    use crate::physics::PhysicsState;
+    use crate::scripting::jni::utils::{FromJObject, ToJObject};
+    use crate::types::ColliderFFI;
+    use glam::{DQuat, DVec3};
+    use jni::objects::{JClass, JObject};
+    use jni::sys::{jboolean, jdouble, jlong, jobject};
     use jni::JNIEnv;
-    use rapier3d::prelude::ColliderHandle;
-    use crate::physics::collider::{Collider, ColliderShape};
-    use crate::scripting::jni::utils::FromJObject;
-    use crate::states::Label;
-
-    impl Collider {
-        pub fn from_jni_object(env: &mut JNIEnv, obj: &JObject) -> anyhow::Result<(ColliderHandle, Self)> {
-            let entity_jobj = env.get_field(obj, "entity", "Ljava/lang/String;")?.l()?;
-            let entity_jstr: JString = entity_jobj.into();
-            let entity_str: String = env.get_string(&entity_jstr)?.into();
-            let entity = Label::from(entity_str);
-
-            let density = env.get_field(obj, "density", "D")?.d()? as f32;
-            let friction = env.get_field(obj, "friction", "D")?.d()? as f32;
-            let restitution = env.get_field(obj, "restitution", "D")?.d()? as f32;
-            let is_sensor = env.get_field(obj, "isSensor", "Z")?.z()?;
-
-            let trans_obj = env.get_field(obj, "translation", "Lcom/dropbear/math/Vector3D;")?.l()?;
-            let translation = DVec3::from_jobject(env, &trans_obj)?.as_vec3().to_array();
-
-            let rot_obj = env.get_field(obj, "rotation", "Lcom/dropbear/math/Vector3D;")?.l()?;
-            let rotation = DVec3::from_jobject(env, &rot_obj)?.as_vec3().to_array();
-
-            let shape_obj = env.get_field(obj, "colliderShape", "Lcom/dropbear/physics/ColliderShape;")?.l()?;
-            let shape = ColliderShape::from_jobject(env, &shape_obj)?;
-
-            let id = env.get_field(obj, "id", "I")?.i()? as u32;
-
-            let index_res: Result<(u32, u32), jni::errors::Error> = (|| {
-                let index_obj = env.get_field(&obj, "index", "Lcom/dropbear/physics/Index;")?.l()?;
-                let idx = env.get_field(&index_obj, "index", "I")?.i()? as u32;
-                let generation = env.get_field(&index_obj, "generation", "I")?.i()? as u32;
-                Ok((idx, generation))
-            })();
-
-            let (raw_index, raw_gen) = match index_res {
-                Ok(v) => v,
-                Err(e) => {
-                    let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Invalid Index in Collider: {:?}", e));
-                    return Err(anyhow::anyhow!("Invalid Index in Collider: {:?}", e));
+    use rapier3d::na::{UnitQuaternion, Vector3};
+    use rapier3d::prelude::{ColliderHandle, SharedShape, TypedShape};
+    use crate::physics::collider::ColliderShape;
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_getColliderShape(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+    ) -> jobject {
+        let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
+
+        let ffi = match ColliderFFI::from_jobject(&mut env, &collider_obj) {
+            Ok(v) => v,
+            Err(_) => return std::ptr::null_mut(),
+        };
+
+        if let Some(collider) = get_collider(&physics, &ffi) {
+            let rapier_shape = collider.shape();
+
+            let my_shape = match rapier_shape.as_typed_shape() {
+                TypedShape::Cuboid(c) => {
+                    let he = c.half_extents;
+                    ColliderShape::Box {
+                        half_extents: [he.x, he.y, he.z]
+                    }
+                },
+                TypedShape::Ball(b) => {
+                     ColliderShape::Sphere {
+                        radius: b.radius
+                     }
+                },
+                TypedShape::Capsule(c) => {
+                    let height = c.segment.length();
+                    ColliderShape::Capsule {
+                        half_height: height * 0.5,
+                        radius: c.radius
+                    }
+                },
+                TypedShape::Cylinder(c) => {
+                    ColliderShape::Cylinder {
+                        half_height: c.half_height,
+                        radius: c.radius
+                    }
+                },
+                TypedShape::Cone(c) => {
+                    ColliderShape::Cone {
+                        half_height: c.half_height,
+                        radius: c.radius
+                    }
+                },
+                _ => {
+                    eprintln!("Unsupported collider shape type found.");
+                    return std::ptr::null_mut();
                 }
             };
 
-            let handle = ColliderHandle::from_raw_parts(raw_index, raw_gen);
-
-            Ok((handle, Self {
-                id,
-                entity,
-                shape,
-                density,
-                friction,
-                restitution,
-                is_sensor,
-                translation,
-                rotation,
-            }))
-        }
-    }
-
-    impl FromJObject for ColliderShape {
-        fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> anyhow::Result<Self>
-        where
-            Self: Sized
-        {
-            if env.is_instance_of(obj, "com/dropbear/physics/ColliderShape$Box")? {
-                let half_extents_obj = env.get_field(obj, "halfExtents", "Lcom/dropbear/math/Vector3D;")?.l()?;
-                let half_extents = DVec3::from_jobject(env, &half_extents_obj)?.as_vec3().to_array();
-                return Ok(ColliderShape::Box { half_extents });
+            match my_shape.to_jobject(&mut env) {
+                Ok(obj) => obj.into_raw(),
+                Err(_) => std::ptr::null_mut(),
             }
 
-            if env.is_instance_of(obj, "com/dropbear/physics/ColliderShape$Sphere")? {
-                let radius = env.get_field(obj, "radius", "F")?.f()?;
-                return Ok(ColliderShape::Sphere { radius });
+        } else {
+            let _ = env.throw_new("java/lang/RuntimeException", "Collider handle invalid");
+            std::ptr::null_mut()
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_setColliderShape(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+        shape_obj: JObject,
+    ) {
+        let physics = crate::convert_ptr!(mut physics_ptr => PhysicsState);
+
+        let ffi = match ColliderFFI::from_jobject(&mut env, &collider_obj) {
+            Ok(v) => v,
+            Err(_) => return,
+        };
+
+        let handle = ColliderHandle::from_raw_parts(ffi.index.index, ffi.index.generation);
+
+        let Some(collider) = physics.colliders.get_mut(handle) else {
+            let _ = env.throw_new("java/lang/IllegalArgumentException", "Collider handle invalid");
+            return;
+        };
+
+        let Ok(shape) = ColliderShape::from_jobject(&mut env, &shape_obj) else {
+            let _ = env.throw_new("java/lang/IllegalArgumentException", "Collider shape is invalid");
+            return;
+        };
+
+        let new_shape = match shape {
+            ColliderShape::Box { half_extents } => {
+                SharedShape::cuboid(half_extents[0], half_extents[1], half_extents[2])
+            }
+            ColliderShape::Sphere { radius } => {
+                SharedShape::ball(radius)
+            }
+            ColliderShape::Capsule { half_height, radius } => {
+                SharedShape::capsule_y(half_height, radius)
+            }
+            ColliderShape::Cylinder { half_height, radius } => {
+                SharedShape::cylinder(half_height, radius)
+            }
+            ColliderShape::Cone { half_height, radius } => {
+                SharedShape::cone(half_height, radius)
             }
+        };
+
+        collider.set_shape(new_shape);
+    }
 
-            if env.is_instance_of(obj, "com/dropbear/physics/ColliderShape$Capsule")? {
-                let half_height = env.get_field(obj, "halfHeight", "F")?.f()?;
-                let radius = env.get_field(obj, "radius", "F")?.f()?;
-                return Ok(ColliderShape::Capsule { half_height, radius });
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_getColliderDensity(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+    ) -> jdouble {
+        let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
+        let ffi = ColliderFFI::from_jobject(&mut env, &collider_obj).ok().unwrap();
+
+        if let Some(col) = get_collider(&physics, &ffi) {
+            col.density() as jdouble
+        } else {
+            0.0
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_setColliderDensity(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+        density: jdouble,
+    ) {
+        let physics = crate::convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Ok(ffi) = ColliderFFI::from_jobject(&mut env, &collider_obj) {
+            if let Some(col) = get_collider_mut(physics, &ffi) {
+                col.set_density(density as f32);
+            }
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_getColliderFriction(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+    ) -> jdouble {
+        let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
+        let ffi = ColliderFFI::from_jobject(&mut env, &collider_obj).ok().unwrap();
+        if let Some(col) = get_collider(&physics, &ffi) {
+            col.friction() as jdouble
+        } else { 0.0 }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_setColliderFriction(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+        friction: jdouble,
+    ) {
+        let physics = crate::convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Ok(ffi) = ColliderFFI::from_jobject(&mut env, &collider_obj) {
+            if let Some(col) = get_collider_mut(physics, &ffi) {
+                col.set_friction(friction as f32);
+            }
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_getColliderRestitution(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+    ) -> jdouble {
+        let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
+        let ffi = ColliderFFI::from_jobject(&mut env, &collider_obj).ok().unwrap();
+        if let Some(col) = get_collider(&physics, &ffi) {
+            col.restitution() as jdouble
+        } else { 0.0 }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_setColliderRestitution(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+        restitution: jdouble,
+    ) {
+        let physics = crate::convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Ok(ffi) = ColliderFFI::from_jobject(&mut env, &collider_obj) {
+            if let Some(col) = get_collider_mut(physics, &ffi) {
+                col.set_restitution(restitution as f32);
+            }
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_getColliderMass(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+    ) -> jdouble {
+        let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
+        let ffi = ColliderFFI::from_jobject(&mut env, &collider_obj).ok().unwrap();
+        if let Some(col) = get_collider(&physics, &ffi) {
+            col.mass() as jdouble
+        } else { 0.0 }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_setColliderMass(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+        mass: jdouble,
+    ) {
+        let physics = crate::convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Ok(ffi) = ColliderFFI::from_jobject(&mut env, &collider_obj) {
+            if let Some(col) = get_collider_mut(physics, &ffi) {
+                col.set_mass(mass as f32);
+            }
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_getColliderIsSensor(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+    ) -> jboolean {
+        let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
+        let ffi = ColliderFFI::from_jobject(&mut env, &collider_obj).ok().unwrap();
+        if let Some(col) = get_collider(&physics, &ffi) {
+            if col.is_sensor() { 1 } else { 0 }
+        } else { 0 }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_setColliderIsSensor(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+        is_sensor: jboolean,
+    ) {
+        let physics = crate::convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Ok(ffi) = ColliderFFI::from_jobject(&mut env, &collider_obj) {
+            if let Some(col) = get_collider_mut(physics, &ffi) {
+                col.set_sensor(is_sensor != 0);
+            }
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_getColliderTranslation(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+    ) -> jobject {
+        let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
+        let ffi = match ColliderFFI::from_jobject(&mut env, &collider_obj) {
+            Ok(v) => v,
+            Err(_) => return std::ptr::null_mut(),
+        };
+
+        if let Some(col) = get_collider(&physics, &ffi) {
+            let t: &Vector3<f32> = col.translation();
+            let vec = crate::types::Vector3::new(t.x as f64, t.y as f64, t.z as f64);
+            match vec.to_jobject(&mut env) {
+                Ok(o) => o.into_raw(),
+                Err(_) => std::ptr::null_mut(),
+            }
+        } else {
+            std::ptr::null_mut()
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_setColliderTranslation(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+        vec_obj: JObject,
+    ) {
+        let physics = crate::convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Ok(ffi) = ColliderFFI::from_jobject(&mut env, &collider_obj) {
+            if let Ok(vec) = crate::types::Vector3::from_jobject(&mut env, &vec_obj) {
+                if let Some(col) = get_collider_mut(physics, &ffi) {
+                    let t = Vector3::new(vec.x as f32, vec.y as f32, vec.z as f32);
+                    col.set_translation(t);
+                }
             }
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_getColliderRotation(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+    ) -> jobject {
+        let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
+        let ffi = match ColliderFFI::from_jobject(&mut env, &collider_obj) {
+            Ok(v) => v,
+            Err(_) => return std::ptr::null_mut(),
+        };
+
+        if let Some(col) = get_collider(&physics, &ffi) {
+            let r: &UnitQuaternion<f32> = col.rotation();
+            let q = DQuat::from_xyzw(r.i as f64, r.j as f64, r.k as f64, r.w as f64);
+            let euler = q.to_euler(glam::EulerRot::XYZ);
+            let vec = crate::types::Vector3::new(euler.0, euler.1, euler.2);
+
+            match vec.to_jobject(&mut env) {
+                Ok(o) => o.into_raw(),
+                Err(_) => std::ptr::null_mut(),
+            }
+        } else {
+            std::ptr::null_mut()
+        }
+    }
 
-            if env.is_instance_of(obj, "com/dropbear/physics/ColliderShape$Cylinder")? {
-                let half_height = env.get_field(obj, "halfHeight", "F")?.f()?;
-                let radius = env.get_field(obj, "radius", "F")?.f()?;
-                return Ok(ColliderShape::Cylinder { half_height, radius });
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_setColliderRotation(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+        vec_obj: JObject,
+    ) {
+        let physics = crate::convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Ok(ffi) = ColliderFFI::from_jobject(&mut env, &collider_obj) {
+            if let Ok(vec) = crate::types::Vector3::from_jobject(&mut env, &vec_obj) {
+                if let Some(col) = get_collider_mut(physics, &ffi) {
+                    let q = DQuat::from_euler(glam::EulerRot::XYZ, vec.x, vec.y, vec.z);
+                    let r = rapier3d::na::UnitQuaternion::new_normalize(
+                        rapier3d::na::Quaternion::new(q.w as f32, q.x as f32, q.y as f32, q.z as f32)
+                    );
+                    col.set_rotation(r);
+                }
             }
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+    use crate::convert_ptr;
+    use crate::physics::collider::shared::{get_collider, get_collider_mut};
+    use crate::physics::PhysicsState;
+    use crate::ptr::PhysicsStatePtr;
+    use crate::types::Vector3;
+
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+    use crate::types::{ColliderFFI, ColliderShapeFFI, ColliderShapeType};
+    use glam::DQuat;
+    use rapier3d::na::{Quaternion, UnitQuaternion};
+    use rapier3d::prelude::{SharedShape, TypedShape};
+
+    pub fn dropbear_get_collider_shape(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+    ) -> DropbearNativeResult<ColliderShapeFFI> {
+        let physics = convert_ptr!(physics_ptr => PhysicsState);
+
+        if let Some(collider) = get_collider(physics, &ffi) {
+            let rapier_shape = collider.shape();
+            let mut result = ColliderShapeFFI {
+                shape_type: ColliderShapeType::Box,
+                radius: 0.0, half_height: 0.0,
+                half_extents_x: 0.0, half_extents_y: 0.0, half_extents_z: 0.0,
+            };
 
-            if env.is_instance_of(obj, "com/dropbear/physics/ColliderShape$Cone")? {
-                let half_height = env.get_field(obj, "halfHeight", "F")?.f()?;
-                let radius = env.get_field(obj, "radius", "F")?.f()?;
-                return Ok(ColliderShape::Cone { half_height, radius });
+            match rapier_shape.as_typed_shape() {
+                TypedShape::Cuboid(c) => {
+                    result.shape_type = ColliderShapeType::Box;
+                    result.half_extents_x = c.half_extents.x;
+                    result.half_extents_y = c.half_extents.y;
+                    result.half_extents_z = c.half_extents.z;
+                },
+                TypedShape::Ball(b) => {
+                    result.shape_type = ColliderShapeType::Sphere;
+                    result.radius = b.radius;
+                },
+                TypedShape::Capsule(c) => {
+                    result.shape_type = ColliderShapeType::Capsule;
+                    result.radius = c.radius;
+                    result.half_height = c.segment.length() * 0.5;
+                },
+                TypedShape::Cylinder(c) => {
+                    result.shape_type = ColliderShapeType::Cylinder;
+                    result.radius = c.radius;
+                    result.half_height = c.half_height;
+                },
+                TypedShape::Cone(c) => {
+                    result.shape_type = ColliderShapeType::Cone;
+                    result.radius = c.radius;
+                    result.half_height = c.half_height;
+                },
+                _ => return DropbearNativeResult::Err(DropbearNativeError::GenericError),
             }
+            DropbearNativeResult::Ok(result)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_set_collider_shape(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+        shape: ColliderShapeFFI,
+    ) -> DropbearNativeResult<()> {
+        let physics = convert_ptr!(mut physics_ptr => PhysicsState);
+
+        if let Some(collider) = get_collider_mut(physics, &ffi) {
+            let new_shape = match shape.shape_type {
+                ColliderShapeType::Box => SharedShape::cuboid(shape.half_extents_x, shape.half_extents_y, shape.half_extents_z),
+                ColliderShapeType::Sphere => SharedShape::ball(shape.radius),
+                ColliderShapeType::Capsule => SharedShape::capsule_y(shape.half_height, shape.radius),
+                ColliderShapeType::Cylinder => SharedShape::cylinder(shape.half_height, shape.radius),
+                ColliderShapeType::Cone => SharedShape::cone(shape.half_height, shape.radius),
+            };
+            collider.set_shape(new_shape);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchHandle)
+        }
+    }
+
+    pub fn dropbear_get_collider_density(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+    ) -> DropbearNativeResult<f64> {
+        let physics = convert_ptr!(physics_ptr => PhysicsState);
+        if let Some(col) = get_collider(physics, &ffi) {
+            DropbearNativeResult::Ok(col.density() as f64)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_set_collider_density(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+        density: f64,
+    ) -> DropbearNativeResult<()> {
+        let physics = convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Some(col) = get_collider_mut(physics, &ffi) {
+            col.set_density(density as f32);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_get_collider_friction(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+    ) -> DropbearNativeResult<f64> {
+        let physics = convert_ptr!(physics_ptr => PhysicsState);
+        if let Some(col) = get_collider(physics, &ffi) {
+            DropbearNativeResult::Ok(col.friction() as f64)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_set_collider_friction(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+        friction: f64,
+    ) -> DropbearNativeResult<()> {
+        let physics = convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Some(col) = get_collider_mut(physics, &ffi) {
+            col.set_friction(friction as f32);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_get_collider_restitution(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+    ) -> DropbearNativeResult<f64> {
+        let physics = convert_ptr!(physics_ptr => PhysicsState);
+        if let Some(col) = get_collider(physics, &ffi) {
+            DropbearNativeResult::Ok(col.restitution() as f64)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_set_collider_restitution(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+        restitution: f64,
+    ) -> DropbearNativeResult<()> {
+        let physics = convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Some(col) = get_collider_mut(physics, &ffi) {
+            col.set_restitution(restitution as f32);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_get_collider_mass(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+    ) -> DropbearNativeResult<f64> {
+        let physics = convert_ptr!(physics_ptr => PhysicsState);
+        if let Some(col) = get_collider(physics, &ffi) {
+            DropbearNativeResult::Ok(col.mass() as f64)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_set_collider_mass(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+        mass: f64,
+    ) -> DropbearNativeResult<()> {
+        let physics = convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Some(col) = get_collider_mut(physics, &ffi) {
+            col.set_mass(mass as f32);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_get_collider_is_sensor(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+    ) -> DropbearNativeResult<bool> {
+        let physics = convert_ptr!(physics_ptr => PhysicsState);
+        if let Some(col) = get_collider(physics, &ffi) {
+            DropbearNativeResult::Ok(col.is_sensor())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_set_collider_is_sensor(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+        is_sensor: bool,
+    ) -> DropbearNativeResult<()> {
+        let physics = convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Some(col) = get_collider_mut(physics, &ffi) {
+            col.set_sensor(is_sensor);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_get_collider_translation(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+    ) -> DropbearNativeResult<Vector3> {
+        let physics = convert_ptr!(physics_ptr => PhysicsState);
+        if let Some(col) = get_collider(physics, &ffi) {
+            let t = col.translation();
+            DropbearNativeResult::Ok(Vector3 { x: t.x as f64, y: t.y as f64, z: t.z as f64 })
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_set_collider_translation(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+        translation: Vector3,
+    ) -> DropbearNativeResult<()> {
+        let physics = convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Some(col) = get_collider_mut(physics, &ffi) {
+            let t = rapier3d::na::Vector3::new(translation.x as f32, translation.y as f32, translation.z as f32);
+            col.set_translation(t);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_get_collider_rotation(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+    ) -> DropbearNativeResult<Vector3> {
+        let physics = convert_ptr!(physics_ptr => PhysicsState);
+        if let Some(col) = get_collider(physics, &ffi) {
+            let r = col.rotation();
+            let q = DQuat::from_xyzw(r.i as f64, r.j as f64, r.k as f64, r.w as f64);
+            let (x, y, z) = q.to_euler(glam::EulerRot::XYZ);
+            DropbearNativeResult::Ok(Vector3 { x, y, z })
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
 
-            env.throw_new("java/lang/IllegalArgumentException", "Unknown or null ColliderShape subclass type")?;
-            Err(anyhow::anyhow!("Unknown ColliderShape type"))
+    pub fn dropbear_set_collider_rotation(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+        rotation: Vector3,
+    ) -> DropbearNativeResult<()> {
+        let physics = convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Some(col) = get_collider_mut(physics, &ffi) {
+            let q = DQuat::from_euler(glam::EulerRot::XYZ, rotation.x, rotation.y, rotation.z);
+            let r = UnitQuaternion::new_normalize(
+                Quaternion::new(q.w as f32, q.x as f32, q.y as f32, q.z as f32)
+            );
+            col.set_rotation(r);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
         }
     }
 }
\ No newline at end of file
diff --git a/eucalyptus-core/src/physics/collider/collidergroup.rs b/eucalyptus-core/src/physics/collider/collidergroup.rs
new file mode 100644
index 0000000..9ca2f16
--- /dev/null
+++ b/eucalyptus-core/src/physics/collider/collidergroup.rs
@@ -0,0 +1,252 @@
+//! Scripting module for collider groups. 
+
+use crate::scripting::jni::utils::{FromJObject, ToJObject};
+use crate::scripting::native::DropbearNativeError;
+use crate::scripting::result::DropbearNativeResult;
+use crate::types::IndexNative;
+use ::jni::objects::{JObject, JValue};
+use ::jni::JNIEnv;
+
+impl ToJObject for IndexNative {
+    fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
+        let cls = env.find_class("com/dropbear/physics/Index")
+            .map_err(|e| {
+                eprintln!("[JNI Error] Could not find Index class: {:?}", e);
+                DropbearNativeError::GenericError
+            })?;
+
+        let obj = env.new_object(
+            cls,
+            "(II)V",
+            &[
+                JValue::Int(self.index as i32),
+                JValue::Int(self.generation as i32)
+            ]
+        ).map_err(|e| {
+            eprintln!("[JNI Error] Failed to create Index object: {:?}", e);
+            DropbearNativeError::GenericError
+        })?;
+
+        Ok(obj)
+    }
+}
+
+impl FromJObject for IndexNative {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self>
+    where
+        Self: Sized
+    {
+        let idx_val = env.get_field(obj, "index", "I")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .i()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let gen_val = env.get_field(obj, "generation", "I")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .i()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        Ok(IndexNative {
+            index: idx_val as u32,
+            generation: gen_val as u32,
+        })
+    }
+}
+
+pub mod shared {
+    use crate::physics::collider::ColliderGroup;
+    use hecs::{Entity, World};
+
+    pub fn collider_group_exists_for_entity(world: &World, entity: Entity) -> bool {
+        world.get::<&ColliderGroup>(entity).is_ok()
+    }
+}
+
+pub mod jni {
+    #![allow(non_snake_case)]
+    use crate::physics::collider::ColliderGroup;
+    use crate::types::{ColliderFFI, IndexNative};
+    use hecs::World;
+    use jni::objects::{JClass, JObject};
+    use jni::sys::{jboolean, jlong, jobjectArray};
+    use jni::JNIEnv;
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderGroupNative_colliderGroupExistsForEntity(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jboolean {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+
+        if super::shared::collider_group_exists_for_entity(&world, entity) {
+            true.into()
+        } else {
+            false.into()
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderGroupNative_getColliderGroupColliders(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        physics_ptr: jlong,
+        entity_id: jlong,
+    ) -> jobjectArray {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let physics = crate::convert_ptr!(physics_ptr => crate::physics::PhysicsState);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+
+        // We check if the ColliderGroup component exists first
+        if world.get::<&ColliderGroup>(entity).is_ok() {
+            let handles_opt = physics.entity_label_map
+                .get(&entity)
+                .and_then(|label| physics.colliders_entity_map.get(label));
+
+            let mut colliders: Vec<ColliderFFI> = Vec::new();
+
+            if let Some(handles) = handles_opt {
+                for (_, handle) in handles {
+                    let (idx, generation) = handle.into_raw_parts();
+
+                    let col = ColliderFFI {
+                        index: IndexNative {
+                            index: idx,
+                            generation,
+                        },
+                        entity_id: entity_id as u64,
+                        id: idx,
+                    };
+                    colliders.push(col);
+                }
+            }
+
+            let collider_cls = match env.find_class("com/dropbear/physics/Collider") {
+                Ok(cls) => cls,
+                Err(e) => {
+                    eprintln!("[JNI Error] Could not find Collider class: {:?}", e);
+                    return std::ptr::null_mut();
+                }
+            };
+
+            let output_array = match env.new_object_array(colliders.len() as i32, &collider_cls, JObject::null()) {
+                Ok(arr) => arr,
+                Err(e) => {
+                    eprintln!("[JNI Error] Failed to allocate Collider array: {:?}", e);
+                    let _ = env.throw_new("java/lang/OutOfMemoryError", "Could not allocate collider array");
+                    return std::ptr::null_mut();
+                }
+            };
+
+            use crate::scripting::jni::utils::ToJObject;
+
+            for (i, ffi) in colliders.iter().enumerate() {
+                let java_obj = match ffi.to_jobject(&mut env) {
+                    Ok(obj) => obj,
+                    Err(_) => return std::ptr::null_mut(),
+                };
+
+                if let Err(e) = env.set_object_array_element(&output_array, i as i32, java_obj) {
+                    eprintln!("[JNI Error] Failed to set array element: {:?}", e);
+                    return std::ptr::null_mut();
+                }
+            }
+
+            output_array.into_raw()
+
+        } else {
+            let _ = env.throw_new("java/lang/RuntimeException", "Entity does not have a ColliderGroup component");
+            std::ptr::null_mut()
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+    use crate::convert_ptr;
+    use crate::physics::collider::ColliderGroup;
+    use crate::physics::PhysicsState;
+    use crate::ptr::{PhysicsStatePtr, WorldPtr};
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+    use crate::types::{ColliderFFI, IndexNative};
+    use hecs::Entity;
+
+    pub fn dropbear_collider_group_exists_for_entity(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<bool> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        DropbearNativeResult::Ok(super::shared::collider_group_exists_for_entity(world, entity))
+    }
+
+    pub fn dropbear_get_collider_group_colliders(
+        world_ptr: WorldPtr,
+        physics_ptr: PhysicsStatePtr,
+        entity_id: u64,
+        out_count: *mut usize,
+    ) -> DropbearNativeResult<*mut ColliderFFI> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let physics = convert_ptr!(physics_ptr => PhysicsState);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if out_count.is_null() {
+            return DropbearNativeResult::Err(DropbearNativeError::NullPointer);
+        }
+
+        if world.get::<&ColliderGroup>(entity).is_err() {
+            unsafe { *out_count = 0; }
+            return DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent);
+        }
+
+        let handles_opt = physics.entity_label_map
+            .get(&entity)
+            .and_then(|label| physics.colliders_entity_map.get(label));
+
+        let mut colliders: Vec<ColliderFFI> = Vec::new();
+
+        if let Some(handles) = handles_opt {
+            for (_, handle) in handles {
+                let (idx, generation) = handle.into_raw_parts();
+
+                let col = ColliderFFI {
+                    index: IndexNative {
+                        index: idx,
+                        generation,
+                    },
+                    entity_id,
+                    id: idx,
+                };
+                colliders.push(col);
+            }
+        }
+
+        unsafe { *out_count = colliders.len(); }
+
+        colliders.shrink_to_fit();
+        let ptr = colliders.as_mut_ptr();
+        std::mem::forget(colliders);
+
+        DropbearNativeResult::Ok(ptr)
+    }
+
+    pub fn dropbear_free_collider_array(
+        ptr: *mut ColliderFFI,
+        count: usize,
+    ) -> DropbearNativeResult<()> {
+        if ptr.is_null() {
+            return DropbearNativeResult::Ok(());
+        }
+
+        unsafe {
+            let _ = Vec::from_raw_parts(ptr, count, count);
+        }
+
+        DropbearNativeResult::Ok(())
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/physics/rigidbody.rs b/eucalyptus-core/src/physics/rigidbody.rs
index ce83bbe..62a2e6c 100644
--- a/eucalyptus-core/src/physics/rigidbody.rs
+++ b/eucalyptus-core/src/physics/rigidbody.rs
@@ -1,15 +1,24 @@
-use serde::{Deserialize, Serialize};
+//! [rapier3d] RigidBodies
+
+use crate::scripting::jni::utils::{FromJObject, ToJObject};
+use crate::scripting::native::DropbearNativeError;
+use crate::scripting::result::DropbearNativeResult;
+use crate::states::Label;
 use dropbear_macro::SerializableComponent;
 use dropbear_traits::SerializableComponent;
-use crate::states::Label;
+use ::jni::objects::{JObject, JValue};
+use ::jni::JNIEnv;
+use rapier3d::prelude::RigidBodyType;
+use serde::{Deserialize, Serialize};
 
 /// How this entity behaves in the physics simulation.
 ///
 /// This intentionally mirrors Rapier's rigid-body types, but stays engine-owned and serializable.
+#[repr(C)]
 #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
 pub enum RigidBodyMode {
 	/// A fully simulated body affected by forces and contacts.
-	Dynamic,
+	Dynamic = 0,
 	/// An immovable body.
 	Fixed,
 	/// A kinematic body controlled by setting its next position.
@@ -24,6 +33,18 @@ impl Default for RigidBodyMode {
 	}
 }
 
+impl From<RigidBodyType> for RigidBodyMode {
+	fn from(value: RigidBodyType) -> Self {
+		match value {
+			RigidBodyType::Dynamic => RigidBodyMode::Dynamic,
+			RigidBodyType::Fixed => Self::Fixed,
+			RigidBodyType::KinematicPositionBased => Self::KinematicPosition,
+			RigidBodyType::KinematicVelocityBased => Self::KinematicVelocity,
+		}
+	}
+}
+
+#[repr(C)]
 #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default)]
 pub struct AxisLock {
 	pub x: bool,
@@ -31,6 +52,59 @@ pub struct AxisLock {
 	pub z: bool,
 }
 
+impl FromJObject for AxisLock {
+	fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self>
+	where
+		Self: Sized
+	{
+		let class = env.find_class("com/dropbear/physics/AxisLock")
+			.map_err(|_| DropbearNativeError::JNIClassNotFound)?;
+
+		if !env.is_instance_of(obj, &class)
+			.map_err(|_| DropbearNativeError::JNIUnwrapFailed)?
+		{
+			return Err(DropbearNativeError::InvalidArgument);
+		}
+
+		let x = env.get_field(obj, "x", "Z")
+			.map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+			.z()
+			.map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+		let y = env.get_field(obj, "y", "Z")
+			.map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+			.z()
+			.map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+		let z = env.get_field(obj, "z", "Z")
+			.map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+			.z()
+			.map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+		Ok(AxisLock { x, y, z })
+	}
+}
+
+impl ToJObject for AxisLock {
+	fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
+		let class = env.find_class("com/dropbear/physics/AxisLock")
+			.map_err(|_| DropbearNativeError::JNIClassNotFound)?;
+
+		let constructor_sig = "(ZZZ)V";
+
+		let args = [
+			JValue::Bool(self.x as u8),
+			JValue::Bool(self.y as u8),
+			JValue::Bool(self.z as u8),
+		];
+
+		let obj = env.new_object(&class, constructor_sig, &args)
+			.map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
+
+		Ok(obj)
+	}
+}
+
 /// A serializable physics rigid-body component.
 ///
 /// Notes:
@@ -56,8 +130,12 @@ pub struct RigidBody {
 	#[serde(default = "RigidBody::default_gravity_scale")]
 	pub gravity_scale: f32,
 
+	#[serde(default)]
+	/// If the rigidbody is currently sleeping or not.
+	pub sleeping: bool,
+
 	/// Whether this body is allowed to sleep.
-	#[serde(default = "RigidBody::default_can_sleep")]
+	#[serde(default = "RigidBody::default_sleep")]
 	pub can_sleep: bool,
 
 	/// Whether continuous collision detection is enabled.
@@ -96,7 +174,8 @@ impl Default for RigidBody {
 			disable_physics: false,
 			mode: RigidBodyMode::default(),
 			gravity_scale: Self::default_gravity_scale(),
-			can_sleep: Self::default_can_sleep(),
+			sleeping: false,
+			can_sleep: Self::default_sleep(),
 			ccd_enabled: false,
 			linvel: [0.0, 0.0, 0.0],
 			angvel: [0.0, 0.0, 0.0],
@@ -113,89 +192,1096 @@ impl RigidBody {
 		1.0
 	}
 
-	const fn default_can_sleep() -> bool {
+	const fn default_sleep() -> bool {
 		true
 	}
 }
 
-pub mod jni {
-	use glam::DVec3;
-	use jni::objects::{JObject, JString};
-	use jni::JNIEnv;
-	use rapier3d::prelude::RigidBodyHandle;
+pub mod shared {
 	use crate::physics::rigidbody::{AxisLock, RigidBody, RigidBodyMode};
-	use crate::scripting::jni::utils::FromJObject;
+	use crate::physics::PhysicsState;
+	use crate::scripting::native::DropbearNativeError;
+	use crate::scripting::result::DropbearNativeResult;
 	use crate::states::Label;
+	use crate::types::{IndexNative, RigidBodyContext, Vector3};
+	use hecs::{Entity, World};
+	use rapier3d::dynamics::{RigidBodyHandle, RigidBodyType};
+	use rapier3d::prelude::vector;
+	use rapier3d::prelude::{nalgebra, ColliderHandle, LockedAxes};
 
-	impl FromJObject for AxisLock {
-		fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> anyhow::Result<Self>
-		where
-			Self: Sized
+	pub fn rigid_body_exists_for_entity(
+		world: &World,
+		physics: &PhysicsState,
+		entity: Entity
+	) -> Option<IndexNative> {
+		if let Ok(mut q) = world.query_one::<(&Label, &RigidBody)>(entity)
+			&& let Some((label, _)) = q.get()
 		{
-			let x = env.get_field(obj, "x", "Z")?.z()?;
-			let y = env.get_field(obj, "y", "Z")?.z()?;
-			let z = env.get_field(obj, "z", "Z")?.z()?;
-			Ok(AxisLock { x, y, z })
+			if let Some(handle) = physics.bodies_entity_map.get(label) {
+				Some(IndexNative::from(handle.0))
+			} else {
+				None
+			}
+		} else {
+			None
+		}
+	}
+
+	pub fn get_rigidbody_type(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<RigidBodyType> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get(handle) {
+			Ok(rb.body_type())
+		} else {
+			Err(DropbearNativeError::PhysicsObjectNotFound)
+		}
+	}
+
+	pub fn set_rigidbody_type(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, mode: i64) -> DropbearNativeResult<()> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get_mut(handle) {
+			let mode = match mode {
+				0 => RigidBodyType::Dynamic,
+				1 => RigidBodyType::Fixed,
+				2 => RigidBodyType::KinematicPositionBased,
+				3 => RigidBodyType::KinematicVelocityBased,
+				_ => { return Err(DropbearNativeError::InvalidArgument); }
+			};
+			rb.set_body_type(mode, true);
+
+			let entity = Entity::from_bits(rb_context.entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+			if let Ok(mut q) = world.query_one::<&mut RigidBody>(entity) && let Some(rb) = q.get() {
+				let rb_mode = RigidBodyMode::from(mode);
+				rb.mode = rb_mode;
+			}
+
+			Ok(())
+		} else {
+			Err(DropbearNativeError::NoSuchHandle)
+		}
+	}
+
+	pub fn get_rigidbody_gravity_scale(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<f64> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get(handle) {
+			Ok(rb.gravity_scale().into())
+		} else {
+			Err(DropbearNativeError::PhysicsObjectNotFound)
+		}
+	}
+	
+	pub fn set_rigidbody_gravity_scale(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new_scale: f64) -> DropbearNativeResult<()> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get_mut(handle) {
+			rb.set_gravity_scale(new_scale as f32, true);
+			
+			let entity = Entity::from_bits(rb_context.entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+			if let Ok(mut q) = world.query_one::<&mut RigidBody>(entity) && let Some(rb) = q.get() {
+				rb.gravity_scale = new_scale as f32;				
+			}
+
+			Ok(())
+		} else {
+			Err(DropbearNativeError::NoSuchHandle)
+		}
+	}
+
+	pub fn get_rigidbody_linear_damping(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<f64> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get(handle) {
+			Ok(rb.linear_damping().into())
+		} else {
+			Err(DropbearNativeError::PhysicsObjectNotFound)
+		}
+	}
+
+	pub fn set_rigidbody_linear_damping(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: f64) -> DropbearNativeResult<()> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get_mut(handle) {
+			rb.set_linear_damping(new as f32);
+
+			let entity = Entity::from_bits(rb_context.entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+			if let Ok(mut q) = world.query_one::<&mut RigidBody>(entity) && let Some(rb) = q.get() {
+				rb.linear_damping = new as f32;
+			}
+
+			Ok(())
+		} else {
+			Err(DropbearNativeError::NoSuchHandle)
+		}
+	}
+
+	pub fn get_rigidbody_angular_damping(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<f64> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get(handle) {
+			Ok(rb.angular_damping().into())
+		} else {
+			Err(DropbearNativeError::PhysicsObjectNotFound)
 		}
 	}
 
-	impl RigidBody {
-		pub fn from_jni(env: &mut JNIEnv, obj: &JObject) -> anyhow::Result<(RigidBodyHandle, Self)> {
-			let entity_jobj = env.get_field(obj, "entity", "Ljava/lang/String;")?.l()?;
-			let entity_jstr: JString = entity_jobj.into();
-			let entity_str: String = env.get_string(&entity_jstr)?.into();
+	pub fn set_rigidbody_angular_damping(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: f64) -> DropbearNativeResult<()> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get_mut(handle) {
+			rb.set_angular_damping(new as f32);
+
+			let entity = Entity::from_bits(rb_context.entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+			if let Ok(mut q) = world.query_one::<&mut RigidBody>(entity) && let Some(rb) = q.get() {
+				rb.angular_damping = new as f32;
+			}
 
-			let entity = Label::from(entity_str);
+			Ok(())
+		} else {
+			Err(DropbearNativeError::NoSuchHandle)
+		}
+	}
 
-			let gravity_scale: f32 = env.get_field(obj, "gravityScale", "D")?.d()? as f32;
-			let can_sleep: bool = env.get_field(obj, "canSleep", "Z")?.z()?;
-			let ccd_enabled: bool = env.get_field(obj, "ccdEnabled", "Z")?.z()?;
-			let linear_damping: f32 = env.get_field(obj, "linearDamping", "D")?.d()? as f32;
-			let angular_damping: f32 = env.get_field(obj, "angularDamping", "D")?.d()? as f32;
+	pub fn get_rigidbody_sleep(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<bool> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get(handle) {
+			Ok(rb.is_sleeping().into())
+		} else {
+			Err(DropbearNativeError::PhysicsObjectNotFound)
+		}
+	}
 
-			let mode_obj = env.get_field(obj, "rigidBodyMode", "Lcom/dropbear/physics/RigidBodyMode;")?.l()?;
-			let mode_ordinal = env.call_method(&mode_obj, "ordinal", "()I", &[])?.i()?;
+	pub fn set_rigidbody_sleep(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: bool) -> DropbearNativeResult<()> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get_mut(handle) {
+			if new {
+				rb.sleep();
+			} else {
+				rb.wake_up(true);
+			}
 
-			let mode = match mode_ordinal {
-				0 => RigidBodyMode::Dynamic,
-				1 => RigidBodyMode::Fixed,
-				2 => RigidBodyMode::KinematicPosition,
-				3 => RigidBodyMode::KinematicVelocity,
-				_ => RigidBodyMode::Dynamic,
+			let entity = Entity::from_bits(rb_context.entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+			if let Ok(mut q) = world.query_one::<&mut RigidBody>(entity) && let Some(rb) = q.get() {
+				rb.sleeping = new;
+			}
+
+			Ok(())
+		} else {
+			Err(DropbearNativeError::NoSuchHandle)
+		}
+	}
+
+	pub fn get_rigidbody_ccd(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<bool> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get(handle) {
+			Ok(rb.is_ccd_enabled().into())
+		} else {
+			Err(DropbearNativeError::PhysicsObjectNotFound)
+		}
+	}
+
+	pub fn set_rigidbody_ccd(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: bool) -> DropbearNativeResult<()> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get_mut(handle) {
+			rb.enable_ccd(new);
+
+			let entity = Entity::from_bits(rb_context.entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+			if let Ok(mut q) = world.query_one::<&mut RigidBody>(entity) && let Some(rb) = q.get() {
+				rb.ccd_enabled = new;
+			}
+
+			Ok(())
+		} else {
+			Err(DropbearNativeError::NoSuchHandle)
+		}
+	}
+
+	pub fn get_rigidbody_linvel(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<Vector3> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get(handle) {
+			let linvel = rb.linvel().clone();
+			Ok(Vector3::new(linvel.x as f64, linvel.y as f64, linvel.z as f64))
+		} else {
+			Err(DropbearNativeError::PhysicsObjectNotFound)
+		}
+	}
+
+	pub fn set_rigidbody_linvel(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: Vector3) -> DropbearNativeResult<()> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get_mut(handle) {
+			rb.set_linvel(vector![new.x as f32, new.y as f32, new.z as f32], true);
+
+			let entity = Entity::from_bits(rb_context.entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+			if let Ok(mut q) = world.query_one::<&mut RigidBody>(entity) && let Some(rb) = q.get() {
+				rb.linvel = [new.x as f32, new.y as f32, new.z as f32];
+			}
+
+			Ok(())
+		} else {
+			Err(DropbearNativeError::NoSuchHandle)
+		}
+	}
+
+	pub fn get_rigidbody_angvel(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<Vector3> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get(handle) {
+			let angvel = rb.angvel().clone();
+			Ok(Vector3::new(angvel.x as f64, angvel.y as f64, angvel.z as f64))
+		} else {
+			Err(DropbearNativeError::PhysicsObjectNotFound)
+		}
+	}
+
+	pub fn set_rigidbody_angvel(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: Vector3) -> DropbearNativeResult<()> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get_mut(handle) {
+			rb.set_angvel(vector![new.x as f32, new.y as f32, new.z as f32], true);
+
+			let entity = Entity::from_bits(rb_context.entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+			if let Ok(mut q) = world.query_one::<&mut RigidBody>(entity) && let Some(rb) = q.get() {
+				rb.angvel = [new.x as f32, new.y as f32, new.z as f32];
+			}
+
+			Ok(())
+		} else {
+			Err(DropbearNativeError::NoSuchHandle)
+		}
+	}
+
+	pub fn get_rigidbody_lock_translation(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<AxisLock> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get(handle) {
+			let lock = rb.locked_axes();
+			let translation_lock = AxisLock {
+				x: lock.contains(LockedAxes::TRANSLATION_LOCKED_X),
+				y: lock.contains(LockedAxes::TRANSLATION_LOCKED_Y),
+				z: lock.contains(LockedAxes::TRANSLATION_LOCKED_Z),
+			};
+			Ok(translation_lock)
+		} else {
+			Err(DropbearNativeError::PhysicsObjectNotFound)
+		}
+	}
+
+	pub fn set_rigidbody_lock_translation(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: AxisLock) -> DropbearNativeResult<()> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get_mut(handle) {
+			let mut bits = rb.locked_axes().clone();
+
+			bits.remove(LockedAxes::TRANSLATION_LOCKED_X);
+			bits.remove(LockedAxes::TRANSLATION_LOCKED_Y);
+			bits.remove(LockedAxes::TRANSLATION_LOCKED_Z);
+			
+			if new.x {
+				bits = bits | LockedAxes::TRANSLATION_LOCKED_X;
+			}
+			if new.y {
+				bits = bits | LockedAxes::TRANSLATION_LOCKED_Y;
+			}
+			if new.z {
+				bits = bits | LockedAxes::TRANSLATION_LOCKED_Z;
+			}
+			
+			rb.set_locked_axes(bits, true);
+			
+			let entity = Entity::from_bits(rb_context.entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+			if let Ok(mut q) = world.query_one::<&mut RigidBody>(entity) && let Some(rb) = q.get() {
+				rb.lock_translation = new;
+			}
+
+			Ok(())
+		} else {
+			Err(DropbearNativeError::NoSuchHandle)
+		}
+	}
+
+	pub fn get_rigidbody_lock_rotation(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<AxisLock> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get(handle) {
+			let lock = rb.locked_axes();
+			let rot_lock = AxisLock {
+				x: lock.contains(LockedAxes::ROTATION_LOCKED_X),
+				y: lock.contains(LockedAxes::ROTATION_LOCKED_Y),
+				z: lock.contains(LockedAxes::ROTATION_LOCKED_Z),
 			};
+			Ok(rot_lock)
+		} else {
+			Err(DropbearNativeError::PhysicsObjectNotFound)
+		}
+	}
+
+	pub fn set_rigidbody_lock_rotation(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: AxisLock) -> DropbearNativeResult<()> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get_mut(handle) {
+			let mut bits = rb.locked_axes().clone();
+			
+			bits.remove(LockedAxes::ROTATION_LOCKED_X);
+			bits.remove(LockedAxes::ROTATION_LOCKED_Y);
+			bits.remove(LockedAxes::ROTATION_LOCKED_Z);
+
+			if new.x {
+				bits = bits | LockedAxes::ROTATION_LOCKED_X;
+			}
+			if new.y {
+				bits = bits | LockedAxes::ROTATION_LOCKED_Y;
+			}
+			if new.z {
+				bits = bits | LockedAxes::ROTATION_LOCKED_Z;
+			}
+
+			rb.set_locked_axes(bits, true);
+
+			let entity = Entity::from_bits(rb_context.entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+			if let Ok(mut q) = world.query_one::<&mut RigidBody>(entity) && let Some(rb) = q.get() {
+				rb.lock_translation = new;
+			}
 
-			let index_obj = env.get_field(obj, "index", "Lcom/dropbear/physics/Index;")?.l()?;
-			let idx_val = env.get_field(&index_obj, "index", "I")?.i()? as u32;
-			let gen_val = env.get_field(&index_obj, "generation", "I")?.i()? as u32;
+			Ok(())
+		} else {
+			Err(DropbearNativeError::NoSuchHandle)
+		}
+	}
+
+	pub fn get_rigidbody_children(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<Vec<ColliderHandle>> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get(handle) {
+			let children = rb.colliders().to_vec();
+			Ok(children)
+		} else {
+			Err(DropbearNativeError::PhysicsObjectNotFound)
+		}
+	}
+
+	pub fn apply_impulse(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: Vector3) -> DropbearNativeResult<()> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get_mut(handle) {
+			rb.apply_impulse(vector![new.x as f32, new.y as f32, new.z as f32], true);
+
+			Ok(())
+		} else {
+			Err(DropbearNativeError::NoSuchHandle)
+		}
+	}
+
+	pub fn apply_torque_impulse(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: Vector3) -> DropbearNativeResult<()> {
+		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
+		if let Some(rb) = physics.bodies.get_mut(handle) {
+			rb.apply_torque_impulse(vector![new.x as f32, new.y as f32, new.z as f32], true);
+
+			Ok(())
+		} else {
+			Err(DropbearNativeError::NoSuchHandle)
+		}
+	}
+}
+
+pub mod jni {
+	#![allow(non_snake_case)]
+	use crate::physics::rigidbody::AxisLock;
+	use crate::physics::PhysicsState;
+	use crate::scripting::jni::utils::{FromJObject, ToJObject};
+	use crate::types::{IndexNative, RigidBodyContext, Vector3};
+	use crate::{convert_jlong_to_entity, convert_ptr};
+	use hecs::World;
+	use jni::objects::{JClass, JObject};
+	use jni::sys::{jboolean, jdouble, jint, jlong, jobject, jobjectArray, jsize};
+	use jni::JNIEnv;
+	use rapier3d::dynamics::RigidBodyType;
+
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_rigidBodyExistsForEntity(
+		mut env: JNIEnv,
+		_: JClass,
+		world_ptr: jlong,
+		physics_ptr: jlong,
+		entity_id: jlong,
+	) -> jobject {
+		let world = convert_ptr!(world_ptr => World);
+		let physics = convert_ptr!(physics_ptr => PhysicsState);
+		let entity = convert_jlong_to_entity!(entity_id);
+
+		match super::shared::rigid_body_exists_for_entity(world, physics, entity) {
+			Some(v) => {
+				match v.to_jobject(&mut env) {
+					Ok(val) => val.into_raw(),
+					Err(e) => {
+						eprintln!("Failed to create new Index jobject: {}", e);
+						let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to create new Index jobject: {}", e));
+						std::ptr::null_mut()
+					}
+				}
+			}
+			None => std::ptr::null_mut()
+		}
+	}
+
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_getRigidBodyMode(
+		mut env: JNIEnv,
+		_: JClass,
+		_world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+	) -> jint {
+		let physics = convert_ptr!(physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException", "Unable to convert com/dropbear/physics/RigidBody into a Rust rigidbody");
+			return -1 as jint;
+		};
+
+		match super::shared::get_rigidbody_type(physics, rb_context) {
+			Ok(v) => {
+				match v {
+					RigidBodyType::Dynamic => 0 as jint,
+					RigidBodyType::Fixed => 1 as jint,
+					RigidBodyType::KinematicPositionBased => 2 as jint,
+					RigidBodyType::KinematicVelocityBased => 3 as jint,
+				}
+			}
+			Err(e) => {
+				eprintln!("Failed to get RigidBody type: {}", e);
+				let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get RigidBody type: {}", e));
+				-1 as jint
+			}
+		}
+	}
+
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_setRigidBodyMode(
+		mut env: JNIEnv,
+		_: JClass,
+		world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+		mode: jint,
+	) {
+		let world = convert_ptr!(world_ptr => World);
+		let mut physics = convert_ptr!(mut physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext");
+			return;
+		};
+
+		if let Err(e) = super::shared::set_rigidbody_type(&mut physics, world, rb_context, mode as i64) {
+			eprintln!("Failed to set RigidBody Type: {}", e);
+			let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to set RigidBody type: {}", e));
+			return;
+		}
+	}
+
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_getRigidBodyGravityScale(
+		mut env: JNIEnv,
+		_: JClass,
+		_world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+	) -> jdouble {
+		let physics = convert_ptr!(physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext");
+			return -1 as jdouble;
+		};
+
+		match super::shared::get_rigidbody_gravity_scale(physics, rb_context) {
+			Ok(v) => v as jdouble,
+			Err(e) => {
+				eprintln!("Failed to get RigidBody component: {}", e);
+				let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get RigidBody component: {}", e));
+				-1 as jdouble
+			}
+		}
+	}
+
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_setRigidBodyGravityScale(
+		mut env: JNIEnv,
+		_: JClass,
+		world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+		gravity_scale: jdouble,
+	) {
+		let world = convert_ptr!(world_ptr => World);
+		let mut physics = convert_ptr!(mut physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext");
+			return;
+		};
+
+		if let Err(e) = super::shared::set_rigidbody_gravity_scale(&mut physics, world, rb_context, gravity_scale as f64) {
+			eprintln!("Failed to set RigidBody gravity scale: {}", e);
+			let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to set RigidBody gravity scale: {}", e));
+			return;
+		}
+	}
+
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_getRigidBodyLinearDamping(
+		mut env: JNIEnv,
+		_: JClass,
+		_world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+	) -> jdouble {
+		let physics = convert_ptr!(physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext");
+			return -1 as jdouble;
+		};
+
+		match super::shared::get_rigidbody_linear_damping(physics, rb_context) {
+			Ok(v) => v as jdouble,
+			Err(e) => {
+				eprintln!("Failed to get RigidBody linear damping: {}", e);
+				let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get RigidBody linear damping: {}", e));
+				-1 as jdouble
+			}
+		}
+	}
+
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_setRigidBodyLinearDamping(
+		mut env: JNIEnv,
+		_: JClass,
+		world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+		linear_damping: jdouble,
+	) {
+		let world = convert_ptr!(world_ptr => World);
+		let mut physics = convert_ptr!(mut physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext");
+			return;
+		};
+
+		if let Err(e) = super::shared::set_rigidbody_linear_damping(&mut physics, world, rb_context, linear_damping as f64) {
+			eprintln!("Failed to set RigidBody linear damping: {}", e);
+			let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to set RigidBody linear damping: {}", e));
+			return;
+		}
+	}
+
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_getRigidBodyAngularDamping(
+		mut env: JNIEnv,
+		_: JClass,
+		_world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+	) -> jdouble {
+		let physics = convert_ptr!(physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext");
+			return -1 as jdouble;
+		};
+
+		match super::shared::get_rigidbody_angular_damping(physics, rb_context) {
+			Ok(v) => v as jdouble,
+			Err(e) => {
+				eprintln!("Failed to get RigidBody angular damping: {}", e);
+				let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get RigidBody angular damping: {}", e));
+				-1 as jdouble
+			}
+		}
+	}
+
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_setRigidBodyAngularDamping(
+		mut env: JNIEnv,
+		_: JClass,
+		world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+		angular_damping: jdouble,
+	) {
+		let world = convert_ptr!(world_ptr => World);
+		let mut physics = convert_ptr!(mut physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext");
+			return;
+		};
+
+		if let Err(e) = super::shared::set_rigidbody_angular_damping(&mut physics, world, rb_context, angular_damping as f64) {
+			eprintln!("Failed to set RigidBody angular damping: {}", e);
+			let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to set RigidBody angular damping: {}", e));
+			return;
+		}
+	}
+
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_getRigidBodySleep(
+		mut env: JNIEnv,
+		_: JClass,
+		_world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+	) -> jboolean {
+		let physics = convert_ptr!(physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext");
+			return 0 as jboolean;
+		};
+
+		match super::shared::get_rigidbody_sleep(physics, rb_context) {
+			Ok(v) => v as jboolean,
+			Err(e) => {
+				eprintln!("Failed to get RigidBody sleep state: {}", e);
+				let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get RigidBody sleep state: {}", e));
+				0 as jboolean
+			}
+		}
+	}
 
-			let linvel_obj = env.get_field(obj, "linearVelocity", "Lcom/dropbear/math/Vector3D;")?.l()?;
-			let linvel = DVec3::from_jobject(env, &linvel_obj)?.as_vec3().to_array();
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_setRigidBodySleep(
+		mut env: JNIEnv,
+		_: JClass,
+		world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+		sleep: jboolean,
+	) {
+		let world = convert_ptr!(world_ptr => World);
+		let mut physics = convert_ptr!(mut physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext");
+			return;
+		};
 
-			let angvel_obj = env.get_field(obj, "angularVelocity", "Lcom/dropbear/math/Vector3D;")?.l()?;
-			let angvel = DVec3::from_jobject(env, &angvel_obj)?.as_vec3().to_array();
+		if let Err(e) = super::shared::set_rigidbody_sleep(&mut physics, world, rb_context, sleep != 0) {
+			eprintln!("Failed to set RigidBody sleep state: {}", e);
+			let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to set RigidBody sleep state: {}", e));
+			return;
+		}
+	}
+
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_getRigidBodyCcdEnabled(
+		mut env: JNIEnv,
+		_: JClass,
+		_world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+	) -> jboolean {
+		let physics = convert_ptr!(physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext");
+			return 0 as jboolean;
+		};
+
+		match super::shared::get_rigidbody_ccd(physics, rb_context) {
+			Ok(v) => v as jboolean,
+			Err(e) => {
+				eprintln!("Failed to get RigidBody CCD state: {}", e);
+				let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get RigidBody CCD state: {}", e));
+				0 as jboolean
+			}
+		}
+	}
 
-			let lock_trans_obj = env.get_field(obj, "lockTranslation", "Lcom/dropbear/physics/AxisLock;")?.l()?;
-			let lock_translation = AxisLock::from_jobject(env, &lock_trans_obj)?;
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_setRigidBodyCcdEnabled(
+		mut env: JNIEnv,
+		_: JClass,
+		world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+		ccd_enabled: jboolean,
+	) {
+		let world = convert_ptr!(world_ptr => World);
+		let mut physics = convert_ptr!(mut physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext");
+			return;
+		};
 
-			let lock_rot_obj = env.get_field(obj, "lockRotation", "Lcom/dropbear/physics/AxisLock;")?.l()?;
-			let lock_rotation = AxisLock::from_jobject(env, &lock_rot_obj)?;
+		if let Err(e) = super::shared::set_rigidbody_ccd(&mut physics, world, rb_context, ccd_enabled != 0) {
+			eprintln!("Failed to set RigidBody CCD state: {}", e);
+			let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to set RigidBody CCD state: {}", e));
+			return;
+		}
+	}
 
-			let handle = RigidBodyHandle::from_raw_parts(idx_val, gen_val);
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_getRigidBodyLinearVelocity(
+		mut env: JNIEnv,
+		_: JClass,
+		_world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+	) -> jobject {
+		let physics = convert_ptr!(physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext");
+			return std::ptr::null_mut();
+		};
 
-			Ok((handle, Self {
-				entity,
-				disable_physics: false,
-				mode,
-				gravity_scale,
-				can_sleep,
-				ccd_enabled,
-				linvel,
-				angvel,
-				linear_damping,
-				angular_damping,
-				lock_translation,
-				lock_rotation,
-			}))
+		match super::shared::get_rigidbody_linvel(physics, rb_context) {
+			Ok(v) => {
+				match v.to_jobject(&mut env) {
+					Ok(val) => val.into_raw(),
+					Err(e) => {
+						eprintln!("Failed to create Vector3d jobject: {}", e);
+						let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to create Vector3d jobject: {}", e));
+						std::ptr::null_mut()
+					}
+				}
+			}
+			Err(e) => {
+				eprintln!("Failed to get RigidBody linear velocity: {}", e);
+				let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get RigidBody linear velocity: {}", e));
+				std::ptr::null_mut()
+			}
 		}
 	}
+
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_setRigidBodyLinearVelocity(
+		mut env: JNIEnv,
+		_: JClass,
+		world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+		linear_velocity: JObject,
+	) {
+		let world = convert_ptr!(world_ptr => World);
+		let mut physics = convert_ptr!(mut physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext");
+			return;
+		};
+
+		let Ok(velocity) = Vector3::from_jobject(&mut env, &linear_velocity) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/math/Vector3d to rust Vector3");
+			return;
+		};
+
+		if let Err(e) = super::shared::set_rigidbody_linvel(&mut physics, world, rb_context, velocity) {
+			eprintln!("Failed to set RigidBody linear velocity: {}", e);
+			let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to set RigidBody linear velocity: {}", e));
+			return;
+		}
+	}
+
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_getRigidBodyAngularVelocity(
+		mut env: JNIEnv,
+		_: JClass,
+		_world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+	) -> jobject {
+		let physics = convert_ptr!(physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext");
+			return std::ptr::null_mut();
+		};
+
+		match super::shared::get_rigidbody_angvel(physics, rb_context) {
+			Ok(v) => {
+				match v.to_jobject(&mut env) {
+					Ok(val) => val.into_raw(),
+					Err(e) => {
+						eprintln!("Failed to create Vector3d jobject: {}", e);
+						let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to create Vector3d jobject: {}", e));
+						std::ptr::null_mut()
+					}
+				}
+			}
+			Err(e) => {
+				eprintln!("Failed to get RigidBody angular velocity: {}", e);
+				let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get RigidBody angular velocity: {}", e));
+				std::ptr::null_mut()
+			}
+		}
+	}
+
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_setRigidBodyAngularVelocity(
+		mut env: JNIEnv,
+		_: JClass,
+		world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+		angular_velocity: JObject,
+	) {
+		let world = convert_ptr!(world_ptr => World);
+		let mut physics = convert_ptr!(mut physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext");
+			return;
+		};
+
+		let Ok(velocity) = Vector3::from_jobject(&mut env, &angular_velocity) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/math/Vector3d to rust Vector3");
+			return;
+		};
+
+		if let Err(e) = super::shared::set_rigidbody_angvel(&mut physics, world, rb_context, velocity) {
+			eprintln!("Failed to set RigidBody angular velocity: {}", e);
+			let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to set RigidBody angular velocity: {}", e));
+			return;
+		}
+	}
+
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_getRigidBodyLockTranslation(
+		mut env: JNIEnv,
+		_: JClass,
+		_world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+	) -> jobject {
+		let physics = convert_ptr!(physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext");
+			return std::ptr::null_mut();
+		};
+
+		match super::shared::get_rigidbody_lock_translation(physics, rb_context) {
+			Ok(v) => {
+				match v.to_jobject(&mut env) {
+					Ok(val) => val.into_raw(),
+					Err(e) => {
+						eprintln!("Failed to create AxisLock jobject: {}", e);
+						let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to create AxisLock jobject: {}", e));
+						std::ptr::null_mut()
+					}
+				}
+			}
+			Err(e) => {
+				eprintln!("Failed to get RigidBody translation lock: {}", e);
+				let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get RigidBody translation lock: {}", e));
+				std::ptr::null_mut()
+			}
+		}
+	}
+
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_setRigidBodyLockTranslation(
+		mut env: JNIEnv,
+		_: JClass,
+		world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+		lock: JObject,
+	) {
+		let world = convert_ptr!(world_ptr => World);
+		let mut physics = convert_ptr!(mut physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext");
+			return;
+		};
+
+		let Ok(axis_lock) = AxisLock::from_jobject(&mut env, &lock) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/AxisLock to rust AxisLock");
+			return;
+		};
+
+		if let Err(e) = super::shared::set_rigidbody_lock_translation(&mut physics, world, rb_context, axis_lock) {
+			eprintln!("Failed to set RigidBody translation lock: {}", e);
+			let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to set RigidBody translation lock: {}", e));
+			return;
+		}
+	}
+
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_getRigidBodyLockRotation(
+		mut env: JNIEnv,
+		_: JClass,
+		_world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+	) -> jobject {
+		let physics = convert_ptr!(physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext");
+			return std::ptr::null_mut();
+		};
+
+		match super::shared::get_rigidbody_lock_rotation(physics, rb_context) {
+			Ok(v) => {
+				match v.to_jobject(&mut env) {
+					Ok(val) => val.into_raw(),
+					Err(e) => {
+						eprintln!("Failed to create AxisLock jobject: {}", e);
+						let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to create AxisLock jobject: {}", e));
+						std::ptr::null_mut()
+					}
+				}
+			}
+			Err(e) => {
+				eprintln!("Failed to get RigidBody rotation lock: {}", e);
+				let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get RigidBody rotation lock: {}", e));
+				std::ptr::null_mut()
+			}
+		}
+	}
+
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_setRigidBodyLockRotation(
+		mut env: JNIEnv,
+		_: JClass,
+		world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+		lock: JObject,
+	) {
+		let world = convert_ptr!(world_ptr => World);
+		let mut physics = convert_ptr!(mut physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext");
+			return;
+		};
+
+		let Ok(axis_lock) = AxisLock::from_jobject(&mut env, &lock) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/AxisLock to rust AxisLock");
+			return;
+		};
+
+		if let Err(e) = super::shared::set_rigidbody_lock_rotation(&mut physics, world, rb_context, axis_lock) {
+			eprintln!("Failed to set RigidBody rotation lock: {}", e);
+			let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to set RigidBody rotation lock: {}", e));
+			return;
+		}
+	}
+
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_getRigidBodyChildren(
+		mut env: JNIEnv,
+		_: JClass,
+		_world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+	) -> jobjectArray {
+		let physics = convert_ptr!(physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext");
+			return std::ptr::null_mut();
+		};
+
+		match super::shared::get_rigidbody_children(physics, rb_context) {
+			Ok(children) => {
+				let mut handles: Vec<JObject> = Vec::new();
+				for c in children {
+					match IndexNative::from(c.0).to_jobject(&mut env) {
+						Ok(val) => { handles.push(val); }
+						Err(_) => { continue; }
+					}
+				}
+
+				let array = match env.new_object_array(handles.len() as i32, "com/dropbear/physics/Collider", JObject::null()) {
+					Ok(array) => array,
+					Err(e) => {
+						eprintln!("Failed to create jlong array: {}", e);
+						let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to create jlong array: {}", e));
+						return std::ptr::null_mut();
+					}
+				};
+
+				for (i, h) in handles.iter().enumerate() {
+					if let Err(e) = env.set_object_array_element(&array, i as jsize, h) {
+						eprintln!("Failed to set jlong array region: {}", e);
+						let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to set jobject array region at index {}: {}", i, e));
+						return std::ptr::null_mut();
+					}
+				}
+
+				array.into_raw()
+			}
+			Err(e) => {
+				eprintln!("Failed to get RigidBody children: {}", e);
+				let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get RigidBody children: {}", e));
+				std::ptr::null_mut()
+			}
+		}
+	}
+
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_applyImpulse(
+		mut env: JNIEnv,
+		_: JClass,
+		world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+		x: jdouble,
+		y: jdouble,
+		z: jdouble,
+	) {
+		let world = convert_ptr!(world_ptr => World);
+		let mut physics = convert_ptr!(mut physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext");
+			return;
+		};
+
+		let impulse = Vector3::new(x as f64, y as f64, z as f64);
+		if let Err(e) = super::shared::apply_impulse(&mut physics, world, rb_context, impulse) {
+			eprintln!("Failed to apply impulse: {}", e);
+			let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to apply impulse: {}", e));
+			return;
+		}
+	}
+
+	#[unsafe(no_mangle)]
+	pub fn Java_com_dropbear_physics_RigidBodyNative_applyTorqueImpulse(
+		mut env: JNIEnv,
+		_: JClass,
+		world_ptr: jlong,
+		physics_ptr: jlong,
+		rigidbody: JObject,
+		x: jdouble,
+		y: jdouble,
+		z: jdouble,
+	) {
+		let world = convert_ptr!(world_ptr => World);
+		let mut physics = convert_ptr!(mut physics_ptr => PhysicsState);
+		let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else {
+			let _ = env.throw_new("java/lang/RuntimeException",
+								  "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext");
+			return;
+		};
+
+		let torque = Vector3::new(x as f64, y as f64, z as f64);
+		if let Err(e) = super::shared::apply_torque_impulse(&mut physics, world, rb_context, torque) {
+			eprintln!("Failed to apply torque impulse: {}", e);
+			let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to apply torque impulse: {}", e));
+			return;
+		}
+	}
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+	
 }
\ No newline at end of file
diff --git a/eucalyptus-core/src/properties.rs b/eucalyptus-core/src/properties.rs
new file mode 100644
index 0000000..c0c68cf
--- /dev/null
+++ b/eucalyptus-core/src/properties.rs
@@ -0,0 +1,795 @@
+use std::fmt;
+use std::fmt::{Display, Formatter};
+use serde::{Deserialize, Serialize};
+use dropbear_macro::SerializableComponent;
+use dropbear_traits::SerializableComponent;
+use egui::Ui;
+use crate::states::Property;
+
+/// Properties for an entity, typically queries with `entity.getProperty<Float>` and `entity.setProperty(21)`
+#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, SerializableComponent)]
+pub struct CustomProperties {
+    pub custom_properties: Vec<Property>,
+    pub next_id: u64,
+}
+
+impl CustomProperties {
+    /// Creates a new [CustomProperties]
+    pub fn new() -> Self {
+        Self {
+            custom_properties: Vec::new(),
+            next_id: 0,
+        }
+    }
+
+    /// Sets the property based on the [Value] (type) and its key.
+    ///
+    /// If the value does NOT exist, it will be created.
+    /// If the value does exist, it will replace the contents of that item.
+    pub fn set_property(&mut self, key: String, value: Value) {
+        if let Some(prop) = self.custom_properties.iter_mut().find(|p| p.key == key) {
+            prop.value = value;
+        } else {
+            self.custom_properties.push(Property {
+                id: self.next_id,
+                key,
+                value,
+            });
+            self.next_id += 1;
+        }
+    }
+
+    /// Fetches the property by its key.
+    pub fn get_property(&self, key: &str) -> Option<&Value> {
+        self.custom_properties
+            .iter()
+            .find(|p| p.key == key)
+            .map(|p| &p.value)
+    }
+
+    /// Fetches the float property
+    pub fn get_float(&self, key: &str) -> Option<f64> {
+        match self.get_property(key)? {
+            Value::Double(f) => Some(*f),
+            _ => None,
+        }
+    }
+
+    /// Fetches the integer property
+    pub fn get_int(&self, key: &str) -> Option<i64> {
+        match self.get_property(key)? {
+            Value::Int(i) => Some(*i),
+            _ => None,
+        }
+    }
+
+    /// Creates a new property based on a key and a value.
+    ///
+    /// It will push that value again to the property vector.
+    pub fn add_property(&mut self, key: String, value: Value) {
+        self.custom_properties.push(Property {
+            id: self.next_id,
+            key,
+            value,
+        });
+        self.next_id += 1;
+    }
+
+    /// Shows a template of the different values when inspected as a component in the editor.
+    pub fn show_value_editor(ui: &mut Ui, value: &mut Value) -> bool {
+        match value {
+            Value::String(s) => ui.text_edit_singleline(s).changed(),
+            Value::Int(i) => ui
+                .add(egui::Slider::new(i, -1000..=1000).text(""))
+                .changed(),
+            Value::Double(f) => ui
+                .add(egui::Slider::new(f, -100.0..=100.0).text(""))
+                .changed(),
+            Value::Bool(b) => ui.checkbox(b, "").changed(),
+            Value::Vec3(vec) => {
+                let mut changed = false;
+                ui.horizontal(|ui| {
+                    changed |= ui
+                        .add(
+                            egui::Slider::new(&mut vec[0], -10.0..=10.0)
+                                .text("X")
+                                .fixed_decimals(2),
+                        )
+                        .changed();
+                    changed |= ui
+                        .add(
+                            egui::Slider::new(&mut vec[1], -10.0..=10.0)
+                                .text("Y")
+                                .fixed_decimals(2),
+                        )
+                        .changed();
+                    changed |= ui
+                        .add(
+                            egui::Slider::new(&mut vec[2], -10.0..=10.0)
+                                .text("Z")
+                                .fixed_decimals(2),
+                        )
+                        .changed();
+                });
+                changed
+            }
+        }
+    }
+}
+
+impl Default for CustomProperties {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub enum Value {
+    String(String),
+    Int(i64),
+    Double(f64),
+    Bool(bool),
+    Vec3([f64; 3]),
+}
+
+impl Default for Value {
+    fn default() -> Self {
+        Self::String(String::new())
+    }
+}
+
+impl Display for Value {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        let string: String = match self {
+            Value::String(_) => "String".into(),
+            Value::Int(_) => "Int".into(),
+            Value::Double(_) => "Double".into(),
+            Value::Bool(_) => "Bool".into(),
+            Value::Vec3(_) => "Vec3".into(),
+        };
+        write!(f, "{}", string)
+    }
+}
+
+pub mod shared {
+    use std::ffi::CStr;
+    use std::os::raw::c_char;
+    use hecs::World;
+    use crate::properties::CustomProperties;
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+
+    pub fn custom_properties_exists_for_entity(world: &World, entity: hecs::Entity) -> bool {
+        world.get::<&CustomProperties>(entity).is_ok()
+    }
+
+    pub(crate) unsafe fn read_key(ptr: *const c_char) -> DropbearNativeResult<String> {
+        if ptr.is_null() {
+            return DropbearNativeResult::Err(DropbearNativeError::NullPointer);
+        }
+        match { CStr::from_ptr(ptr) }.to_str() {
+            Ok(s) => DropbearNativeResult::Ok(s.to_string()),
+            Err(_) => DropbearNativeResult::Err(DropbearNativeError::InvalidUTF8),
+        }
+    }
+}
+
+pub mod jni {
+    #![allow(non_snake_case)]
+    use hecs::World;
+    use jni::JNIEnv;
+    use jni::objects::{JClass, JObject, JString, JValue};
+    use jni::sys::{jboolean, jdouble, jfloat, jint, jlong, jobject, jstring};
+    use glam::DVec3;
+
+    use crate::properties::{CustomProperties, Value};
+    use crate::scripting::jni::utils::{FromJObject, ToJObject};
+    use crate::types::Vector3;
+
+    /// Returns a primitive that is boxed (long => java.lang.Long)
+    ///
+    /// ```
+    /// return_boxed!(&mut env, Some(JValue::Int(21 as jint)), "(I)Ljava/lang/Integer;", "java/lang/Integer")
+    /// ```
+    #[macro_export]
+    macro_rules! return_boxed {
+        ($env:expr, $val:expr, $sig:expr, $wrapper:expr) => {
+            match $val {
+                Some(v) => {
+                    let result = |env: &mut jni::JNIEnv| -> jni::errors::Result<jni::sys::jobject> {
+                        let cls = env.find_class($wrapper)?;
+
+                        let param: jni::objects::JValue = v.into();
+                        let ret = env.call_static_method(cls, "valueOf", $sig, &[param])?;
+
+                        Ok(ret.l()?.into_raw())
+                    }($env);
+
+                    match result {
+                        Ok(ptr) => ptr,
+                        Err(e) => {
+                            eprintln!("return_boxed failed for {}: {:?}", $wrapper, e);
+
+                            let _ = $env.throw_new("java/lang/RuntimeException", format!("Boxing failed: {:?}", e));
+
+                            std::ptr::null_mut()
+                        }
+                    }
+                }
+                None => std::ptr::null_mut(),
+            }
+        };
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_customPropertiesExistsForEntity(
+        _env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+    ) -> jboolean {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+
+        if super::shared::custom_properties_exists_for_entity(&world, entity) {
+            1
+        } else {
+            0
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_getStringProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+    ) -> jstring {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        if let Ok(props) = world.get::<&CustomProperties>(entity) {
+            if let Some(Value::String(s)) = props.get_property(&key_str) {
+                return env.new_string(s).map(|s| s.into_raw()).unwrap_or(std::ptr::null_mut());
+            }
+        }
+        std::ptr::null_mut()
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_getIntProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        let val = if let Ok(props) = world.get::<&CustomProperties>(entity) {
+            if let Some(Value::Int(v)) = props.get_property(&key_str) {
+                Some(JValue::Int(*v as jint))
+            } else { None }
+        } else { None };
+
+        return_boxed!(&mut env, val, "(I)Ljava/lang/Integer;", "java/lang/Integer")
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_getLongProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        let val = if let Ok(props) = world.get::<&CustomProperties>(entity) {
+            if let Some(Value::Int(v)) = props.get_property(&key_str) {
+                Some(JValue::Long(*v))
+            } else { None }
+        } else { None };
+
+        return_boxed!(&mut env, val, "(J)Ljava/lang/Long;", "java/lang/Long")
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_getDoubleProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        let val = if let Ok(props) = world.get::<&CustomProperties>(entity) {
+            if let Some(Value::Double(v)) = props.get_property(&key_str) {
+                Some(JValue::Double(*v))
+            } else { None }
+        } else { None };
+
+        return_boxed!(&mut env, val, "(D)Ljava/lang/Double;", "java/lang/Double")
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_getFloatProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        let val = if let Ok(props) = world.get::<&CustomProperties>(entity) {
+            if let Some(Value::Double(v)) = props.get_property(&key_str) {
+                Some(JValue::Float(*v as jfloat))
+            } else { None }
+        } else { None };
+
+        return_boxed!(&mut env, val, "(F)Ljava/lang/Float;", "java/lang/Float")
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_getBoolProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        let val = if let Ok(props) = world.get::<&CustomProperties>(entity) {
+            if let Some(Value::Bool(v)) = props.get_property(&key_str) {
+                Some(JValue::Bool(if *v { 1 } else { 0 }))
+            } else { None }
+        } else { None };
+
+        return_boxed!(&mut env, val, "(Z)Ljava/lang/Boolean;", "java/lang/Boolean")
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_getVec3Property(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        if let Ok(props) = world.get::<&CustomProperties>(entity) {
+            if let Some(Value::Vec3(v)) = props.get_property(&key_str) {
+                return match Vector3::from(*v).to_jobject(&mut env) {
+                    Ok(obj) => obj.into_raw(),
+                    Err(_) => std::ptr::null_mut()
+                };
+            }
+        }
+        std::ptr::null_mut()
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_setStringProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+        value: JString,
+    ) {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+        let val_str = crate::convert_jstring!(env, value);
+
+        if let Ok(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::String(val_str));
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_setIntProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+        value: jint,
+    ) {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        if let Ok(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Int(value as i64));
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_setLongProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+        value: jlong,
+    ) {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        if let Ok(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Int(value));
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_setFloatProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+        value: jfloat,
+    ) {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        if let Ok(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Double(value as f64));
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_setDoubleProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+        value: jdouble,
+    ) {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        if let Ok(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Double(value));
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_setBoolProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+        value: jboolean,
+    ) {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        if let Ok(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Bool(value != 0));
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_setVec3Property(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+        value: JObject,
+    ) {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        let vec_val = match Vector3::from_jobject(&mut env, &value) {
+            Ok(v) => v,
+            Err(_) => return,
+        };
+
+        if let Ok(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Vec3(vec_val.to_array()));
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+    use hecs::{Entity, World};
+    use std::ffi::CString;
+    use std::os::raw::c_char;
+
+    use crate::convert_ptr;
+    use crate::properties::shared::read_key;
+    use crate::properties::{CustomProperties, Value};
+    use crate::ptr::WorldPtr;
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+    use crate::types::Vector3;
+    
+    pub fn dropbear_custom_properties_exists_for_entity(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<bool> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        DropbearNativeResult::Ok(super::shared::custom_properties_exists_for_entity(world, entity))
+    }
+
+
+    pub fn dropbear_get_string_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char
+    ) -> DropbearNativeResult<*mut c_char> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+        let key_str = unsafe { read_key(key)? };
+
+        if let Ok(props) = world.get::<&CustomProperties>(entity) {
+            if let Some(Value::String(s)) = props.get_property(&key_str) {
+                return match CString::new(s.clone()) {
+                    Ok(c) => DropbearNativeResult::Ok(c.into_raw()),
+                    Err(_) => DropbearNativeResult::Err(DropbearNativeError::CStringError),
+                };
+            }
+        }
+        DropbearNativeResult::Err(DropbearNativeError::InvalidArgument)
+    }
+
+    pub fn dropbear_get_int_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char
+    ) -> DropbearNativeResult<i32> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+        let key_str = unsafe { read_key(key)? };
+
+        if let Ok(props) = world.get::<&CustomProperties>(entity) {
+            if let Some(Value::Int(v)) = props.get_property(&key_str) {
+                return DropbearNativeResult::Ok(*v as i32);
+            }
+        }
+        DropbearNativeResult::Err(DropbearNativeError::InvalidArgument)
+    }
+
+    pub fn dropbear_get_long_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char
+    ) -> DropbearNativeResult<i64> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+        let key_str = unsafe { read_key(key)? };
+
+        if let Ok(props) = world.get::<&CustomProperties>(entity) {
+            if let Some(Value::Int(v)) = props.get_property(&key_str) {
+                return DropbearNativeResult::Ok(*v);
+            }
+        }
+        DropbearNativeResult::Err(DropbearNativeError::InvalidArgument)
+    }
+
+    pub fn dropbear_get_double_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char
+    ) -> DropbearNativeResult<f64> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+        let key_str = unsafe { read_key(key)? };
+
+        if let Ok(props) = world.get::<&CustomProperties>(entity) {
+            if let Some(Value::Double(v)) = props.get_property(&key_str) {
+                return DropbearNativeResult::Ok(*v);
+            }
+        }
+        DropbearNativeResult::Err(DropbearNativeError::InvalidArgument)
+    }
+
+    pub fn dropbear_get_float_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char
+    ) -> DropbearNativeResult<f32> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+        let key_str = unsafe { read_key(key)? };
+
+        if let Ok(props) = world.get::<&CustomProperties>(entity) {
+            if let Some(Value::Double(v)) = props.get_property(&key_str) {
+                return DropbearNativeResult::Ok(*v as f32);
+            }
+        }
+        DropbearNativeResult::Err(DropbearNativeError::InvalidArgument)
+    }
+
+    pub fn dropbear_get_bool_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char
+    ) -> DropbearNativeResult<bool> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+        let key_str = unsafe { read_key(key)? };
+
+        if let Ok(props) = world.get::<&CustomProperties>(entity) {
+            if let Some(Value::Bool(v)) = props.get_property(&key_str) {
+                return DropbearNativeResult::Ok(*v);
+            }
+        }
+        DropbearNativeResult::Err(DropbearNativeError::InvalidArgument)
+    }
+
+    pub fn dropbear_get_vec3_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char
+    ) -> DropbearNativeResult<Vector3> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+        let key_str = unsafe { read_key(key)? };
+
+        if let Ok(props) = world.get::<&CustomProperties>(entity) {
+            if let Some(Value::Vec3(v)) = props.get_property(&key_str) {
+                return DropbearNativeResult::Ok(Vector3::from(*v));
+            }
+        }
+        DropbearNativeResult::Err(DropbearNativeError::InvalidArgument)
+    }
+
+    pub fn dropbear_set_string_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char,
+        value: *const c_char
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+        let key_str = unsafe { read_key(key)? };
+        let val_str = unsafe { read_key(value)? };
+
+        if let Ok(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::String(val_str));
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_int_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char,
+        value: i32
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+        let key_str = unsafe { read_key(key)? };
+
+        if let Ok(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Int(value as i64));
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_long_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char,
+        value: i64
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+        let key_str = unsafe { read_key(key)? };
+
+        if let Ok(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Int(value));
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_double_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char,
+        value: f64
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+        let key_str = unsafe { read_key(key)? };
+
+        if let Ok(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Double(value));
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_float_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char,
+        value: f32
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+        let key_str = unsafe { read_key(key)? };
+
+        if let Ok(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Double(value as f64));
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_bool_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char,
+        value: bool
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+        let key_str = unsafe { read_key(key)? };
+
+        if let Ok(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Bool(value));
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_vec3_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char,
+        value: Vector3
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+        let key_str = unsafe { read_key(key)? };
+
+        if let Ok(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Vec3(value.to_array()));
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/scene.rs b/eucalyptus-core/src/scene.rs
index 1c28ade..798fee9 100644
--- a/eucalyptus-core/src/scene.rs
+++ b/eucalyptus-core/src/scene.rs
@@ -1,17 +1,18 @@
 //! Deals with scene loading and scene metadata.
 
 pub mod loading;
+pub mod scripting;
 
-use crate::camera::{CameraComponent};
+use crate::camera::CameraComponent;
 use crate::hierarchy::{Children, Parent, SceneHierarchy};
-use crate::states::{Camera3D, Label, Light, CustomProperties, PROJECT, Script, SerializedMeshRenderer, WorldLoadingStatus};
+use crate::states::{Camera3D, Label, Light, Script, SerializedMeshRenderer, WorldLoadingStatus, PROJECT};
 use crate::utils::ResolveReference;
 use dropbear_engine::asset::ASSET_REGISTRY;
 use dropbear_engine::camera::{Camera, CameraBuilder};
 use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform};
-use dropbear_engine::graphics::{SharedGraphicsContext};
+use dropbear_engine::graphics::SharedGraphicsContext;
 use dropbear_engine::lighting::{Light as EngineLight, LightComponent};
-use dropbear_engine::model::{Model};
+use dropbear_engine::model::Model;
 use dropbear_engine::utils::{ResourceReference, ResourceReferenceType};
 use dropbear_traits::SerializableComponent;
 use dropbear_traits::registry::ComponentRegistry;
@@ -24,9 +25,10 @@ use std::fs;
 use std::path::{Path, PathBuf};
 use std::sync::Arc;
 use crossbeam_channel::Sender;
-use crate::physics::collider::{ColliderGroup};
+use crate::physics::collider::ColliderGroup;
 use crate::physics::PhysicsState;
 use crate::physics::rigidbody::RigidBody;
+use crate::properties::CustomProperties;
 
 #[derive(Default, Debug, Serialize, Deserialize, Clone)]
 pub struct SceneEntity {
diff --git a/eucalyptus-core/src/scene/scripting.rs b/eucalyptus-core/src/scene/scripting.rs
new file mode 100644
index 0000000..e3c5db2
--- /dev/null
+++ b/eucalyptus-core/src/scene/scripting.rs
@@ -0,0 +1,565 @@
+use crate::scripting::result::DropbearNativeResult;
+use ::jni::objects::{JObject, JValue};
+use ::jni::JNIEnv;
+
+pub mod shared {
+    use crate::command::CommandBuffer;
+    use crate::scene::loading::{SceneLoadResult, SceneLoader};
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+    use crossbeam_channel::Sender;
+    use parking_lot::Mutex;
+
+    pub fn load_scene_async(
+        command_buffer: &Sender<CommandBuffer>,
+        scene_loader: &Mutex<SceneLoader>,
+        scene_name: String,
+        _loading_scene: Option<String>,
+    ) -> DropbearNativeResult<u64> {
+        let mut loader = scene_loader.lock();
+
+        if let Some(existing_id) = loader.find_pending_id_by_name(&scene_name) {
+            return Ok(existing_id);
+        }
+
+        let id = loader.register_load(scene_name.clone());
+
+        let handle = crate::scene::loading::SceneLoadHandle {
+            id,
+            scene_name: scene_name.clone(),
+        };
+
+        // Send load command
+        command_buffer.try_send(CommandBuffer::LoadSceneAsync(handle))
+            .map_err(|_| DropbearNativeError::SendError)?;
+
+        Ok(id)
+    }
+
+    pub fn switch_to_scene_immediate(
+        command_buffer: &Sender<CommandBuffer>,
+        scene_name: String,
+    ) -> DropbearNativeResult<()> {
+        command_buffer.try_send(CommandBuffer::SwitchSceneImmediate(scene_name))
+            .map_err(|_| DropbearNativeError::SendError)?;
+        Ok(())
+    }
+
+    pub fn switch_to_scene_async(
+        command_buffer: &Sender<CommandBuffer>,
+        scene_loader: &Mutex<SceneLoader>,
+        scene_id: u64,
+    ) -> DropbearNativeResult<()> {
+        let loader = scene_loader.lock();
+
+        if let Some(entry) = loader.get_entry(scene_id) {
+            if matches!(entry.result, SceneLoadResult::Success) {
+                let handle = crate::scene::loading::SceneLoadHandle {
+                    id: scene_id,
+                    scene_name: entry.scene_name.clone(),
+                };
+
+                command_buffer.try_send(CommandBuffer::SwitchToAsync(handle))
+                    .map_err(|_| DropbearNativeError::SendError)?;
+                Ok(())
+            } else {
+                Err(DropbearNativeError::PrematureSceneSwitch)
+            }
+        } else {
+            Err(DropbearNativeError::NoSuchHandle)
+        }
+    }
+
+    pub fn get_scene_load_handle_scene_name(
+        scene_loader: &Mutex<SceneLoader>,
+        scene_id: u64,
+    ) -> DropbearNativeResult<String> {
+        let loader = scene_loader.lock();
+
+        if let Some(entry) = loader.get_entry(scene_id) {
+            Ok(entry.scene_name.clone())
+        } else {
+            Err(DropbearNativeError::NoSuchHandle)
+        }
+    }
+
+    pub fn get_scene_load_progress(
+        scene_loader: &Mutex<SceneLoader>,
+        scene_id: u64,
+    ) -> DropbearNativeResult<crate::utils::Progress> {
+        let mut loader = scene_loader.lock();
+
+        if let Some(entry) = loader.get_entry_mut(scene_id) {
+            // Update progress from status channel if available
+            if let Some(ref rx) = entry.status {
+                while let Ok(status) = rx.try_recv() {
+                    match status {
+                        crate::states::WorldLoadingStatus::Idle => {
+                            entry.progress.message = "Idle".to_string();
+                        },
+                        crate::states::WorldLoadingStatus::LoadingEntity { index, name, total } => {
+                            entry.progress.current = index;
+                            entry.progress.total = total;
+                            entry.progress.message = format!("Loading entity: {}", name);
+                        },
+                        crate::states::WorldLoadingStatus::Completed => {
+                            entry.progress.current = entry.progress.total;
+                            entry.progress.message = "Completed".to_string();
+                        }
+                    }
+                }
+            }
+
+            Ok(entry.progress.clone())
+        } else {
+            Err(DropbearNativeError::NoSuchHandle)
+        }
+    }
+
+    pub fn get_scene_load_status(
+        scene_loader: &Mutex<SceneLoader>,
+        scene_id: u64,
+    ) -> DropbearNativeResult<u32> {
+        let loader = scene_loader.lock();
+
+        if let Some(entry) = loader.get_entry(scene_id) {
+            let status = match entry.result {
+                SceneLoadResult::Pending => 0,  // PENDING
+                SceneLoadResult::Success => 1,  // READY
+                SceneLoadResult::Error(_) => 2, // FAILED
+            };
+            Ok(status)
+        } else {
+            Err(DropbearNativeError::NoSuchHandle)
+        }
+    }
+}
+
+pub mod jni {
+    #![allow(non_snake_case)]
+    use crate::command::CommandBuffer;
+    use crate::ptr::{CommandBufferPtr, SceneLoaderPtr};
+    use crate::scripting::jni::utils::ToJObject;
+    use crate::{convert_jstring, convert_ptr};
+    use jni::objects::{JClass, JString, JValue};
+    use jni::sys::{jint, jlong, jobject};
+    use jni::JNIEnv;
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_scene_SceneManagerNative_loadSceneAsyncNative__JJLjava_lang_String_2(
+        mut env: JNIEnv,
+        _: JClass,
+        command_buffer_ptr: jlong,
+        scene_manager_handle: jlong,
+        scene_name: JString,
+    ) -> jobject {
+        let command_buffer = convert_ptr!(command_buffer_ptr, CommandBufferPtr => crossbeam_channel::Sender<CommandBuffer>);
+        let scene_loader = convert_ptr!(scene_manager_handle, SceneLoaderPtr => parking_lot::Mutex<crate::scene::loading::SceneLoader>);
+
+        let scene_name_str = convert_jstring!(env, scene_name);
+
+        match super::shared::load_scene_async(command_buffer, scene_loader, scene_name_str, None) {
+            Ok(scene_id) => {
+                match env.find_class("java/lang/Long") {
+                    Ok(long_class) => {
+                        match env.new_object(long_class, "(J)V", &[JValue::Long(scene_id as i64)]) {
+                            Ok(obj) => obj.into_raw(),
+                            Err(e) => {
+                                eprintln!("Failed to create Long object: {}", e);
+                                let _ = env.throw_new("java/lang/RuntimeException",
+                                                      format!("Failed to create Long object: {}", e));
+                                std::ptr::null_mut()
+                            }
+                        }
+                    }
+                    Err(e) => {
+                        eprintln!("Failed to find Long class: {}", e);
+                        let _ = env.throw_new("java/lang/RuntimeException",
+                                              format!("Failed to find Long class: {}", e));
+                        std::ptr::null_mut()
+                    }
+                }
+            }
+            Err(e) => {
+                eprintln!("Failed to load scene async: {}", e);
+                let _ = env.throw_new("java/lang/RuntimeException",
+                                      format!("Failed to load scene async: {:?}", e));
+                std::ptr::null_mut()
+            }
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_scene_SceneManagerNative_loadSceneAsyncNative__JJLjava_lang_String_2Ljava_lang_String_2(
+        mut env: JNIEnv,
+        _: JClass,
+        command_buffer_ptr: jlong,
+        scene_manager_handle: jlong,
+        scene_name: JString,
+        loading_scene: JString,
+    ) -> jobject {
+        let command_buffer = convert_ptr!(command_buffer_ptr, CommandBufferPtr => crossbeam_channel::Sender<CommandBuffer>);
+        let scene_loader = convert_ptr!(scene_manager_handle, SceneLoaderPtr => parking_lot::Mutex<crate::scene::loading::SceneLoader>);
+
+        let scene_name_str = convert_jstring!(env, scene_name);
+        let loading_scene_str = convert_jstring!(env, loading_scene);
+
+        match super::shared::load_scene_async(command_buffer, scene_loader, scene_name_str, Some(loading_scene_str)) {
+            Ok(scene_id) => {
+                match env.find_class("java/lang/Long") {
+                    Ok(long_class) => {
+                        match env.new_object(long_class, "(J)V", &[JValue::Long(scene_id as i64)]) {
+                            Ok(obj) => obj.into_raw(),
+                            Err(e) => {
+                                eprintln!("Failed to create Long object: {}", e);
+                                let _ = env.throw_new("java/lang/RuntimeException",
+                                                      format!("Failed to create Long object: {}", e));
+                                std::ptr::null_mut()
+                            }
+                        }
+                    }
+                    Err(e) => {
+                        eprintln!("Failed to find Long class: {}", e);
+                        let _ = env.throw_new("java/lang/RuntimeException",
+                                              format!("Failed to find Long class: {}", e));
+                        std::ptr::null_mut()
+                    }
+                }
+            }
+            Err(e) => {
+                eprintln!("Failed to load scene async with loading scene: {}", e);
+                let _ = env.throw_new("java/lang/RuntimeException",
+                                      format!("Failed to load scene async: {:?}", e));
+                std::ptr::null_mut()
+            }
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_scene_SceneManagerNative_switchToSceneImmediateNative(
+        mut env: JNIEnv,
+        _: JClass,
+        command_buffer_ptr: jlong,
+        scene_name: JString,
+    ) {
+        let command_buffer = convert_ptr!(command_buffer_ptr, CommandBufferPtr => crossbeam_channel::Sender<CommandBuffer>);
+
+        let scene_name_str = convert_jstring!(env, scene_name);
+
+        if let Err(e) = super::shared::switch_to_scene_immediate(command_buffer, scene_name_str) {
+            eprintln!("Failed to switch scene immediate: {}", e);
+            let _ = env.throw_new("java/lang/RuntimeException",
+                                  format!("Failed to switch scene immediate: {:?}", e));
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_scene_SceneLoadHandleNative_getSceneLoadHandleSceneName(
+        mut env: JNIEnv,
+        _: JClass,
+        scene_loader_handle: jlong,
+        scene_id: jlong,
+    ) -> jobject {
+        let scene_loader = convert_ptr!(scene_loader_handle, SceneLoaderPtr => parking_lot::Mutex<crate::scene::loading::SceneLoader>);
+
+        match super::shared::get_scene_load_handle_scene_name(scene_loader, scene_id as u64) {
+            Ok(scene_name) => {
+                match env.new_string(scene_name) {
+                    Ok(jstring) => jstring.into_raw(),
+                    Err(e) => {
+                        eprintln!("Failed to create Java string: {}", e);
+                        let _ = env.throw_new("java/lang/RuntimeException",
+                                              format!("Failed to create Java string: {}", e));
+                        std::ptr::null_mut()
+                    }
+                }
+            }
+            Err(e) => {
+                eprintln!("Failed to get scene name: {}", e);
+                let _ = env.throw_new("java/lang/RuntimeException",
+                                      format!("Failed to get scene name: {:?}", e));
+                std::ptr::null_mut()
+            }
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_scene_SceneLoadHandleNative_switchToSceneAsync(
+        mut env: JNIEnv,
+        _: JClass,
+        command_buffer_ptr: jlong,
+        scene_id: jlong,
+    ) {
+        let command_buffer = convert_ptr!(command_buffer_ptr, CommandBufferPtr => crossbeam_channel::Sender<CommandBuffer>);
+        let scene_loader = convert_ptr!(scene_id as SceneLoaderPtr => parking_lot::Mutex<crate::scene::loading::SceneLoader>);
+
+        if let Err(e) = super::shared::switch_to_scene_async(command_buffer, scene_loader, scene_id as u64) {
+            eprintln!("Failed to switch scene async: {}", e);
+
+            // Check if it's a premature scene switch error
+            if let crate::scripting::native::DropbearNativeError::PrematureSceneSwitch = e {
+                let _ = env.throw_new("com/dropbear/exception/PrematureSceneSwitchException",
+                                      "Cannot switch to scene before it has finished loading");
+            } else {
+                let _ = env.throw_new("java/lang/RuntimeException",
+                                      format!("Failed to switch scene async: {:?}", e));
+            }
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_scene_SceneLoadHandleNative_getSceneLoadProgress(
+        mut env: JNIEnv,
+        _: JClass,
+        scene_loader_handle: jlong,
+        scene_id: jlong,
+    ) -> jobject {
+        let scene_loader = convert_ptr!(scene_loader_handle, SceneLoaderPtr => parking_lot::Mutex<crate::scene::loading::SceneLoader>);
+
+        match super::shared::get_scene_load_progress(scene_loader, scene_id as u64) {
+            Ok(progress) => {
+                match progress.to_jobject(&mut env) {
+                    Ok(obj) => obj.into_raw(),
+                    Err(e) => {
+                        eprintln!("Failed to create Progress object: {:?}", e);
+                        let _ = env.throw_new("java/lang/RuntimeException",
+                                              format!("Failed to create Progress object: {:?}", e));
+                        std::ptr::null_mut()
+                    }
+                }
+            }
+            Err(e) => {
+                eprintln!("Failed to get scene load progress: {}", e);
+                let _ = env.throw_new("java/lang/RuntimeException",
+                                      format!("Failed to get scene load progress: {:?}", e));
+                std::ptr::null_mut()
+            }
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_scene_SceneLoadHandleNative_getSceneLoadStatus(
+        mut env: JNIEnv,
+        _: JClass,
+        scene_loader_handle: jlong,
+        scene_id: jlong,
+    ) -> jint {
+        let scene_loader = convert_ptr!(scene_loader_handle, SceneLoaderPtr => parking_lot::Mutex<crate::scene::loading::SceneLoader>);
+
+        match super::shared::get_scene_load_status(scene_loader, scene_id as u64) {
+            Ok(status) => status as jint,
+            Err(e) => {
+                eprintln!("Failed to get scene load status: {}", e);
+                let _ = env.throw_new("java/lang/RuntimeException",
+                                      format!("Failed to get scene load status: {:?}", e));
+                -1 as jint
+            }
+        }
+    }
+}
+
+pub mod native {
+    use crate::ptr::{CommandBufferPtr, SceneLoaderPtr};
+    use crate::scripting::native::DropbearNativeError;
+    use std::ffi::c_char;
+    use std::ffi::CStr;
+
+    #[unsafe(no_mangle)]
+    pub extern "C" fn load_scene_async(
+        command_buffer_ptr: CommandBufferPtr,
+        scene_loader_ptr: SceneLoaderPtr,
+        scene_name: *const c_char,
+        loading_scene: *const c_char,
+        out_scene_id: *mut u64,
+    ) -> DropbearNativeError {
+        if command_buffer_ptr.is_null() || scene_loader_ptr.is_null() || scene_name.is_null() {
+            return DropbearNativeError::NullPointer;
+        }
+
+        let command_buffer = unsafe { &*command_buffer_ptr };
+        let scene_loader = unsafe { &*scene_loader_ptr };
+
+        let scene_name_cstr = unsafe { CStr::from_ptr(scene_name) };
+        let scene_name_str = match scene_name_cstr.to_str() {
+            Ok(s) => s.to_string(),
+            Err(_) => return DropbearNativeError::InvalidUTF8,
+        };
+
+        let loading_scene_option = if !loading_scene.is_null() {
+            let loading_scene_cstr = unsafe { CStr::from_ptr(loading_scene) };
+            match loading_scene_cstr.to_str() {
+                Ok(s) => Some(s.to_string()),
+                Err(_) => return DropbearNativeError::InvalidUTF8,
+            }
+        } else {
+            None
+        };
+
+        match super::shared::load_scene_async(command_buffer, scene_loader, scene_name_str, loading_scene_option) {
+            Ok(scene_id) => {
+                if !out_scene_id.is_null() {
+                    unsafe { *out_scene_id = scene_id };
+                }
+                DropbearNativeError::Success
+            }
+            Err(e) => e,
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub extern "C" fn switch_to_scene_immediate(
+        command_buffer_ptr: CommandBufferPtr,
+        scene_name: *const c_char,
+    ) -> DropbearNativeError {
+        if command_buffer_ptr.is_null() || scene_name.is_null() {
+            return DropbearNativeError::NullPointer;
+        }
+
+        let command_buffer = unsafe { &*command_buffer_ptr };
+
+        let scene_name_cstr = unsafe { CStr::from_ptr(scene_name) };
+        let scene_name_str = match scene_name_cstr.to_str() {
+            Ok(s) => s.to_string(),
+            Err(_) => return DropbearNativeError::InvalidUTF8,
+        };
+
+        match super::shared::switch_to_scene_immediate(command_buffer, scene_name_str) {
+            Ok(_) => DropbearNativeError::Success,
+            Err(e) => e,
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub extern "C" fn get_scene_load_handle_scene_name(
+        scene_loader_ptr: SceneLoaderPtr,
+        scene_id: u64,
+        out_buffer: *mut c_char,
+        buffer_size: usize,
+    ) -> DropbearNativeError {
+        if scene_loader_ptr.is_null() || out_buffer.is_null() {
+            return DropbearNativeError::NullPointer;
+        }
+
+        let scene_loader = unsafe { &*scene_loader_ptr };
+
+        match super::shared::get_scene_load_handle_scene_name(scene_loader, scene_id) {
+            Ok(scene_name) => {
+                let c_string = match std::ffi::CString::new(scene_name) {
+                    Ok(cstr) => cstr,
+                    Err(_) => return DropbearNativeError::CStringError,
+                };
+
+                let bytes = c_string.as_bytes_with_nul();
+                if bytes.len() > buffer_size {
+                    return DropbearNativeError::BufferTooSmall;
+                }
+
+                unsafe {
+                    std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_buffer as *mut u8, bytes.len());
+                }
+
+                DropbearNativeError::Success
+            }
+            Err(e) => e,
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub extern "C" fn switch_to_scene_async(
+        command_buffer_ptr: CommandBufferPtr,
+        scene_loader_ptr: SceneLoaderPtr,
+        scene_id: u64,
+    ) -> DropbearNativeError {
+        if command_buffer_ptr.is_null() || scene_loader_ptr.is_null() {
+            return DropbearNativeError::NullPointer;
+        }
+
+        let command_buffer = unsafe { &*command_buffer_ptr };
+        let scene_loader = unsafe { &*scene_loader_ptr };
+
+        match super::shared::switch_to_scene_async(command_buffer, scene_loader, scene_id) {
+            Ok(_) => DropbearNativeError::Success,
+            Err(e) => e,
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub extern "C" fn get_scene_load_progress(
+        scene_loader_ptr: SceneLoaderPtr,
+        scene_id: u64,
+        out_current: *mut f64,
+        out_total: *mut f64,
+        out_message: *mut c_char,
+        message_buffer_size: usize,
+    ) -> DropbearNativeError {
+        if scene_loader_ptr.is_null() {
+            return DropbearNativeError::NullPointer;
+        }
+
+        let scene_loader = unsafe { &*scene_loader_ptr };
+
+        match super::shared::get_scene_load_progress(scene_loader, scene_id) {
+            Ok(progress) => {
+                if !out_current.is_null() {
+                    unsafe { *out_current = progress.current as f64 };
+                }
+                if !out_total.is_null() {
+                    unsafe { *out_total = progress.total as f64 };
+                }
+
+                if !out_message.is_null() && message_buffer_size > 0 {
+                    let message_cstr = match std::ffi::CString::new(progress.message) {
+                        Ok(cstr) => cstr,
+                        Err(_) => return DropbearNativeError::CStringError,
+                    };
+
+                    let bytes = message_cstr.as_bytes_with_nul();
+                    let copy_len = std::cmp::min(bytes.len(), message_buffer_size);
+
+                    unsafe {
+                        std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_message as *mut u8, copy_len);
+                    }
+                }
+
+                DropbearNativeError::Success
+            }
+            Err(e) => e,
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub extern "C" fn get_scene_load_status(
+        scene_loader_ptr: SceneLoaderPtr,
+        scene_id: u64,
+    ) -> i32 {
+        if scene_loader_ptr.is_null() {
+            return DropbearNativeError::NullPointer as i32;
+        }
+
+        let scene_loader = unsafe { &*scene_loader_ptr };
+
+        match super::shared::get_scene_load_status(scene_loader, scene_id) {
+            Ok(status) => status as i32,
+            Err(e) => e as i32,
+        }
+    }
+}
+
+impl crate::scripting::jni::utils::ToJObject for crate::utils::Progress {
+    fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
+        let class = env.find_class("com/dropbear/utils/Progress")
+            .map_err(|_| crate::scripting::native::DropbearNativeError::JNIClassNotFound)?;
+
+        let message_jstring = env.new_string(&self.message)
+            .map_err(|_| crate::scripting::native::DropbearNativeError::JNIFailedToCreateObject)?;
+
+        let obj = env.new_object(&class, "(DDLjava/lang/String;)V", &[
+            JValue::Double(self.current as f64),
+            JValue::Double(self.total as f64),
+            JValue::Object(&JObject::from(message_jstring)),
+        ])
+            .map_err(|_| crate::scripting::native::DropbearNativeError::JNIFailedToCreateObject)?;
+
+        Ok(obj)
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting.rs b/eucalyptus-core/src/scripting.rs
index c5d79fc..73a2fe6 100644
--- a/eucalyptus-core/src/scripting.rs
+++ b/eucalyptus-core/src/scripting.rs
@@ -6,14 +6,15 @@ pub mod error;
 pub mod jni;
 pub mod native;
 pub mod utils;
+pub mod result;
 
 pub static JVM_ARGS: OnceLock<String> = OnceLock::new();
 
 use std::sync::OnceLock;
-use crate::ptr::{CommandBufferPtr, InputStatePtr, PhysicsStatePtr, WorldPtr};
+use crate::ptr::{AssetRegistryPtr, CommandBufferPtr, InputStatePtr, PhysicsStatePtr, SceneLoaderPtr, WorldPtr};
 use crate::scripting::jni::JavaContext;
 use crate::scripting::native::NativeLibrary;
-use crate::states::Script;
+use crate::states::{Script};
 use anyhow::Context;
 use crossbeam_channel::Sender;
 use dropbear_engine::asset::ASSET_REGISTRY;
@@ -22,9 +23,10 @@ use std::collections::{HashMap, HashSet};
 use std::path::{Path, PathBuf};
 use tokio::io::{AsyncBufReadExt, BufReader};
 use tokio::process::Command;
+use dropbear_engine::asset::PointerKind::Const;
+use dropbear_engine::model::MODEL_CACHE;
 use magna_carta::Target;
 use crate::scene::loading::SCENE_LOADER;
-use crate::scripting::native::exports::dropbear_common::DropbearContext;
 
 /// The target of the script. This can be either a JVM or a native library.
 #[derive(Default, Clone, Debug)]
@@ -219,6 +221,9 @@ impl ScriptManager {
     ) -> anyhow::Result<()> {
         let assets = &raw const *ASSET_REGISTRY;
         let scene_loader = &raw const *SCENE_LOADER;
+        
+        let model_cache_ptr = &raw const *MODEL_CACHE;
+        ASSET_REGISTRY.add_pointer(Const("model_cache"), model_cache_ptr as usize);
 
         let context = DropbearContext {
             world,
@@ -229,6 +234,13 @@ impl ScriptManager {
             physics_state,
         };
 
+        if world.is_null() { log::error!("World pointer is null"); }
+        if input.is_null() { log::error!("InputState pointer is null"); }
+        if graphics.is_null() { log::error!("CommandBuffer pointer is null"); }
+        if assets.is_null() { log::error!("AssetRegistry pointer is null"); }
+        if scene_loader.is_null() { log::error!("SceneLoader pointer is null"); }
+        if physics_state.is_null() { log::error!("PhysicsState pointer is null"); }
+
         match &self.script_target {
             ScriptTarget::JVM { .. } => {
                 if let Some(jvm) = &mut self.jvm {
@@ -836,3 +848,15 @@ pub async fn build_native(
         Err(anyhow::anyhow!(err))
     }
 }
+
+/// Describes all the different pointers that can be passed into a scripting
+/// module.
+#[repr(C)]
+pub struct DropbearContext {
+    pub world: WorldPtr,
+    pub input: InputStatePtr,
+    pub graphics: CommandBufferPtr,
+    pub assets: AssetRegistryPtr,
+    pub scene_loader: SceneLoaderPtr,
+    pub physics_state: PhysicsStatePtr,
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting/jni.rs b/eucalyptus-core/src/scripting/jni.rs
index 544ef55..97e9714 100644
--- a/eucalyptus-core/src/scripting/jni.rs
+++ b/eucalyptus-core/src/scripting/jni.rs
@@ -1,23 +1,23 @@
 #![allow(non_snake_case)]
 //! Deals with the Java Native Interface (JNI) with the help of the [`jni`] crate
 
-pub mod exports;
+// pub mod exports;
 pub mod utils;
 
 use crate::APP_INFO;
 use crate::logging::LOG_LEVEL;
 use crate::ptr::WorldPtr;
 use crate::scripting::error::LastErrorMessage;
-use jni::objects::{GlobalRef, JClass, JLongArray, JObject, JValue};
+use jni::objects::{GlobalRef, JClass, JLongArray, JObject, JObjectArray, JString, JValue};
 use jni::sys::jlong;
-use jni::{InitArgsBuilder, JNIVersion, JavaVM};
+use jni::{InitArgsBuilder, JNIEnv, JNIVersion, JavaVM};
 use sha2::{Digest, Sha256};
 use std::fs;
 use std::net::TcpListener;
 use std::path::PathBuf;
 use once_cell::sync::OnceCell;
 use crate::scripting::JVM_ARGS;
-use crate::scripting::native::exports::dropbear_common::DropbearContext;
+use crate::scripting::DropbearContext;
 
 #[derive(Default, Clone)]
 pub enum RuntimeMode {
@@ -295,109 +295,197 @@ impl JavaContext {
     ) -> anyhow::Result<()> {
         let mut env = self.jvm.attach_current_thread()?;
 
-        let world_handle = context.world as jlong;
-        let input_handle = context.input as jlong;
-        let graphics_handle = context.graphics as jlong;
-        let asset_handle = context.assets as jlong;
-        let scene_loader_handle = context.scene_loader as jlong;
-        let physics_handle = context.physics_state as jlong;
-
-        let dropbear_context_class: JClass = env.find_class("com/dropbear/ffi/DropbearContext")?;
-        let dropbear_context_obj = env.new_object(
-            dropbear_context_class,
-            "(JJJJJ)V",
-            &[
+        let result = (|| -> anyhow::Result<()> {
+            let world_handle = context.world as jlong;
+            let input_handle = context.input as jlong;
+            let graphics_handle = context.graphics as jlong;
+            let asset_handle = context.assets as jlong;
+            let scene_loader_handle = context.scene_loader as jlong;
+            let physics_handle = context.physics_state as jlong;
+
+            let args = [
                 JValue::Long(world_handle),
                 JValue::Long(input_handle),
                 JValue::Long(graphics_handle),
                 JValue::Long(asset_handle),
                 JValue::Long(scene_loader_handle),
                 JValue::Long(physics_handle)
-            ]
-        )?;
-
-        log::trace!("Locating \"com/dropbear/ffi/NativeEngine\" class");
-        let native_engine_class: JClass = env.find_class("com/dropbear/ffi/NativeEngine")?;
+            ];
 
-        let native_engine_obj = if let Some(ref native_engine_ref) = self.native_engine_instance {
-            native_engine_ref.as_obj()
-        } else {
-            log::trace!("Creating new instance of NativeEngine");
-            let native_engine_obj = env.new_object(native_engine_class, "()V", &[])?;
-            let native_engine_global_ref = env.new_global_ref(native_engine_obj)?;
-            self.native_engine_instance = Some(native_engine_global_ref);
-            self.native_engine_instance
-                .as_ref()
-                .expect("NativeEngine global ref must exist")
-                .as_obj()
-        };
+            let mut sig = String::from("(");
+            for _ in 0..args.len() {
+                sig.push('J');
+            }
+            sig.push(')');
+            sig.push('V');
+
+            let dropbear_context_class: JClass = env.find_class("com/dropbear/ffi/DropbearContext")?;
+            let dropbear_context_obj = env.new_object(
+                dropbear_context_class,
+                sig,
+                &args
+            )?;
 
-        log::trace!("Calling NativeEngine.init() with arg [\"com/dropbear/ffi/DropbearContext\"]");
-        env.call_method(
-            native_engine_obj,
-            "init",
-            "(Lcom/dropbear/ffi/DropbearContext;)V",
-            &[JValue::Object(&dropbear_context_obj)],
-        )?;
-
-        if self.dropbear_engine_class.is_none() {
-            let dropbear_class: JClass = env.find_class("com/dropbear/DropbearEngine")?;
-            log::trace!("Creating DropbearEngine constructor with arg (NativeEngine_object)");
-            let dropbear_obj = env.new_object(
-                dropbear_class,
-                "(Lcom/dropbear/ffi/NativeEngine;)V",
-                &[JValue::Object(native_engine_obj)],
+            log::trace!("Locating \"com/dropbear/ffi/NativeEngine\" class");
+            let native_engine_class: JClass = env.find_class("com/dropbear/ffi/NativeEngine")?;
+
+            let native_engine_obj = if let Some(ref native_engine_ref) = self.native_engine_instance {
+                native_engine_ref.as_obj()
+            } else {
+                log::trace!("Creating new instance of NativeEngine");
+                let native_engine_obj = env.new_object(native_engine_class, "()V", &[])?;
+                let native_engine_global_ref = env.new_global_ref(native_engine_obj)?;
+                self.native_engine_instance = Some(native_engine_global_ref);
+                self.native_engine_instance
+                    .as_ref()
+                    .expect("NativeEngine global ref must exist")
+                    .as_obj()
+            };
+
+            log::trace!("Calling NativeEngine.init() with arg [\"com/dropbear/ffi/DropbearContext\"]");
+            env.call_method(
+                native_engine_obj,
+                "init",
+                "(Lcom/dropbear/ffi/DropbearContext;)V",
+                &[JValue::Object(&dropbear_context_obj)],
             )?;
 
-            log::trace!("Creating new global ref for DropbearEngine");
-            let engine_global_ref = env.new_global_ref(dropbear_obj)?;
-            self.dropbear_engine_class = Some(engine_global_ref);
-        }
+            if self.dropbear_engine_class.is_none() {
+                let dropbear_class: JClass = env.find_class("com/dropbear/DropbearEngine")?;
+                log::trace!("Creating DropbearEngine constructor with arg (NativeEngine_object)");
+                let dropbear_obj = env.new_object(
+                    dropbear_class,
+                    "(Lcom/dropbear/ffi/NativeEngine;)V",
+                    &[JValue::Object(native_engine_obj)],
+                )?;
 
-        let jar_path_jstring = env.new_string(self.jar_path.to_string_lossy())?;
-        let log_level_str = { LOG_LEVEL.lock().to_string() };
-        let log_level_enum_class = env.find_class("com/dropbear/logging/LogLevel")?;
-        let log_level_enum_instance = env
-            .get_static_field(
-                log_level_enum_class,
-                log_level_str,
-                "Lcom/dropbear/logging/LogLevel;",
-            )?
-            .l()?;
-
-        let std_out_writer_class = env.find_class("com/dropbear/logging/StdoutWriter")?;
-        let log_writer_obj = env.new_object(std_out_writer_class, "()V", &[])?;
-
-        if self.system_manager_instance.is_none() {
-            let engine_ref = self
-                .dropbear_engine_class
-                .as_ref()
-                .expect("DropbearEngine global ref must exist")
-                .as_obj();
-
-            log::trace!("Locating \"com/dropbear/host/SystemManager\" class");
-            let system_manager_class: JClass = env.find_class("com/dropbear/host/SystemManager")?;
-            log::trace!(
+                log::trace!("Creating new global ref for DropbearEngine");
+                let engine_global_ref = env.new_global_ref(dropbear_obj)?;
+                self.dropbear_engine_class = Some(engine_global_ref);
+            }
+
+            let jar_path_jstring = env.new_string(self.jar_path.to_string_lossy())?;
+            let log_level_str = { LOG_LEVEL.lock().to_string() };
+            let log_level_enum_class = env.find_class("com/dropbear/logging/LogLevel")?;
+            let log_level_enum_instance = env
+                .get_static_field(
+                    log_level_enum_class,
+                    log_level_str,
+                    "Lcom/dropbear/logging/LogLevel;",
+                )?
+                .l()?;
+
+            let std_out_writer_class = env.find_class("com/dropbear/logging/StdoutWriter")?;
+            let log_writer_obj = env.new_object(std_out_writer_class, "()V", &[])?;
+
+            if self.system_manager_instance.is_none() {
+                let engine_ref = self
+                    .dropbear_engine_class
+                    .as_ref()
+                    .expect("DropbearEngine global ref must exist")
+                    .as_obj();
+
+                log::trace!("Locating \"com/dropbear/host/SystemManager\" class");
+                let system_manager_class: JClass = env.find_class("com/dropbear/host/SystemManager")?;
+                log::trace!(
                 "Creating SystemManager constructor with args (jar_path_string, dropbear_engine_object, log_writer_object, log_level_enum, log_target_string)"
             );
 
-            let log_target_jstring = env.new_string("dropbear_rust_host")?;
-
-            let system_manager_obj = env.new_object(
-                system_manager_class,
-                "(Ljava/lang/String;Lcom/dropbear/DropbearEngine;Lcom/dropbear/logging/LogWriter;Lcom/dropbear/logging/LogLevel;Ljava/lang/String;)V",
-                &[
-                    JValue::Object(&jar_path_jstring),
-                    JValue::Object(engine_ref),
-                    JValue::Object(&log_writer_obj),
-                    JValue::Object(&log_level_enum_instance),
-                    JValue::Object(&log_target_jstring),
-                ],
+                let log_target_jstring = env.new_string("dropbear_rust_host")?;
+
+                let system_manager_obj = env.new_object(
+                    system_manager_class,
+                    "(Ljava/lang/String;Lcom/dropbear/DropbearEngine;Lcom/dropbear/logging/LogWriter;Lcom/dropbear/logging/LogLevel;Ljava/lang/String;)V",
+                    &[
+                        JValue::Object(&jar_path_jstring),
+                        JValue::Object(engine_ref),
+                        JValue::Object(&log_writer_obj),
+                        JValue::Object(&log_level_enum_instance),
+                        JValue::Object(&log_target_jstring),
+                    ],
+                )?;
+
+                log::trace!("Creating new global ref for SystemManager");
+                let manager_global_ref = env.new_global_ref(system_manager_obj)?;
+                self.system_manager_instance = Some(manager_global_ref);
+            }
+
+            Ok(())
+        })();
+
+        Self::get_exception(&mut env)?;
+
+        result
+    }
+
+    pub fn get_exception(env: &mut JNIEnv) -> anyhow::Result<()> {
+        if let Ok(ex) = env.exception_occurred() {
+            if ex.is_null() {
+                return Ok(());
+            }
+
+            env.exception_clear()?;
+
+            let message_result = env.call_method(
+                &ex,
+                "toString",
+                "()Ljava/lang/String;",
+                &[]
             )?;
+            let message_obj = message_result.l()?;
+            let message_jstring = JString::from(message_obj);
+            let message_str: String = env.get_string(&message_jstring)?.into();
+
+            let stack_trace_result = env.call_method(
+                &ex,
+                "getStackTrace",
+                "()[Ljava/lang/StackTraceElement;",
+                &[]
+            )?;
+            let stack_trace_obj = stack_trace_result.l()?;
+            let stack_trace_array = JObjectArray::from(stack_trace_obj);
+            let stack_len = env.get_array_length(&stack_trace_array)?;
+
+            let mut error_msg = format!("{}\n", message_str);
 
-            log::trace!("Creating new global ref for SystemManager");
-            let manager_global_ref = env.new_global_ref(system_manager_obj)?;
-            self.system_manager_instance = Some(manager_global_ref);
+            for i in 0..stack_len {
+                let element = env.get_object_array_element(&stack_trace_array, i)?;
+
+                let element_str_result = env.call_method(
+                    &element,
+                    "toString",
+                    "()Ljava/lang/String;",
+                    &[]
+                )?;
+                let element_str_obj = element_str_result.l()?;
+                let element_jstring = JString::from(element_str_obj);
+                let element_string: String = env.get_string(&element_jstring)?.into();
+
+                error_msg.push_str(&format!("  at {}\n", element_string));
+            }
+
+            let cause_result = env.call_method(
+                &ex,
+                "getCause",
+                "()Ljava/lang/Throwable;",
+                &[]
+            )?;
+            let cause_obj = cause_result.l()?;
+
+            if !cause_obj.is_null() {
+                let cause_str_result = env.call_method(
+                    &cause_obj,
+                    "toString",
+                    "()Ljava/lang/String;",
+                    &[]
+                )?;
+                let cause_str_obj = cause_str_result.l()?;
+                let cause_jstring = JString::from(cause_str_obj);
+                let cause_string: String = env.get_string(&cause_jstring)?.into();
+                error_msg.push_str(&format!("Caused by: {}\n", cause_string));
+            }
+
+            return Err(anyhow::anyhow!("Java exception: {}", error_msg));
         }
 
         Ok(())
@@ -412,85 +500,107 @@ impl JavaContext {
         if let Some(ref manager_ref) = self.system_manager_instance {
             let mut env = self.jvm.attach_current_thread()?;
 
-            log::trace!("Calling SystemManager.reloadJar()");
-            let jar_path_jstring = env.new_string(self.jar_path.to_string_lossy())?;
-            env.call_method(
-                manager_ref,
-                "reloadJar",
-                "(Ljava/lang/String;)V",
-                &[JValue::Object(&jar_path_jstring)],
-            )?;
+            let result = (|| -> anyhow::Result<()> {
+                log::trace!("Calling SystemManager.reloadJar()");
+                let jar_path_jstring = env.new_string(self.jar_path.to_string_lossy())?;
+                env.call_method(
+                    manager_ref,
+                    "reloadJar",
+                    "(Ljava/lang/String;)V",
+                    &[JValue::Object(&jar_path_jstring)],
+                )?;
+                Ok(())
+            })();
+
+            Self::get_exception(&mut env)?;
+
+            Ok(result?)
         } else {
             log::warn!("SystemManager instance not found during reload.");
             // self.init(world)?;
             return Err(anyhow::anyhow!("SystemManager not initialised for reload."));
         }
-
-        log::info!("Reload complete via SystemManager!");
-
-        Ok(())
     }
 
     pub fn load_systems_for_tag(&mut self, tag: &str) -> anyhow::Result<()> {
         if let Some(ref manager_ref) = self.system_manager_instance {
             let mut env = self.jvm.attach_current_thread()?;
 
-            log::trace!(
+            let result = (|| -> anyhow::Result<()> {
+                log::trace!(
                 "Calling SystemManager.loadSystemsForTag() with tag: {}",
                 tag
             );
-            let tag_jstring = env.new_string(tag)?;
-            env.call_method(
-                manager_ref,
-                "loadSystemsForTag",
-                "(Ljava/lang/String;)V",
-                &[JValue::Object(&tag_jstring)],
-            )?;
+                let tag_jstring = env.new_string(tag)?;
+                env.call_method(
+                    manager_ref,
+                    "loadSystemsForTag",
+                    "(Ljava/lang/String;)V",
+                    &[JValue::Object(&tag_jstring)],
+                )?;
 
-            log::debug!("Loaded systems for tag: {}", tag);
+                log::debug!("Loaded systems for tag: {}", tag);
+                Ok(())
+            })();
+
+            Self::get_exception(&mut env)?;
+
+            Ok(result?)
         } else {
             return Err(anyhow::anyhow!(
                 "SystemManager not initialised when loading systems for tag: {}",
                 tag
             ));
         }
-        Ok(())
     }
 
     pub fn update_all_systems(&self, dt: f32) -> anyhow::Result<()> {
         if let Some(ref manager_ref) = self.system_manager_instance {
             let mut env = self.jvm.attach_current_thread()?;
 
-            log_once::trace_once!("Calling SystemManager.updateAllSystems() with dt: {}", dt);
-            env.call_method(
-                manager_ref,
-                "updateAllSystems",
-                "(F)V",
-                &[JValue::Float(dt)],
-            )?;
+            let result = (|| -> anyhow::Result<()> {
+                log_once::trace_once!("Calling SystemManager.updateAllSystems() with dt: {}", dt);
+                env.call_method(
+                    manager_ref,
+                    "updateAllSystems",
+                    "(F)V",
+                    &[JValue::Float(dt)],
+                )?;
+
+                log_once::trace_once!("Updated all systems with dt: {}", dt);
+
+                Ok(())
+            })();
 
-            log_once::trace_once!("Updated all systems with dt: {}", dt);
+            Self::get_exception(&mut env)?;
+
+            Ok(result?)
         } else {
             return Err(anyhow::anyhow!(
                 "SystemManager not initialised when updating systems."
             ));
         }
-        Ok(())
     }
 
     pub fn physics_update_all_systems(&self, dt: f32) -> anyhow::Result<()> {
         if let Some(ref manager_ref) = self.system_manager_instance {
             let mut env = self.jvm.attach_current_thread()?;
 
-            log_once::trace_once!("Calling SystemManager.physicsUpdateAllSystems() with dt: {}", dt);
-            env.call_method(
-                manager_ref,
-                "physicsUpdateAllSystems",
-                "(F)V",
-                &[JValue::Float(dt)],
-            )?;
+            let result = (|| -> anyhow::Result<()> {
+                log_once::trace_once!("Calling SystemManager.physicsUpdateAllSystems() with dt: {}", dt);
+                env.call_method(
+                    manager_ref,
+                    "physicsUpdateAllSystems",
+                    "(F)V",
+                    &[JValue::Float(dt)],
+                )?;
 
-            Ok(())
+                Ok(())
+            })();
+
+            Self::get_exception(&mut env)?;
+
+            Ok(result?)
         } else {
             Err(anyhow::anyhow!(
                 "SystemManager not initialised when physics updating systems."
@@ -502,47 +612,59 @@ impl JavaContext {
         if let Some(ref manager_ref) = self.system_manager_instance {
             let mut env = self.jvm.attach_current_thread()?;
 
-            log::trace!(
+            let result = (|| -> anyhow::Result<()> {
+                log::trace!(
                 "Calling SystemManager.updateSystemsByTag() with tag: {}, dt: {}",
                 tag,
                 dt
             );
-            let tag_jstring = env.new_string(tag)?;
-            env.call_method(
-                manager_ref,
-                "updateSystemsByTag",
-                "(Ljava/lang/String;F)V",
-                &[JValue::Object(&tag_jstring), JValue::Float(dt)],
-            )?;
+                let tag_jstring = env.new_string(tag)?;
+                env.call_method(
+                    manager_ref,
+                    "updateSystemsByTag",
+                    "(Ljava/lang/String;F)V",
+                    &[JValue::Object(&tag_jstring), JValue::Float(dt)],
+                )?;
+
+                log::debug!("Updated systems for tag: {} with dt: {}", tag, dt);
+                Ok(())
+            })();
 
-            log::debug!("Updated systems for tag: {} with dt: {}", tag, dt);
+            Self::get_exception(&mut env)?;
+
+            Ok(result?)
         } else {
             return Err(anyhow::anyhow!(
                 "SystemManager not initialised when updating systems for tag: {}",
                 tag
             ));
         }
-        Ok(())
     }
 
     pub fn physics_update_systems_for_tag(&self, tag: &str, dt: f32) -> anyhow::Result<()> {
         if let Some(ref manager_ref) = self.system_manager_instance {
             let mut env = self.jvm.attach_current_thread()?;
 
-            log::trace!(
-                "Calling SystemManager.physicsUpdateSystemsByTag() with tag: {}, dt: {}",
-                tag,
-                dt
-            );
-            let tag_jstring = env.new_string(tag)?;
-            env.call_method(
-                manager_ref,
-                "physicsUpdateSystemsByTag",
-                "(Ljava/lang/String;F)V",
-                &[JValue::Object(&tag_jstring), JValue::Float(dt)],
-            )?;
+            let result = (|| -> anyhow::Result<()> {
+                log::trace!(
+                    "Calling SystemManager.physicsUpdateSystemsByTag() with tag: {}, dt: {}",
+                    tag,
+                    dt
+                );
+                let tag_jstring = env.new_string(tag)?;
+                env.call_method(
+                    manager_ref,
+                    "physicsUpdateSystemsByTag",
+                    "(Ljava/lang/String;F)V",
+                    &[JValue::Object(&tag_jstring), JValue::Float(dt)],
+                )?;
 
-            Ok(())
+                Ok(())
+            })();
+
+            Self::get_exception(&mut env)?;
+
+            Ok(result?)
         } else {
             Err(anyhow::anyhow!(
                 "SystemManager not initialised when physics updating systems for tag: {}",
@@ -560,49 +682,55 @@ impl JavaContext {
         if let Some(ref manager_ref) = self.system_manager_instance {
             let mut env = self.jvm.attach_current_thread()?;
 
-            log::trace!(
-                "Calling SystemManager.updateSystemsForEntities() with tag: {}, count: {}, dt: {}",
-                tag,
-                entity_ids.len(),
-                dt
-            );
+            let result = (|| -> anyhow::Result<()> {
+                log::trace!(
+                    "Calling SystemManager.updateSystemsForEntities() with tag: {}, count: {}, dt: {}",
+                    tag,
+                    entity_ids.len(),
+                    dt
+                );
 
-            let tag_jstring = env.new_string(tag)?;
-            let entity_array: JLongArray = env.new_long_array(entity_ids.len() as i32)?;
-            let entity_array_raw = entity_array.as_raw();
-            log::trace!("u64 entity: {:?}", entity_ids);
-            log::trace!(
-                "i64 entity: {:?}",
-                entity_ids.iter().map(|e| *e as i64).collect::<Vec<_>>()
-            );
-            if !entity_ids.is_empty() {
-                env.set_long_array_region(
-                    entity_array,
-                    0,
-                    &entity_ids.iter().map(|e| *e as i64).collect::<Vec<_>>(),
+                let tag_jstring = env.new_string(tag)?;
+                let entity_array: JLongArray = env.new_long_array(entity_ids.len() as i32)?;
+                let entity_array_raw = entity_array.as_raw();
+                log::trace!("u64 entity: {:?}", entity_ids);
+                log::trace!(
+                    "i64 entity: {:?}",
+                    entity_ids.iter().map(|e| *e as i64).collect::<Vec<_>>()
+                );
+                if !entity_ids.is_empty() {
+                    env.set_long_array_region(
+                        entity_array,
+                        0,
+                        &entity_ids.iter().map(|e| *e as i64).collect::<Vec<_>>(),
+                    )?;
+                }
+                let entity_array_obj =
+                    unsafe { JObject::from_raw(entity_array_raw.cast::<jni::sys::_jobject>()) };
+
+                env.call_method(
+                    manager_ref,
+                    "updateSystemsForEntities",
+                    "(Ljava/lang/String;[JF)V",
+                    &[
+                        JValue::Object(&tag_jstring),
+                        JValue::Object(&entity_array_obj),
+                        JValue::Float(dt),
+                    ],
                 )?;
-            }
-            let entity_array_obj =
-                unsafe { JObject::from_raw(entity_array_raw.cast::<jni::sys::_jobject>()) };
 
-            env.call_method(
-                manager_ref,
-                "updateSystemsForEntities",
-                "(Ljava/lang/String;[JF)V",
-                &[
-                    JValue::Object(&tag_jstring),
-                    JValue::Object(&entity_array_obj),
-                    JValue::Float(dt),
-                ],
-            )?;
+                log::trace!(
+                    "Updated systems for tag: {} across {} entities with dt: {}",
+                    tag,
+                    entity_ids.len(),
+                    dt
+                );
+                Ok(())
+            })();
 
-            log::trace!(
-                "Updated systems for tag: {} across {} entities with dt: {}",
-                tag,
-                entity_ids.len(),
-                dt
-            );
-            Ok(())
+            Self::get_exception(&mut env)?;
+
+            Ok(result?)
         } else {
             Err(anyhow::anyhow!(
                 "SystemManager not initialised when updating systems for tag: {}",
@@ -620,38 +748,44 @@ impl JavaContext {
         if let Some(ref manager_ref) = self.system_manager_instance {
             let mut env = self.jvm.attach_current_thread()?;
 
-            log::trace!(
-                "Calling SystemManager.physicsUpdateSystemsForEntities() with tag: {}, count: {}, dt: {}",
-                tag,
-                entity_ids.len(),
-                dt
-            );
+            let result = (|| -> anyhow::Result<()> {
+                log::trace!(
+                    "Calling SystemManager.physicsUpdateSystemsForEntities() with tag: {}, count: {}, dt: {}",
+                    tag,
+                    entity_ids.len(),
+                    dt
+                );
 
-            let tag_jstring = env.new_string(tag)?;
-            let entity_array: JLongArray = env.new_long_array(entity_ids.len() as i32)?;
-            let entity_array_raw = entity_array.as_raw();
-            if !entity_ids.is_empty() {
-                env.set_long_array_region(
-                    entity_array,
-                    0,
-                    &entity_ids.iter().map(|e| *e as i64).collect::<Vec<_>>(),
+                let tag_jstring = env.new_string(tag)?;
+                let entity_array: JLongArray = env.new_long_array(entity_ids.len() as i32)?;
+                let entity_array_raw = entity_array.as_raw();
+                if !entity_ids.is_empty() {
+                    env.set_long_array_region(
+                        entity_array,
+                        0,
+                        &entity_ids.iter().map(|e| *e as i64).collect::<Vec<_>>(),
+                    )?;
+                }
+                let entity_array_obj =
+                    unsafe { JObject::from_raw(entity_array_raw.cast::<jni::sys::_jobject>()) };
+
+                env.call_method(
+                    manager_ref,
+                    "physicsUpdateSystemsForEntities",
+                    "(Ljava/lang/String;[JF)V",
+                    &[
+                        JValue::Object(&tag_jstring),
+                        JValue::Object(&entity_array_obj),
+                        JValue::Float(dt),
+                    ],
                 )?;
-            }
-            let entity_array_obj =
-                unsafe { JObject::from_raw(entity_array_raw.cast::<jni::sys::_jobject>()) };
 
-            env.call_method(
-                manager_ref,
-                "physicsUpdateSystemsForEntities",
-                "(Ljava/lang/String;[JF)V",
-                &[
-                    JValue::Object(&tag_jstring),
-                    JValue::Object(&entity_array_obj),
-                    JValue::Float(dt),
-                ],
-            )?;
+                Ok(())
+            })();
 
-            Ok(())
+            Self::get_exception(&mut env)?;
+
+            Ok(result?)
         } else {
             Err(anyhow::anyhow!(
                 "SystemManager not initialised when physics updating systems for tag: {}",
@@ -664,16 +798,22 @@ impl JavaContext {
         if let Some(ref manager_ref) = self.system_manager_instance {
             let mut env = self.jvm.attach_current_thread()?;
 
-            log::trace!("Calling SystemManager.unloadSystemsByTag() with tag: {}", tag);
-            let tag_jstring = env.new_string(tag)?;
-            env.call_method(
-                manager_ref,
-                "unloadSystemsByTag",
-                "(Ljava/lang/String;)V",
-                &[JValue::Object(&tag_jstring)],
-            )?;
+            let result = (|| -> anyhow::Result<()> {
+                log::trace!("Calling SystemManager.unloadSystemsByTag() with tag: {}", tag);
+                let tag_jstring = env.new_string(tag)?;
+                env.call_method(
+                    manager_ref,
+                    "unloadSystemsByTag",
+                    "(Ljava/lang/String;)V",
+                    &[JValue::Object(&tag_jstring)],
+                )?;
 
-            Ok(())
+                Ok(())
+            })();
+
+            Self::get_exception(&mut env)?;
+
+            Ok(result?)
         } else {
             Err(anyhow::anyhow!(
                 "SystemManager not initialised when unloading systems for tag: {}",
@@ -686,19 +826,25 @@ impl JavaContext {
         if let Some(ref manager_ref) = self.system_manager_instance {
             let mut env = self.jvm.attach_current_thread()?;
 
-            log::trace!(
-                "Calling SystemManager.destroySystemsByTag() with tag: {}",
-                tag
-            );
-            let tag_jstring = env.new_string(tag)?;
-            env.call_method(
-                manager_ref,
-                "destroySystemsByTag",
-                "(Ljava/lang/String;)V",
-                &[JValue::Object(&tag_jstring)],
-            )?;
+            let result = (|| -> anyhow::Result<()> {
+                log::trace!(
+                    "Calling SystemManager.destroySystemsByTag() with tag: {}",
+                    tag
+                );
+                let tag_jstring = env.new_string(tag)?;
+                env.call_method(
+                    manager_ref,
+                    "destroySystemsByTag",
+                    "(Ljava/lang/String;)V",
+                    &[JValue::Object(&tag_jstring)],
+                )?;
 
-            Ok(())
+                Ok(())
+            })();
+
+            Self::get_exception(&mut env)?;
+
+            Ok(result?)
         } else {
             Err(anyhow::anyhow!(
                 "SystemManager not initialised when destroying systems for tag: {}",
@@ -711,9 +857,15 @@ impl JavaContext {
         if let Some(ref manager_ref) = self.system_manager_instance {
             let mut env = self.jvm.attach_current_thread()?;
 
-            log::trace!("Calling SystemManager.unloadAllSystems()");
-            env.call_method(manager_ref, "unloadAllSystems", "()V", &[])?;
-            Ok(())
+            let result = (|| -> anyhow::Result<()> {
+                log::trace!("Calling SystemManager.unloadAllSystems()");
+                env.call_method(manager_ref, "unloadAllSystems", "()V", &[])?;
+                Ok(())
+            })();
+
+            Self::get_exception(&mut env)?;
+
+            Ok(result?)
         } else {
             Err(anyhow::anyhow!(
                 "SystemManager not initialised when unloading all systems."
@@ -725,16 +877,22 @@ impl JavaContext {
         if let Some(ref manager_ref) = self.system_manager_instance {
             let mut env = self.jvm.attach_current_thread()?;
 
-            log::trace!("Calling SystemManager.getSystemCount() for tag: {}", tag);
-            let tag_jstring = env.new_string(tag)?;
-            let result = env.call_method(
-                manager_ref,
-                "getSystemCount",
-                "(Ljava/lang/String;)I",
-                &[JValue::Object(&tag_jstring)],
-            )?;
+            let result = (|| -> anyhow::Result<i32> {
+                log::trace!("Calling SystemManager.getSystemCount() for tag: {}", tag);
+                let tag_jstring = env.new_string(tag)?;
+                let result = env.call_method(
+                    manager_ref,
+                    "getSystemCount",
+                    "(Ljava/lang/String;)I",
+                    &[JValue::Object(&tag_jstring)],
+                )?;
 
-            Ok(result.i()?)
+                Ok(result.i()?)
+            })();
+
+            Self::get_exception(&mut env)?;
+
+            Ok(result?)
         } else {
             Err(anyhow::anyhow!(
                 "SystemManager not initialised when getting system count for tag: {}",
@@ -747,16 +905,22 @@ impl JavaContext {
         if let Some(ref manager_ref) = self.system_manager_instance {
             let mut env = self.jvm.attach_current_thread()?;
 
-            log::trace!("Calling SystemManager.hasSystemsForTag() for tag: {}", tag);
-            let tag_jstring = env.new_string(tag)?;
-            let result = env.call_method(
-                manager_ref,
-                "hasSystemsForTag",
-                "(Ljava/lang/String;)Z",
-                &[JValue::Object(&tag_jstring)],
-            )?;
+            let result = (|| -> anyhow::Result<bool> {
+                log::trace!("Calling SystemManager.hasSystemsForTag() for tag: {}", tag);
+                let tag_jstring = env.new_string(tag)?;
+                let result = env.call_method(
+                    manager_ref,
+                    "hasSystemsForTag",
+                    "(Ljava/lang/String;)Z",
+                    &[JValue::Object(&tag_jstring)],
+                )?;
 
-            Ok(result.z()?)
+                Ok(result.z()?)
+            })();
+
+            Self::get_exception(&mut env)?;
+
+            Ok(result?)
         } else {
             Err(anyhow::anyhow!(
                 "SystemManager not initialised when checking for systems for tag: {}",
@@ -769,10 +933,16 @@ impl JavaContext {
         if let Some(ref manager_ref) = self.system_manager_instance {
             let mut env = self.jvm.attach_current_thread()?;
 
-            log::trace!("Calling SystemManager.getTotalSystemCount()");
-            let result = env.call_method(manager_ref, "getTotalSystemCount", "()I", &[])?;
+            let result = (|| -> anyhow::Result<i32> {
+                log::trace!("Calling SystemManager.getTotalSystemCount()");
+                let result = env.call_method(manager_ref, "getTotalSystemCount", "()I", &[])?;
+
+                Ok(result.i()?)
+            })();
+
+            Self::get_exception(&mut env)?;
 
-            Ok(result.i()?)
+            Ok(result?)
         } else {
             Err(anyhow::anyhow!(
                 "SystemManager not initialised when getting total system count."
diff --git a/eucalyptus-core/src/scripting/jni/utils.rs b/eucalyptus-core/src/scripting/jni/utils.rs
index 4d68f4b..80281e7 100644
--- a/eucalyptus-core/src/scripting/jni/utils.rs
+++ b/eucalyptus-core/src/scripting/jni/utils.rs
@@ -1,32 +1,11 @@
 //! Utilities for JNI and JVM based code.
 
-use glam::{DVec3, Vec3};
+use crate::scripting::native::DropbearNativeError;
+use crate::scripting::result::DropbearNativeResult;
+use glam::DVec3;
+use jni::objects::{JObject, JValue};
+use jni::sys::jint;
 use jni::JNIEnv;
-use jni::objects::{JFloatArray, JObject, JValue};
-use jni::sys::{jfloatArray, jint};
-
-pub fn new_float_array(env: &mut JNIEnv, x: f32, y: f32) -> jfloatArray {
-    let java_array: JFloatArray = match env.new_float_array(2) {
-        Ok(v) => v,
-        Err(e) => {
-            eprintln!("[ERROR] Failed to create float array: {}", e);
-            return std::ptr::null_mut();
-        }
-    };
-    let elements: [f32; 2] = [x, y];
-    match env.set_float_array_region(&java_array, 0, &elements) {
-        Ok(()) => java_array.into_raw(),
-        Err(e) => {
-            eprintln!("[ERROR] Error setting float array region: {}", e);
-            env.throw_new(
-                "java/lang/RuntimeException",
-                "Failed to set float array region",
-            )
-                .unwrap();
-            std::ptr::null_mut()
-        }
-    }
-}
 
 const JAVA_MOUSE_BUTTON_LEFT: jint = 0;
 const JAVA_MOUSE_BUTTON_RIGHT: jint = 1;
@@ -41,122 +20,20 @@ pub fn java_button_to_rust(button_code: jint) -> Option<winit::event::MouseButto
         JAVA_MOUSE_BUTTON_MIDDLE => Some(winit::event::MouseButton::Middle),
         JAVA_MOUSE_BUTTON_BACK => Some(winit::event::MouseButton::Back),
         JAVA_MOUSE_BUTTON_FORWARD => Some(winit::event::MouseButton::Forward),
-        other if other >= 0 => Some(winit::event::MouseButton::Other(other as u16)), // Assuming Other uses the int directly
+        other if other >= 0 => Some(winit::event::MouseButton::Other(other as u16)),
         _ => None,
     }
 }
 
-pub fn create_vector3<'a>(
-    env: &mut JNIEnv<'a>,
-    x: f64,
-    y: f64,
-    z: f64,
-) -> anyhow::Result<JObject<'a>> {
-    let vector3_class = env.find_class("com/dropbear/math/Vector3")?;
-
-    let x_obj = env
-        .call_static_method(
-            "java/lang/Double",
-            "valueOf",
-            "(D)Ljava/lang/Double;",
-            &[JValue::Double(x)],
-        )?
-        .l()?;
-
-    let y_obj = env
-        .call_static_method(
-            "java/lang/Double",
-            "valueOf",
-            "(D)Ljava/lang/Double;",
-            &[JValue::Double(y)],
-        )?
-        .l()?;
-
-    let z_obj = env
-        .call_static_method(
-            "java/lang/Double",
-            "valueOf",
-            "(D)Ljava/lang/Double;",
-            &[JValue::Double(z)],
-        )?
-        .l()?;
-
-    let vector3 = env.new_object(
-        vector3_class,
-        "(Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;)V",
-        &[
-            JValue::Object(&x_obj),
-            JValue::Object(&y_obj),
-            JValue::Object(&z_obj),
-        ],
-    )?;
-
-    Ok(vector3)
-}
-
-pub fn extract_vector3(env: &mut JNIEnv, vector_obj: &JObject) -> Option<Vec3> {
-    let x_obj = env
-        .get_field(vector_obj, "x", "Ljava/lang/Number;")
-        .ok()?
-        .l()
-        .ok()?;
-    let y_obj = env
-        .get_field(vector_obj, "y", "Ljava/lang/Number;")
-        .ok()?
-        .l()
-        .ok()?;
-    let z_obj = env
-        .get_field(vector_obj, "z", "Ljava/lang/Number;")
-        .ok()?
-        .l()
-        .ok()?;
-
-    let x = env
-        .call_method(&x_obj, "doubleValue", "()D", &[])
-        .ok()?
-        .d()
-        .ok()?;
-    let y = env
-        .call_method(&y_obj, "doubleValue", "()D", &[])
-        .ok()?
-        .d()
-        .ok()?;
-    let z = env
-        .call_method(&z_obj, "doubleValue", "()D", &[])
-        .ok()?
-        .d()
-        .ok()?;
-
-    Some(Vec3::new(x as f32, y as f32, z as f32))
-}
-
 /// Trait that defines conversion from a Java object to a Rust struct.
 pub trait FromJObject {
     /// Converts a Java object to a Rust struct.
-    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> anyhow::Result<Self>
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self>
     where
         Self: Sized;
 }
 
-impl FromJObject for DVec3 {
-    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> anyhow::Result<Self>
-    where
-        Self: Sized
-    {
-        let x_obj = env
-            .get_field(obj, "x", "Ljava/lang/Number;")?.l()?;
-        let y_obj = env
-            .get_field(obj, "y", "Ljava/lang/Number;")?.l()?;
-        let z_obj = env
-            .get_field(obj, "z", "Ljava/lang/Number;")?.l()?;
-
-        let x = env
-            .call_method(&x_obj, "doubleValue", "()D", &[])?.d()?;
-        let y = env
-            .call_method(&y_obj, "doubleValue", "()D", &[])?.d()?;
-        let z = env
-            .call_method(&z_obj, "doubleValue", "()D", &[])?.d()?;
-
-        Ok(DVec3::new(x, y, z))
-    }
+/// Converts a Rust object (struct or enum) into a java [JObject]
+pub trait ToJObject {
+    fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>>;
 }
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting/native.rs b/eucalyptus-core/src/scripting/native.rs
index 03cb8b0..9cb00c9 100644
--- a/eucalyptus-core/src/scripting/native.rs
+++ b/eucalyptus-core/src/scripting/native.rs
@@ -1,29 +1,18 @@
 //! Deals with Kotlin/Native library loading for different platforms.
 #![allow(clippy::missing_safety_doc)]
 
-pub mod exports;
+// pub mod exports;
 pub mod sig;
 pub mod utils;
 
 use crate::scripting::error::LastErrorMessage;
-use crate::scripting::native::sig::{
-    DestroyAll,
-    DestroyInScopeTagged,
-    DestroyTagged,
-    Init,
-    LoadTagged,
-    PhysicsUpdateAll,
-    PhysicsUpdateTagged,
-    PhysicsUpdateWithEntities,
-    UpdateAll,
-    UpdateTagged,
-    UpdateWithEntities,
-};
+use crate::scripting::native::sig::{DestroyAll, DestroyInScopeTagged, DestroyTagged, Init, LoadTagged, PhysicsUpdateAll, PhysicsUpdateTagged, PhysicsUpdateWithEntities, UpdateAll, UpdateTagged, UpdateWithEntities};
 use anyhow::anyhow;
 use libloading::{Library, Symbol};
 use std::ffi::CString;
+use std::fmt::{Display, Formatter};
 use std::path::Path;
-use crate::scripting::native::exports::dropbear_common::DropbearContext;
+use crate::scripting::DropbearContext;
 
 pub struct NativeLibrary {
     #[allow(dead_code)]
@@ -263,13 +252,12 @@ impl NativeLibrary {
             return Ok(());
         }
 
-        let code_label = DropbearNativeError::code_to_string(result);
         let last_error = self
             .get_last_error()
             .map(|msg| format!(": {msg}"))
             .unwrap_or_default();
 
-        anyhow::bail!("Native script {} failed ({}{})", operation, code_label, last_error);
+        anyhow::bail!("Native script {} failed ({})", operation, last_error);
     }
 }
 
@@ -362,6 +350,8 @@ impl LastErrorMessage for NativeLibrary {
     }
 }
 
+#[repr(C)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 /// Displays the types of errors that can be returned by the native library.
 pub enum DropbearNativeError {
     /// An error in the case the function returns an unsigned value.
@@ -369,8 +359,7 @@ pub enum DropbearNativeError {
     /// Subtract [`DropbearNativeError::UnsignedGenericError`] with another value
     /// to get the alternative unsigned error.
     UnsignedGenericError = 65535,
-    /// Used to describe an error that doesn't have a particular cause. All it knows
-    /// is that there was an error thrown.
+    /// An error that is thrown, but doesn't have any attached context.
     GenericError = 1,
     /// The default return code for a successful FFI operation.
     Success = 0,
@@ -437,6 +426,31 @@ pub enum DropbearNativeError {
     GamepadNotFound = -11,
     /// When the argument is invalid
     InvalidArgument = -12,
+    /// The handle provided does not exist. Could be for an asset, entity, or other handle type.
+    NoSuchHandle = -13,
+    /// Failed to create a Java object via JNI.
+    JNIFailedToCreateObject = -14,
+    /// Failed to get a field from a Java object via JNI.
+    JNIFailedToGetField = -15,
+    /// Failed to find a Java class via JNI.
+    JNIClassNotFound = -16,
+    /// Failed to find a Java method via JNI.
+    JNIMethodNotFound = -17,
+    /// Failed to unwrap a Java object via JNI.
+    JNIUnwrapFailed = -18,
+    /// Generic asset error. There was an error thrown, however there is no context attached. 
+    GenericAssetError = -19,
+    /// The provided uri (either euca:// or https) was invalid and formatted wrong.
+    InvalidURI = -20,
+    /// The asset provided by the handle is wrong.
+    AssetNotFound = -21,
+    /// When a handle has been inputted wrongly.
+    InvalidHandle = -22,
+    PhysicsObjectNotFound = -23,
+    
+    /// The entity provided was invalid, likely not from [hecs::Entity::from_bits].
+    InvalidEntity = -100,
+
 
     /// The CString (or `*const c_char`) contained invalid UTF-8 while being decoded.
     InvalidUTF8 = -108,
@@ -445,26 +459,40 @@ pub enum DropbearNativeError {
     ///
     /// The number `1274` comes from the total sum of the word "UnknownError" in decimal
     UnknownError = -1274,
-
 }
 
-impl DropbearNativeError {
-    /// Attempts to convert an [`i32`] numerical code to a [`String`] for better error displaying
-    pub fn code_to_string(code: i32) -> String {
-        match code {
-            x if x == DropbearNativeError::NullPointer as i32 => "NullPointer (-1)".to_string(),
-            x if x == DropbearNativeError::QueryFailed as i32 => "QueryFailed (-2)".to_string(),
-            x if x == DropbearNativeError::EntityNotFound as i32 => "EntityNotFound (-3)".to_string(),
-            x if x == DropbearNativeError::NoSuchComponent as i32 => "NoSuchComponent (-4)".to_string(),
-            x if x == DropbearNativeError::NoSuchEntity as i32 => "NoSuchEntity (-5)".to_string(),
-            x if x == DropbearNativeError::WorldInsertError as i32 => "WorldInsertError (-6)".to_string(),
-            x if x == DropbearNativeError::SendError as i32 => "SendError (-7)".to_string(),
-            x if x == DropbearNativeError::InvalidUTF8 as i32 => "InvalidUTF8 (-108)".to_string(),
-            x if x == DropbearNativeError::UnknownError as i32 => "UnknownError (-1274)".to_string(),
-            x if x == DropbearNativeError::UnsignedGenericError as i32 => {
-                "UnsignedGenericError (65535)".to_string()
-            }
-            _ => format!("code {code}"),
-        }
+impl Display for DropbearNativeError {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        f.write_str(match self {
+            DropbearNativeError::NullPointer => "NullPointer (-1)",
+            DropbearNativeError::QueryFailed => "QueryFailed (-2)",
+            DropbearNativeError::EntityNotFound => "EntityNotFound (-3)",
+            DropbearNativeError::NoSuchComponent => "NoSuchComponent (-4)",
+            DropbearNativeError::NoSuchEntity => "NoSuchEntity (-5)",
+            DropbearNativeError::WorldInsertError => "WorldInsertError (-6)",
+            DropbearNativeError::SendError => "SendError (-7)",
+            DropbearNativeError::InvalidUTF8 => "InvalidUTF8 (-108)",
+            DropbearNativeError::UnknownError => "UnknownError (-1274)",
+            DropbearNativeError::UnsignedGenericError => "UnsignedGenericError (65535)",
+            DropbearNativeError::Success => "Success (0) [should not be displayed]",
+            DropbearNativeError::CStringError => "CStringError (-8)",
+            DropbearNativeError::BufferTooSmall => "BufferTooSmall (-9)",
+            DropbearNativeError::PrematureSceneSwitch => "PrematureSceneSwitch (-10)",
+            DropbearNativeError::GamepadNotFound => "GamepadNotFound (-11)",
+            DropbearNativeError::InvalidArgument => "InvalidArgument (-12)",
+            DropbearNativeError::NoSuchHandle => "NoSuchHandle (-13)",
+            DropbearNativeError::JNIFailedToCreateObject => "JNIFailedToCreateObject (-14)",
+            DropbearNativeError::JNIFailedToGetField => "JNIFailedToGetField (-15)",
+            DropbearNativeError::JNIClassNotFound => "JNIClassNotFound (-16)",
+            DropbearNativeError::JNIMethodNotFound => "JNIMethodNotFound (-17)",
+            DropbearNativeError::JNIUnwrapFailed => "JNIUnwrapFailed (-18)",
+            DropbearNativeError::InvalidEntity => "InvalidEntity (-100)",
+            DropbearNativeError::GenericAssetError => "GenericAssetError (-19)",
+            DropbearNativeError::InvalidURI => "InvalidURI (-20)",
+            DropbearNativeError::AssetNotFound => "AssetNotFound (-21)",
+            DropbearNativeError::InvalidHandle => "InvalidHandle (-22)",
+            DropbearNativeError::GenericError => "GenericError (1)",
+            DropbearNativeError::PhysicsObjectNotFound => "PhysicsObjectNotFound (-23)",
+        })
     }
 }
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting/native/exports/dropbear_common.rs b/eucalyptus-core/src/scripting/native/exports/dropbear_common.rs
index f002c80..9856d93 100644
--- a/eucalyptus-core/src/scripting/native/exports/dropbear_common.rs
+++ b/eucalyptus-core/src/scripting/native/exports/dropbear_common.rs
@@ -11,16 +11,4 @@ pub type DropbearNativeReturn = i32;
 pub type Handle = i64;
 
 /// A helper type that defines a value that can either be a 0 or 1.
-pub type Bool = i32;
-
-/// Describes all the different pointers that can be passed into a scripting
-/// module.
-#[repr(C)]
-pub struct DropbearContext {
-    pub world: WorldPtr,
-    pub input: InputStatePtr,
-    pub graphics: CommandBufferPtr,
-    pub assets: AssetRegistryPtr,
-    pub scene_loader: SceneLoaderPtr,
-    pub physics_state: PhysicsStatePtr,
-}
\ No newline at end of file
+pub type Bool = i32;
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting/native/sig.rs b/eucalyptus-core/src/scripting/native/sig.rs
index 596a14a..c03a1d0 100644
--- a/eucalyptus-core/src/scripting/native/sig.rs
+++ b/eucalyptus-core/src/scripting/native/sig.rs
@@ -1,7 +1,6 @@
-/// Different signatures for Native implementations
+/// Different input signatures for Native implementations
 use std::ffi::c_char;
-
-use crate::scripting::native::exports::dropbear_common::DropbearContext;
+use crate::scripting::DropbearContext;
 
 /// CName: `dropbear_init`
 pub type Init = unsafe extern "C" fn(dropbear_context: *const DropbearContext) -> i32;
@@ -40,4 +39,5 @@ pub type DestroyAll = unsafe extern "C" fn() -> i32;
 /// CName: `dropbear_get_last_error_message`
 pub type GetLastErrorMessage = unsafe extern "C" fn() -> *const c_char;
 /// CName: `dropbear_set_last_error_message`
-pub type SetLastErrorMessage = unsafe extern "C" fn(msg: *const c_char);
\ No newline at end of file
+pub type SetLastErrorMessage = unsafe extern "C" fn(msg: *const c_char);
+
diff --git a/eucalyptus-core/src/scripting/native/utils.rs b/eucalyptus-core/src/scripting/native/utils.rs
index 9365549..edff3ad 100644
--- a/eucalyptus-core/src/scripting/native/utils.rs
+++ b/eucalyptus-core/src/scripting/native/utils.rs
@@ -1,33 +1,4 @@
-use dropbear_engine::entity::Transform;
-use glam::{DQuat, DVec3};
 use dropbear_engine::gilrs;
-use crate::scripting::native::exports::dropbear_math::NativeTransform;
-
-pub fn write_native_transform(target: &mut NativeTransform, transform: &Transform) {
-    target.position_x = transform.position.x;
-    target.position_y = transform.position.y;
-    target.position_z = transform.position.z;
-    target.rotation_x = transform.rotation.x;
-    target.rotation_y = transform.rotation.y;
-    target.rotation_z = transform.rotation.z;
-    target.rotation_w = transform.rotation.w;
-    target.scale_x = transform.scale.x;
-    target.scale_y = transform.scale.y;
-    target.scale_z = transform.scale.z;
-}
-
-pub fn native_transform_to_transform(native: &NativeTransform) -> Transform {
-    Transform {
-        position: DVec3::new(native.position_x, native.position_y, native.position_z),
-        rotation: DQuat::from_xyzw(
-            native.rotation_x,
-            native.rotation_y,
-            native.rotation_z,
-            native.rotation_w,
-        ),
-        scale: DVec3::new(native.scale_x, native.scale_y, native.scale_z),
-    }
-}
 
 pub fn button_from_ordinal(ordinal: i32) -> Result<gilrs::Button, ()> {
     match ordinal {
@@ -53,4 +24,20 @@ pub fn button_from_ordinal(ordinal: i32) -> Result<gilrs::Button, ()> {
         19 => Ok(gilrs::Button::DPadRight),
         _ => Err(()),
     }
+}
+
+#[dropbear_macro::impl_c_api]
+mod string {
+    use crate::scripting::result::DropbearNativeResult;
+    use std::ffi::{c_char, CString};
+
+    pub fn dropbear_free_string(ptr: *mut c_char) -> DropbearNativeResult<()> {
+        if ptr.is_null() {
+            return Ok(());
+        }
+        unsafe {
+            let _ = CString::from_raw(ptr);
+        }
+        Ok(())
+    }
 }
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting/result.rs b/eucalyptus-core/src/scripting/result.rs
new file mode 100644
index 0000000..b723508
--- /dev/null
+++ b/eucalyptus-core/src/scripting/result.rs
@@ -0,0 +1,4 @@
+use crate::scripting::native::DropbearNativeError;
+
+/// A result type for dropbear based native functions.
+pub type DropbearNativeResult<T> = Result<T, DropbearNativeError>;
\ No newline at end of file
diff --git a/eucalyptus-core/src/states.rs b/eucalyptus-core/src/states.rs
index 575f02e..a6ee104 100644
--- a/eucalyptus-core/src/states.rs
+++ b/eucalyptus-core/src/states.rs
@@ -1,4 +1,7 @@
-//!
+//! Different states and objects that exist in the scene. 
+//! 
+//! It's really just a "throw everything in here, organise later". 
+
 use crate::camera::{CameraComponent, CameraType};
 use crate::config::{ProjectConfig, ResourceConfig, SourceConfig};
 use crate::scene::SceneConfig;
@@ -8,7 +11,6 @@ use dropbear_engine::entity::{MaterialOverride, MeshRenderer, Transform};
 use dropbear_engine::lighting::LightComponent;
 use dropbear_engine::utils::ResourceReference;
 use dropbear_macro::SerializableComponent;
-use egui::Ui;
 use once_cell::sync::Lazy;
 use parking_lot::RwLock;
 use serde::{Deserialize, Serialize};
@@ -19,6 +21,7 @@ use std::fmt::{Display, Formatter};
 use std::ops::{Deref, DerefMut};
 use std::path::PathBuf;
 use hecs::World;
+use crate::properties::Value;
 
 /// A global "singleton" that contains the configuration of a project.
 pub static PROJECT: Lazy<RwLock<ProjectConfig>> =
@@ -223,151 +226,6 @@ pub struct Property {
     pub value: Value,
 }
 
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-pub enum Value {
-    String(String),
-    Int(i64),
-    Float(f64),
-    Bool(bool),
-    Vec3([f32; 3]),
-}
-
-impl Default for Value {
-    fn default() -> Self {
-        Self::String(String::new())
-    }
-}
-
-impl Display for Value {
-    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
-        let string: String = match self {
-            Value::String(_) => "String".into(),
-            Value::Int(_) => "Int".into(),
-            Value::Float(_) => "Float".into(),
-            Value::Bool(_) => "Bool".into(),
-            Value::Vec3(_) => "Vec3".into(),
-        };
-        write!(f, "{}", string)
-    }
-}
-
-/// Properties for an entity, typically queries with `entity.getProperty<Float>` and `entity.setProperty(67)`
-#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, SerializableComponent)]
-pub struct CustomProperties {
-    pub custom_properties: Vec<Property>,
-    pub next_id: u64,
-}
-
-impl CustomProperties {
-    /// Creates a new [CustomProperties]
-    pub fn new() -> Self {
-        Self {
-            custom_properties: Vec::new(),
-            next_id: 0,
-        }
-    }
-
-    /// Sets the property based on the [Value] (type) and its key.
-    ///
-    /// If the value does NOT exist, it will be created.
-    /// If the value does exist, it will replace the contents of that item.
-    pub fn set_property(&mut self, key: String, value: Value) {
-        if let Some(prop) = self.custom_properties.iter_mut().find(|p| p.key == key) {
-            prop.value = value;
-        } else {
-            self.custom_properties.push(Property {
-                id: self.next_id,
-                key,
-                value,
-            });
-            self.next_id += 1;
-        }
-    }
-
-    /// Fetches the property by its key.
-    pub fn get_property(&self, key: &str) -> Option<&Value> {
-        self.custom_properties
-            .iter()
-            .find(|p| p.key == key)
-            .map(|p| &p.value)
-    }
-
-    /// Fetches the float property
-    pub fn get_float(&self, key: &str) -> Option<f64> {
-        match self.get_property(key)? {
-            Value::Float(f) => Some(*f),
-            _ => None,
-        }
-    }
-
-    /// Fetches the integer property
-    pub fn get_int(&self, key: &str) -> Option<i64> {
-        match self.get_property(key)? {
-            Value::Int(i) => Some(*i),
-            _ => None,
-        }
-    }
-
-    /// Creates a new property based on a key and a value.
-    ///
-    /// It will push that value again to the property vector.
-    pub fn add_property(&mut self, key: String, value: Value) {
-        self.custom_properties.push(Property {
-            id: self.next_id,
-            key,
-            value,
-        });
-        self.next_id += 1;
-    }
-
-    /// Shows a template of the different values when inspected as a component in the editor.
-    pub fn show_value_editor(ui: &mut Ui, value: &mut Value) -> bool {
-        match value {
-            Value::String(s) => ui.text_edit_singleline(s).changed(),
-            Value::Int(i) => ui
-                .add(egui::Slider::new(i, -1000..=1000).text(""))
-                .changed(),
-            Value::Float(f) => ui
-                .add(egui::Slider::new(f, -100.0..=100.0).text(""))
-                .changed(),
-            Value::Bool(b) => ui.checkbox(b, "").changed(),
-            Value::Vec3(vec) => {
-                let mut changed = false;
-                ui.horizontal(|ui| {
-                    changed |= ui
-                        .add(
-                            egui::Slider::new(&mut vec[0], -10.0..=10.0)
-                                .text("X")
-                                .fixed_decimals(2),
-                        )
-                        .changed();
-                    changed |= ui
-                        .add(
-                            egui::Slider::new(&mut vec[1], -10.0..=10.0)
-                                .text("Y")
-                                .fixed_decimals(2),
-                        )
-                        .changed();
-                    changed |= ui
-                        .add(
-                            egui::Slider::new(&mut vec[2], -10.0..=10.0)
-                                .text("Z")
-                                .fixed_decimals(2),
-                        )
-                        .changed();
-                });
-                changed
-            }
-        }
-    }
-}
-
-impl Default for CustomProperties {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
 // A serializable configuration struct for the [Light] type
 #[derive(Debug, Serialize, Deserialize, Clone, SerializableComponent)]
 pub struct Light {
diff --git a/eucalyptus-core/src/transform.rs b/eucalyptus-core/src/transform.rs
new file mode 100644
index 0000000..b6a2d38
--- /dev/null
+++ b/eucalyptus-core/src/transform.rs
@@ -0,0 +1,377 @@
+use crate::scripting::jni::utils::{FromJObject, ToJObject};
+use crate::scripting::native::DropbearNativeError;
+use crate::scripting::result::DropbearNativeResult;
+use dropbear_engine::entity::{EntityTransform, Transform};
+use glam::{DQuat, DVec3};
+use ::jni::objects::{JObject, JValue};
+use ::jni::JNIEnv;
+use crate::types::Vector3;
+
+impl FromJObject for Transform {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
+        let pos_val = env.get_field(obj, "position", "Lcom/dropbear/math/Vector3d;")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?;
+
+        let pos_obj = pos_val.l()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let rot_val = env.get_field(obj, "rotation", "Lcom/dropbear/math/Quaterniond;")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?;
+
+        let rot_obj = rot_val.l()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let scale_val = env.get_field(obj, "scale", "Lcom/dropbear/math/Vector3d;")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?;
+
+        let scale_obj = scale_val.l()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let position: DVec3 = Vector3::from_jobject(env, &pos_obj)?.into();
+        let scale: DVec3 = Vector3::from_jobject(env, &scale_obj)?.into();
+
+        let mut get_double = |field: &str| -> DropbearNativeResult<f64> {
+            env.get_field(&rot_obj, field, "D")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+                .d()
+                .map_err(|_| DropbearNativeError::JNIUnwrapFailed)
+        };
+
+        let rx = get_double("x")?;
+        let ry = get_double("y")?;
+        let rz = get_double("z")?;
+        let rw = get_double("w")?;
+
+        let rotation = DQuat::from_xyzw(rx, ry, rz, rw);
+
+        Ok(Transform {
+            position,
+            rotation,
+            scale,
+        })
+    }
+}
+
+impl ToJObject for Transform {
+    fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
+        let cls = env.find_class("com/dropbear/math/Transform")
+            .map_err(|e| {
+                eprintln!("Could not find Transform class: {:?}", e);
+                DropbearNativeError::JNIClassNotFound
+            })?;
+
+        let p = self.position;
+        let r = self.rotation;
+        let s = self.scale;
+
+
+        let args = [
+            JValue::Double(p.x), JValue::Double(p.y), JValue::Double(p.z),
+            JValue::Double(r.x), JValue::Double(r.y), JValue::Double(r.z), JValue::Double(r.w),
+            JValue::Double(s.x), JValue::Double(s.y), JValue::Double(s.z),
+        ];
+
+        let obj = env.new_object(cls, "(DDDDDDDDDD)V", &args)
+            .map_err(|e| {
+                eprintln!("Failed to create Transform object: {:?}", e);
+                DropbearNativeError::JNIFailedToCreateObject
+            })?;
+
+        Ok(obj)
+    }
+}
+
+impl FromJObject for EntityTransform {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
+        let local_val = env.get_field(obj, "local", "Lcom/dropbear/math/Transform;")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?;
+
+        let local_obj = local_val.l()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let world_val = env.get_field(obj, "world", "Lcom/dropbear/math/Transform;")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?;
+
+        let world_obj = world_val.l()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let local = Transform::from_jobject(env, &local_obj)?;
+        let world = Transform::from_jobject(env, &world_obj)?;
+
+        Ok(EntityTransform::new(local, world))
+    }
+}
+
+impl ToJObject for EntityTransform {
+    fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
+        let cls = env.find_class("com/dropbear/components/EntityTransform")
+            .map_err(|e| {
+                eprintln!("Could not find EntityTransform class: {:?}", e);
+                DropbearNativeError::JNIClassNotFound
+            })?;
+
+        let local_obj = self.local().to_jobject(env)?;
+        let world_obj = self.world().to_jobject(env)?;
+
+        let args = [
+            JValue::Object(&local_obj),
+            JValue::Object(&world_obj)
+        ];
+
+        let obj = env.new_object(
+            cls,
+            "(Lcom/dropbear/math/Transform;Lcom/dropbear/math/Transform;)V",
+            &args
+        ).map_err(|e| {
+            eprintln!("Failed to create EntityTransform object: {:?}", e);
+            DropbearNativeError::JNIFailedToCreateObject
+        })?;
+
+        Ok(obj)
+    }
+}
+
+pub mod shared {
+    use dropbear_engine::entity::EntityTransform;
+    use hecs::{Entity, World};
+
+    pub fn entity_transform_exists_for_entity(world: &World, entity: Entity) -> bool {
+        world.get::<&EntityTransform>(entity).is_ok()
+    }
+}
+
+pub mod jni {
+    #![allow(non_snake_case)]
+    use crate::convert_jlong_to_entity;
+    use crate::hierarchy::EntityTransformExt;
+    use crate::scripting::jni::utils::{FromJObject, ToJObject};
+    use dropbear_engine::entity::EntityTransform;
+    use hecs::World;
+    use jni::objects::{JClass, JObject};
+    use jni::sys::{jboolean, jlong, jobject};
+    use jni::JNIEnv;
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_EntityTransformNative_entityTransformExistsForEntity(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jboolean {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = convert_jlong_to_entity!(entity_id);
+
+        if world.get::<&EntityTransform>(entity).is_ok() { 1 } else { 0 }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_EntityTransformNative_getLocalTransform(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = convert_jlong_to_entity!(entity_id);
+
+        if let Ok(et) = world.get::<&EntityTransform>(entity) {
+            match et.local().to_jobject(&mut env) {
+                Ok(obj) => obj.into_raw(),
+                Err(_) => std::ptr::null_mut(),
+            }
+        } else {
+            let _ = env.throw_new("java/lang/RuntimeException", "Entity missing EntityTransform");
+            std::ptr::null_mut()
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_EntityTransformNative_setLocalTransform(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        transform_obj: JObject,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = convert_jlong_to_entity!(entity_id);
+
+        let new_transform = match dropbear_engine::entity::Transform::from_jobject(&mut env, &transform_obj) {
+            Ok(t) => t,
+            Err(e) => {
+                let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Invalid Transform: {:?}", e));
+                return;
+            }
+        };
+
+        if let Ok(mut et) = world.get::<&mut EntityTransform>(entity) {
+            *et.local_mut() = new_transform;
+        } else {
+            let _ = env.throw_new("java/lang/RuntimeException", "Entity missing EntityTransform");
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_EntityTransformNative_getWorldTransform(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = convert_jlong_to_entity!(entity_id);
+
+        if let Ok(et) = world.get::<&EntityTransform>(entity) {
+            match et.world().to_jobject(&mut env) {
+                Ok(obj) => obj.into_raw(),
+                Err(_) => std::ptr::null_mut(),
+            }
+        } else {
+            let _ = env.throw_new("java/lang/RuntimeException", "Entity missing EntityTransform");
+            std::ptr::null_mut()
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_EntityTransformNative_setWorldTransform(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        transform_obj: JObject,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = convert_jlong_to_entity!(entity_id);
+
+        let new_transform = match dropbear_engine::entity::Transform::from_jobject(&mut env, &transform_obj) {
+            Ok(t) => t,
+            Err(_) => return,
+        };
+
+        if let Ok(mut et) = world.get::<&mut EntityTransform>(entity) {
+            *et.world_mut() = new_transform;
+        } else {
+            let _ = env.throw_new("java/lang/RuntimeException", "Entity missing EntityTransform");
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_EntityTransformNative_propagateTransform(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = convert_jlong_to_entity!(entity_id);
+
+        if let Ok(et) = world.get::<&EntityTransform>(entity) {
+            let propagated = et.propagate(&world, entity);
+            match propagated.to_jobject(&mut env) {
+                Ok(obj) => obj.into_raw(),
+                Err(_) => std::ptr::null_mut(),
+            }
+        } else {
+            let _ = env.throw_new("java/lang/RuntimeException", "Entity missing EntityTransform");
+            std::ptr::null_mut()
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+    use crate::hierarchy::EntityTransformExt;
+    use crate::types::{TransformNative as Transform};
+    use dropbear_engine::entity::EntityTransform;
+    use hecs::{Entity, World};
+
+    use crate::convert_ptr;
+    use crate::ptr::WorldPtr;
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+
+    pub fn dropbear_entity_transform_exists_for_entity(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<bool> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+        DropbearNativeResult::Ok(world.get::<&EntityTransform>(entity).is_ok())
+    }
+
+    pub fn dropbear_get_local_transform(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<Transform> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(et) = world.get::<&EntityTransform>(entity) {
+            DropbearNativeResult::Ok(Transform::from(et.local().clone()))
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+
+    pub fn dropbear_set_local_transform(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: Transform,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut et) = world.get::<&mut EntityTransform>(entity) {
+            *et.local_mut() = dropbear_engine::entity::Transform::from(value);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_get_world_transform(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<Transform> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(et) = world.get::<&EntityTransform>(entity) {
+            DropbearNativeResult::Ok(Transform::from(et.world().clone()))
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_world_transform(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: Transform,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut et) = world.get::<&mut EntityTransform>(entity) {
+            *et.world_mut() = dropbear_engine::entity::Transform::from(value);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_propagate_transform(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<Transform> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(et) = world.get::<&EntityTransform>(entity) {
+            let propagated = et.propagate(world, entity);
+            DropbearNativeResult::Ok(Transform::from(propagated))
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/types.rs b/eucalyptus-core/src/types.rs
new file mode 100644
index 0000000..9266a94
--- /dev/null
+++ b/eucalyptus-core/src/types.rs
@@ -0,0 +1,473 @@
+//! FFI and C types of other library types, as used in the scripting module.
+use glam::{DQuat, DVec3};
+use jni::JNIEnv;
+use jni::objects::{JObject, JValue};
+use jni::sys::jdouble;
+use rapier3d::data::Index;
+use dropbear_engine::entity::Transform;
+use crate::scripting::jni::utils::{FromJObject, ToJObject};
+use crate::scripting::native::DropbearNativeError;
+use crate::scripting::result::DropbearNativeResult;
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct Vector3 {
+    pub x: f64,
+    pub y: f64,
+    pub z: f64,
+}
+
+impl Vector3 {
+    pub fn new(x: f64, y: f64, z: f64) -> Vector3 {
+        Vector3 {
+            x, y, z
+        }
+    }
+
+    pub fn to_array(&self) -> [f64; 3] {
+        [self.x, self.y, self.z]
+    }
+    pub fn to_float_array(&self) -> [f32; 3] {
+        [self.x as f32, self.y as f32, self.z as f32]
+    }
+}
+
+impl From<glam::DVec3> for Vector3 {
+    fn from(v: glam::DVec3) -> Self {
+        Self { x: v.x, y: v.y, z: v.z }
+    }
+}
+
+impl From<[f64; 3]> for Vector3 {
+    fn from(value: [f64; 3]) -> Self {
+        Vector3 {
+            x: value[0],
+            y: value[1],
+            z: value[2],
+        }
+    }
+}
+
+impl From<[f32; 3]> for Vector3 {
+    fn from(value: [f32; 3]) -> Self {
+        Vector3 {
+            x: value[0] as f64,
+            y: value[1] as f64,
+            z: value[2] as f64,
+        }
+    }
+}
+
+impl From<Vector3> for glam::DVec3 {
+    fn from(v: Vector3) -> Self {
+        Self::new(v.x, v.y, v.z)
+    }
+}
+
+impl FromJObject for Vector3 {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self>
+    where
+        Self: Sized
+    {
+        let class = env.find_class("com/dropbear/math/Vector3d")
+            .map_err(|_| DropbearNativeError::JNIClassNotFound)?;
+
+        if !env.is_instance_of(obj, &class)
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?
+        {
+            return Err(DropbearNativeError::InvalidArgument);
+        }
+
+        let x = env.get_field(obj, "x", "D")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .d()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let y = env.get_field(obj, "y", "D")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .d()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let z = env.get_field(obj, "z", "D")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .d()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        Ok(Vector3::new(x, y, z))
+    }
+}
+
+impl ToJObject for Vector3 {
+    fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
+        let class = env.find_class("com/dropbear/math/Vector3d")
+            .map_err(|_| DropbearNativeError::JNIClassNotFound)?;
+
+        let constructor_sig = "(DDD)V";
+
+        let args = [
+            jni::objects::JValue::Double(self.x),
+            jni::objects::JValue::Double(self.y),
+            jni::objects::JValue::Double(self.z),
+        ];
+
+        let obj = env.new_object(&class, constructor_sig, &args)
+            .map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
+
+        Ok(obj)
+    }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct Quaternion {
+    pub x: f64,
+    pub y: f64,
+    pub z: f64,
+    pub w: f64,
+}
+
+impl From<DQuat> for Quaternion {
+    fn from(value: DQuat) -> Self {
+        Self {
+            x: value.x,
+            y: value.y,
+            z: value.z,
+            w: value.w,
+        }
+    }
+}
+
+impl From<Quaternion> for glam::DQuat {
+    fn from(value: Quaternion) -> Self {
+        Self {
+            x: value.x,
+            y: value.y,
+            z: value.z,
+            w: value.w,
+        }
+    }
+}
+
+impl From<[f64; 4]> for Quaternion {
+    fn from(value: [f64; 4]) -> Self {
+        Self {
+            x: value[0],
+            y: value[1],
+            z: value[2],
+            w: value[3],
+        }
+    }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct TransformNative {
+    position: Vector3,
+    rotation: Quaternion,
+    scale: Vector3,
+}
+
+impl From<Transform> for TransformNative {
+    fn from(value: Transform) -> Self {
+        Self {
+            position: Vector3::from(value.position),
+            rotation: Quaternion::from(value.rotation),
+            scale: Vector3::from(value.scale),
+        }
+    }
+}
+
+impl From<TransformNative> for Transform {
+    fn from(value: TransformNative) -> Self {
+        Self {
+            position: DVec3::from(value.position),
+            rotation: DQuat::from(value.rotation),
+            scale: DVec3::from(value.scale),
+        }
+    }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct Vector2 {
+    pub(crate) x: f64,
+    pub(crate) y: f64,
+}
+
+impl Vector2 {
+    pub fn to_array(&self) -> [f64; 2] {
+        [self.x, self.y]
+    }
+}
+
+impl From<glam::DVec2> for Vector2 {
+    fn from(v: glam::DVec2) -> Self {
+        Self { x: v.x, y: v.y }
+    }
+}
+
+impl From<[f64; 2]> for Vector2 {
+    fn from(value: [f64; 2]) -> Self {
+        Vector2 {
+            x: value[0],
+            y: value[1],
+        }
+    }
+}
+
+impl From<Vector2> for glam::DVec2 {
+    fn from(v: Vector2) -> Self {
+        Self::new(v.x, v.y)
+    }
+}
+
+impl From<(f64, f64)> for Vector2 {
+    fn from(value: (f64, f64)) -> Self {
+        Self {
+            x: value.0,
+            y: value.1,
+        }
+    }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct ColliderFFI {
+    pub index: IndexNative,
+    pub entity_id: u64,
+    pub id: u32,
+}
+
+impl ToJObject for ColliderFFI {
+    fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
+        let collider_cls = env.find_class("com/dropbear/physics/Collider")
+            .map_err(|_| DropbearNativeError::JNIClassNotFound)?;
+
+        let index_cls = env.find_class("com/dropbear/physics/Index")
+            .map_err(|_| DropbearNativeError::JNIClassNotFound)?;
+
+        let entity_cls = env.find_class("com/dropbear/EntityId")
+            .map_err(|_| DropbearNativeError::JNIClassNotFound)?;
+
+        let entity_obj = env.new_object(
+            &entity_cls,
+            "(J)V",
+            &[JValue::Long(self.entity_id as i64)]
+        ).map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
+
+        let index_obj = env.new_object(
+            &index_cls,
+            "(II)V",
+            &[
+                JValue::Int(self.index.index as i32),
+                JValue::Int(self.index.generation as i32)
+            ]
+        ).map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
+
+        let collider_obj = env.new_object(
+            collider_cls,
+            "(Lcom/dropbear/physics/Index;Lcom/dropbear/EntityId;I)V",
+            &[
+                JValue::Object(&index_obj),
+                JValue::Object(&entity_obj),
+                JValue::Int(self.id as i32)
+            ]
+        ).map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
+
+        Ok(collider_obj)
+    }
+}
+
+impl FromJObject for ColliderFFI {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self>
+    where
+        Self: Sized
+    {
+        let index_obj = env.get_field(obj, "index", "Lcom/dropbear/physics/Index;")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .l()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let entity_obj = env.get_field(obj, "entity", "Lcom/dropbear/EntityId;")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .l()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let id_val = env.get_field(obj, "id", "I")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .i()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let entity_raw = env.get_field(&entity_obj, "raw", "J")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .j()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let idx_val = env.get_field(&index_obj, "index", "I")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .i()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let gen_val = env.get_field(&index_obj, "generation", "I")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .i()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        Ok(ColliderFFI {
+            index: IndexNative {
+                index: idx_val as u32,
+                generation: gen_val as u32,
+            },
+            entity_id: entity_raw as u64,
+            id: id_val as u32,
+        })
+    }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct IndexNative {
+    pub(crate) index: u32,
+    pub(crate) generation: u32,
+}
+
+impl From<Index> for IndexNative {
+    fn from(value: Index) -> Self {
+        let raw = value.into_raw_parts();
+        Self {
+            index: raw.0,
+            generation: raw.1,
+        }
+    }
+}
+
+impl From<IndexNative> for Index {
+    fn from(value: IndexNative) -> Self {
+        Self::from_raw_parts(value.index, value.generation)
+    }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub enum ColliderShapeType {
+    Box = 0,
+    Sphere = 1,
+    Capsule = 2,
+    Cylinder = 3,
+    Cone = 4,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct ColliderShapeFFI {
+    pub shape_type: ColliderShapeType,
+    pub radius: f32,
+    pub half_height: f32,
+    pub half_extents_x: f32,
+    pub half_extents_y: f32,
+    pub half_extents_z: f32,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RigidBodyContext {
+    pub index: IndexNative,
+    pub entity_id: u64,
+}
+
+impl FromJObject for RigidBodyContext {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self>
+    where
+        Self: Sized
+    {
+        let index_obj = env.get_field(obj, "index", "Lcom/dropbear/physics/Index;")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .l()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let idx_val = env.get_field(&index_obj, "index", "I")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .i()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let gen_val = env.get_field(&index_obj, "generation", "I")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .i()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let entity_obj = env.get_field(obj, "entity", "Lcom/dropbear/EntityId;")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .l()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let entity_raw = env.get_field(&entity_obj, "raw", "J")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .j()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        Ok(RigidBodyContext {
+            index: IndexNative {
+                index: idx_val as u32,
+                generation: gen_val as u32,
+            },
+            entity_id: entity_raw as u64,
+        })
+    }
+}
+
+impl ToJObject for RigidBodyContext {
+    fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
+        let index_cls = env.find_class("com/dropbear/physics/Index")
+            .map_err(|e| {
+                eprintln!("[JNI Error] Class 'com/dropbear/physics/Index' not found: {:?}", e);
+                DropbearNativeError::JNIClassNotFound
+            })?;
+
+        let entity_cls = env.find_class("com/dropbear/EntityId")
+            .map_err(|e| {
+                eprintln!("[JNI Error] Class 'com/dropbear/EntityId' not found: {:?}", e);
+                DropbearNativeError::JNIClassNotFound
+            })?;
+
+        let rb_cls = env.find_class("com/dropbear/physics/RigidBody")
+            .map_err(|e| {
+                eprintln!("[JNI Error] Class 'com/dropbear/physics/RigidBody' not found: {:?}", e);
+                DropbearNativeError::JNIClassNotFound
+            })?;
+
+        let index_obj = env.new_object(
+            &index_cls,
+            "(II)V",
+            &[
+                JValue::Int(self.index.index as i32),
+                JValue::Int(self.index.generation as i32)
+            ]
+        ).map_err(|e| {
+            eprintln!("[JNI Error] Failed to create Index object: {:?}", e);
+            DropbearNativeError::JNIFailedToCreateObject
+        })?;
+
+        let entity_obj = env.new_object(
+            &entity_cls,
+            "(J)V",
+            &[JValue::Long(self.entity_id as i64)]
+        ).map_err(|e| {
+            eprintln!("[JNI Error] Failed to create EntityId object: {:?}", e);
+            DropbearNativeError::JNIFailedToCreateObject
+        })?;
+
+        let rb_obj = env.new_object(
+            rb_cls,
+            "(Lcom/dropbear/physics/Index;Lcom/dropbear/EntityId;)V",
+            &[
+                JValue::Object(&index_obj),
+                JValue::Object(&entity_obj)
+            ]
+        ).map_err(|e| {
+            eprintln!("[JNI Error] Failed to create RigidBody object: {:?}", e);
+            DropbearNativeError::JNIFailedToCreateObject
+        })?;
+
+        Ok(rb_obj)
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/utils.rs b/eucalyptus-core/src/utils.rs
index 54fa539..5744646 100644
--- a/eucalyptus-core/src/utils.rs
+++ b/eucalyptus-core/src/utils.rs
@@ -4,7 +4,6 @@ use crate::states::Node;
 use dropbear_engine::utils::{ResourceReference, ResourceReferenceType, relative_path_from_euca};
 use std::path::{Path, PathBuf};
 use std::time::Duration;
-use serde::{Deserialize, Serialize};
 use winit::keyboard::KeyCode;
 
 pub const PROTO_TEXTURE: &[u8] = include_bytes!("../../resources/textures/proto.png");
@@ -347,7 +346,7 @@ macro_rules! convert_ptr {
 
         if ptr.is_null() {
             let message = format!(
-                "[{}] [ERROR] {} pointer is null",
+                "[{}] {} pointer is null",
                 std::any::type_name::<$target_ty>(),
                 stringify!($ptr)
             );
@@ -545,6 +544,12 @@ macro_rules! ffi_error_return {
             }
         }
 
+        impl<T> ErrorValue for $crate::scripting::result::DropbearNativeResult<T> {
+            fn error_value() -> Self {
+                Err($crate::scripting::native::DropbearNativeError::NullPointer)
+            }
+        }
+
         ErrorValue::error_value()
     }};
 
diff --git a/eucalyptus-editor/README.md b/eucalyptus-editor/README.md
index 5055465..4d2c94f 100644
--- a/eucalyptus-editor/README.md
+++ b/eucalyptus-editor/README.md
@@ -1,3 +1,12 @@
 # eucalyptus-editor
 
-The primary editor used to make games on the dropbear engine.
\ No newline at end of file
+The primary editor used to make games on the dropbear engine. It is scripted in Kotlin and is JVM first-party
+supported. 
+
+## Other functions
+
+To run a local play test, run `eucalyptus-editor.exe play {project_dir}` with optional jvm args specified through
+`--jvm-args {jvm_args}`. 
+
+eucalyptus-editor does not have a scripting interface, therefore can be run without the Java Virtual Machine. If you do
+wish to enter into "Play Mode" or start testing your game, you will require a form of Java Development Kit (JDK).
\ No newline at end of file
diff --git a/eucalyptus-editor/src/editor/component.rs b/eucalyptus-editor/src/editor/component.rs
index e81715a..9b997fd 100644
--- a/eucalyptus-editor/src/editor/component.rs
+++ b/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::{ASSET_REGISTRY, AssetHandle};
+use dropbear_engine::asset::{AssetHandle, ASSET_REGISTRY};
 use dropbear_engine::attenuation::ATTENUATION_PRESETS;
 use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform};
 use dropbear_engine::graphics::NO_TEXTURE;
@@ -9,11 +9,12 @@ use dropbear_engine::lighting::LightType;
 use dropbear_engine::utils::ResourceReference;
 use egui::{CollapsingHeader, ComboBox, DragValue, Grid, RichText, TextEdit, Ui, UiBuilder};
 use eucalyptus_core::camera::CameraType;
-use eucalyptus_core::states::{Camera3D, Light, CustomProperties, Property, Script, Value};
+use eucalyptus_core::states::{Camera3D, Light, Property, Script};
 use eucalyptus_core::{fatal, warn};
 use glam::{DVec3, Vec3};
 use hecs::Entity;
 use std::time::Instant;
+use eucalyptus_core::properties::{CustomProperties, Value};
 
 /// A trait that can added to any component that allows you to inspect the value in the editor.
 pub trait InspectableComponent {
@@ -42,7 +43,7 @@ impl From<Value> for ValueType {
         match value {
             Value::String(_) => ValueType::String,
             Value::Int(_) => ValueType::Int,
-            Value::Float(_) => ValueType::Float,
+            Value::Double(_) => ValueType::Float,
             Value::Bool(_) => ValueType::Bool,
             Value::Vec3(_) => ValueType::Vec3,
         }
@@ -54,7 +55,7 @@ impl From<&mut Value> for ValueType {
         match value {
             Value::String(_) => ValueType::String,
             Value::Int(_) => ValueType::Int,
-            Value::Float(_) => ValueType::Float,
+            Value::Double(_) => ValueType::Float,
             Value::Bool(_) => ValueType::Bool,
             Value::Vec3(_) => ValueType::Vec3,
         }
@@ -119,7 +120,7 @@ impl InspectableComponent for CustomProperties {
                     if selected_type != current_type {
                         property.value = match selected_type {
                             ValueType::String => Value::String(String::new()),
-                            ValueType::Float => Value::Float(0.0),
+                            ValueType::Float => Value::Double(0.0),
                             ValueType::Int => Value::Int(0),
                             ValueType::Bool => Value::Bool(false),
                             ValueType::Vec3 => Value::Vec3([0.0, 0.0, 0.0]),
@@ -146,7 +147,7 @@ impl InspectableComponent for CustomProperties {
                         Value::Int(n) => {
                             ui.add(DragValue::new(n).speed(1.0));
                         }
-                        Value::Float(f) => {
+                        Value::Double(f) => {
                             ui.add(DragValue::new(f).speed(speed));
                         }
                         Value::Bool(b) => {
diff --git a/eucalyptus-editor/src/editor/dock.rs b/eucalyptus-editor/src/editor/dock.rs
index 22f3856..ee9c351 100644
--- a/eucalyptus-editor/src/editor/dock.rs
+++ b/eucalyptus-editor/src/editor/dock.rs
@@ -1,11 +1,11 @@
 use super::*;
 use crate::editor::{
-    ViewportMode,
     console_error::{ConsoleItem, ErrorLevel},
+    ViewportMode,
 };
 use std::{
     cmp::Ordering,
-    collections::{HashMap, hash_map::DefaultHasher},
+    collections::{hash_map::DefaultHasher, HashMap},
     fs,
     hash::{Hash, Hasher},
     io,
@@ -25,7 +25,7 @@ use egui::{self, CollapsingHeader, Margin, RichText};
 use egui_dock::TabViewer;
 use egui_ltreeview::{Action, NodeBuilder, TreeViewBuilder};
 use eucalyptus_core::hierarchy::{Children, Hierarchy, Parent};
-use eucalyptus_core::states::{Label, Light, CustomProperties, PROJECT, Script};
+use eucalyptus_core::states::{Label, Light, Script, PROJECT};
 use eucalyptus_core::traits::registry::ComponentRegistry;
 use hecs::{Entity, EntityBuilder, World};
 use indexmap::Equivalent;
@@ -34,6 +34,7 @@ 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::rigidbody::RigidBody;
+use eucalyptus_core::properties::CustomProperties;
 
 pub struct EditorTabViewer<'a> {
     pub view: egui::TextureId,
@@ -478,9 +479,9 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                                         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;
-                                            }
+                                            // if name.contains("EntityTransform") {
+                                            //     continue;
+                                            // }
                                             let short_name =
                                                 name.split("::").last().unwrap_or(name);
                                             let display_name =
@@ -926,8 +927,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                         && let Some(col) = q.get()
                     {
                         if let Some(col) = col {
-                            let mut collider = Collider::new();
-                            collider.id = col.colliders.len() as u32 + 1;
+                            let collider = Collider::new();
                             col.insert(collider);
                         } else {
                             manual_edit = true;
diff --git a/eucalyptus-editor/src/editor/input.rs b/eucalyptus-editor/src/editor/input.rs
index ede1c42..eb322f8 100644
--- a/eucalyptus-editor/src/editor/input.rs
+++ b/eucalyptus-editor/src/editor/input.rs
@@ -3,7 +3,7 @@ use dropbear_engine::{
     entity::MeshRenderer,
     input::{Controller, Keyboard, Mouse},
 };
-use eucalyptus_core::states::{CustomProperties, Label};
+use eucalyptus_core::states::Label;
 use eucalyptus_core::success_without_console;
 use gilrs::{Button, GamepadId};
 use log;
@@ -11,6 +11,7 @@ use transform_gizmo_egui::{GizmoMode, GizmoOrientation};
 use winit::{
     dpi::PhysicalPosition, event::MouseButton, event_loop::ActiveEventLoop, keyboard::KeyCode,
 };
+use eucalyptus_core::properties::CustomProperties;
 
 impl Keyboard for Editor {
     fn key_down(&mut self, key: KeyCode, _event_loop: &ActiveEventLoop) {
diff --git a/eucalyptus-editor/src/editor/mod.rs b/eucalyptus-editor/src/editor/mod.rs
index 5c75751..ea5082b 100644
--- a/eucalyptus-editor/src/editor/mod.rs
+++ b/eucalyptus-editor/src/editor/mod.rs
@@ -16,23 +16,23 @@ use crossbeam_channel::{unbounded, Receiver, Sender};
 use dropbear_engine::entity::EntityTransform;
 use dropbear_engine::mipmap::MipMapGenerator;
 use dropbear_engine::shader::Shader;
-use dropbear_engine::{camera::Camera, entity::{MeshRenderer, Transform}, future::FutureHandle, graphics::{RenderContext, SharedGraphicsContext}, lighting::LightManager, model::{MODEL_CACHE, ModelId}, scene::SceneCommand, DropbearWindowBuilder, WindowData};
+use dropbear_engine::{camera::Camera, entity::{MeshRenderer, Transform}, future::FutureHandle, graphics::{RenderContext, SharedGraphicsContext}, lighting::LightManager, model::{ModelId, MODEL_CACHE}, scene::SceneCommand, DropbearWindowBuilder, WindowData};
 use egui::{self, Context};
 use egui_dock::{DockArea, DockState, NodeIndex, Style};
 use eucalyptus_core::{register_components, APP_INFO};
 use eucalyptus_core::hierarchy::{Children, SceneHierarchy};
 use eucalyptus_core::scene::{SceneConfig, SceneEntity};
-use eucalyptus_core::states::{CustomProperties, Label, SerializedMeshRenderer};
+use eucalyptus_core::states::{Label, SerializedMeshRenderer};
 use eucalyptus_core::traits::SerializableComponent;
 use eucalyptus_core::traits::registry::ComponentRegistry;
 use eucalyptus_core::{
     camera::{CameraComponent, CameraType, DebugCamera},
     fatal, info,
     input::InputState,
-    scripting::{BuildStatus},
+    scripting::BuildStatus,
     states,
     states::{
-        EditorTab, PROJECT, SCENES, Script, WorldLoadingStatus,
+        EditorTab, Script, WorldLoadingStatus, PROJECT, SCENES,
     },
     success,
     utils::ViewportMode,
@@ -46,7 +46,7 @@ use std::{
     fs,
     path::PathBuf,
     sync::Arc,
-    time::{Instant},
+    time::Instant,
 };
 use std::rc::Rc;
 use tokio::sync::oneshot;
@@ -57,6 +57,7 @@ use winit::{keyboard::KeyCode, window::Window};
 use winit::dpi::PhysicalSize;
 use eucalyptus_core::physics::collider::{ColliderShape, WireframeGeometry};
 use eucalyptus_core::physics::collider::shader::ColliderWireframePipeline;
+use eucalyptus_core::properties::CustomProperties;
 use crate::about::AboutWindow;
 
 pub struct Editor {
diff --git a/eucalyptus-editor/src/editor/scene.rs b/eucalyptus-editor/src/editor/scene.rs
index 71aada3..2a7d00d 100644
--- a/eucalyptus-editor/src/editor/scene.rs
+++ b/eucalyptus-editor/src/editor/scene.rs
@@ -1,8 +1,9 @@
 use crossbeam_channel::unbounded;
+use glam::DMat4;
 use super::*;
 use crate::signal::SignalController;
 use crate::spawn::PendingSpawnController;
-use dropbear_engine::asset::{ASSET_REGISTRY, PointerKind};
+use dropbear_engine::asset::{PointerKind, ASSET_REGISTRY};
 use dropbear_engine::graphics::{InstanceRaw, RenderContext};
 use dropbear_engine::model::MODEL_CACHE;
 use dropbear_engine::{
@@ -13,7 +14,7 @@ use dropbear_engine::{
 };
 use eucalyptus_core::hierarchy::EntityTransformExt;
 use eucalyptus_core::logging;
-use eucalyptus_core::states::{CustomProperties, Label, WorldLoadingStatus};
+use eucalyptus_core::states::{Label, WorldLoadingStatus};
 use log;
 use parking_lot::Mutex;
 use wgpu::Color;
@@ -21,6 +22,7 @@ use wgpu::util::DeviceExt;
 use winit::{event_loop::ActiveEventLoop, keyboard::KeyCode};
 use eucalyptus_core::physics::collider::ColliderGroup;
 use eucalyptus_core::physics::collider::shader::ColliderUniform;
+use eucalyptus_core::properties::CustomProperties;
 
 impl Scene for Editor {
     fn load(&mut self, graphics: &mut RenderContext) {
@@ -472,18 +474,23 @@ impl Scene for Editor {
 
                             for (_entity, (entity_transform, group)) in q.iter() {
                                 for collider in &group.colliders {
-                                    let entity_matrix = entity_transform.sync().matrix().as_mat4();
+                                    let world_tf = entity_transform.sync();
+
+                                    let entity_matrix = DMat4::from_rotation_translation(
+                                        world_tf.rotation,
+                                        world_tf.position
+                                    ).as_mat4();
 
                                     let offset_transform = Transform::new()
                                         .with_offset(collider.translation, collider.rotation);
+
                                     let offset_matrix = offset_transform.matrix().as_mat4();
 
                                     let final_matrix = entity_matrix * offset_matrix;
 
                                     let color = [0.0, 1.0, 0.0, 1.0];
-
                                     let collider_uniform = ColliderUniform::from_matrix(final_matrix, color);
-
+                                    
                                     let collider_buffer = graphics.shared.device.create_buffer_init(
                                         &wgpu::util::BufferInitDescriptor {
                                             label: Some("Collider Uniform Buffer"),
diff --git a/eucalyptus-editor/src/physics/rigidbody.rs b/eucalyptus-editor/src/physics/rigidbody.rs
index 62a4f6f..6204385 100644
--- a/eucalyptus-editor/src/physics/rigidbody.rs
+++ b/eucalyptus-editor/src/physics/rigidbody.rs
@@ -41,8 +41,11 @@ impl InspectableComponent for RigidBody {
                     .range(0.0..=10.0));
             });
 
+
             ui.checkbox(&mut self.can_sleep, "Can Sleep");
 
+            ui.checkbox(&mut self.sleeping, "Initially sleeping?");
+
             ui.checkbox(&mut self.ccd_enabled, "CCD Enabled");
 
             ui.add_space(8.0);
diff --git a/eucalyptus-editor/src/runtime/mod.rs b/eucalyptus-editor/src/runtime/mod.rs
index bf71fb2..5c8edcd 100644
--- a/eucalyptus-editor/src/runtime/mod.rs
+++ b/eucalyptus-editor/src/runtime/mod.rs
@@ -219,8 +219,9 @@ impl PlayMode {
             .script_manager
             .init_script(None, entity_tag_map.clone(), target.clone())
         {
-            log::error!("Failed to initialise scripts: {}", e);
-            return;
+            panic!("Failed to initialise scripts: {}", e);
+        } else {
+            log::debug!("Initialised scripts successfully!");
         }
 
         let world_ptr = self.world.as_mut() as WorldPtr;
@@ -232,12 +233,13 @@ impl PlayMode {
             .script_manager
             .load_script(world_ptr, input_ptr, graphics_ptr, physics_ptr)
         {
-            log::error!("Failed to load scripts: {}", e);
-            return;
+            panic!("Failed to load scripts: {}", e);
+        } else {
+            log::debug!("Loaded scripts successfully!");
         }
 
         self.scripts_ready = true;
-        log::debug!("Scripts initialised successfully");
+        log::debug!("Scripts reloaded successfully!");
     }
 
     /// Requests an asynchronous scene load, returning immediately and loading the scene in the background.
@@ -381,6 +383,7 @@ impl PlayMode {
             if let Some(physics_state) = self.pending_physics_state.take() {
                 self.physics_state = physics_state;
             }
+            self.has_initial_resize_done = false;
             if let Some(new_camera) = self.pending_camera.take() {
                 self.active_camera = Some(new_camera);
             }
diff --git a/eucalyptus-editor/src/runtime/scene.rs b/eucalyptus-editor/src/runtime/scene.rs
index 40b06eb..00bae74 100644
--- a/eucalyptus-editor/src/runtime/scene.rs
+++ b/eucalyptus-editor/src/runtime/scene.rs
@@ -2,7 +2,7 @@ use std::collections::HashMap;
 use egui::{CentralPanel, MenuBar, TopBottomPanel};
 use eucalyptus_core::physics::collider::ColliderGroup;
 use eucalyptus_core::physics::collider::shader::ColliderUniform;
-use glam::{DQuat, DVec3};
+use glam::{DMat4, DQuat, DVec3};
 use hecs::Entity;
 use wgpu::Color;
 use wgpu::util::DeviceExt;
@@ -93,11 +93,11 @@ impl Scene for PlayMode {
                     let new_local_pos = (inv_p_rot * relative_pos) / p_scale;
                     let new_local_rot = inv_p_rot * new_world_rot;
 
-                    let local = transform.local_mut();
+                    let local = transform.world_mut();
                     local.position = new_local_pos;
                     local.rotation = new_local_rot;
                 } else {
-                    let local = transform.local_mut();
+                    let local = transform.world_mut();
                     local.position = new_world_pos;
                     local.rotation = new_world_rot;
                 }
@@ -472,16 +472,21 @@ impl Scene for PlayMode {
 
                 for (_entity, (entity_transform, group)) in q.iter() {
                     for collider in &group.colliders {
-                        let entity_matrix = entity_transform.sync().matrix().as_mat4();
+                        let world_tf = entity_transform.sync();
+
+                        let entity_matrix = DMat4::from_rotation_translation(
+                            world_tf.rotation,
+                            world_tf.position
+                        ).as_mat4();
 
                         let offset_transform = Transform::new()
                             .with_offset(collider.translation, collider.rotation);
+
                         let offset_matrix = offset_transform.matrix().as_mat4();
 
                         let final_matrix = entity_matrix * offset_matrix;
 
                         let color = [0.0, 1.0, 0.0, 1.0];
-
                         let collider_uniform = ColliderUniform::from_matrix(final_matrix, color);
 
                         let collider_buffer = graphics.shared.device.create_buffer_init(
diff --git a/eucalyptus-editor/src/signal.rs b/eucalyptus-editor/src/signal.rs
index 371edcd..49a3875 100644
--- a/eucalyptus-editor/src/signal.rs
+++ b/eucalyptus-editor/src/signal.rs
@@ -7,10 +7,10 @@ use dropbear_engine::utils::{ResourceReference, ResourceReferenceType};
 use egui::Align2;
 use eucalyptus_core::camera::{CameraComponent, CameraType};
 use eucalyptus_core::scene::SceneEntity;
-use eucalyptus_core::scripting::{BuildStatus, build_jvm};
-use eucalyptus_core::spawn::{PendingSpawn, push_pending_spawn};
+use eucalyptus_core::scripting::{build_jvm, BuildStatus};
+use eucalyptus_core::spawn::{push_pending_spawn, PendingSpawn};
 use eucalyptus_core::states::{
-    EditorTab, Label, Light, CustomProperties, PROJECT, Script, SerializedMeshRenderer,
+    EditorTab, Label, Light, Script, SerializedMeshRenderer, PROJECT,
 };
 use eucalyptus_core::traits::SerializableComponent;
 use eucalyptus_core::{fatal, info, success, success_without_console, warn, warn_without_console};
@@ -18,6 +18,7 @@ use std::any::TypeId;
 use std::path::PathBuf;
 use std::sync::Arc;
 use winit::keyboard::KeyCode;
+use eucalyptus_core::properties::CustomProperties;
 
 pub trait SignalController {
     fn run_signal(&mut self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<()>;
diff --git a/eucalyptus-editor/src/spawn.rs b/eucalyptus-editor/src/spawn.rs
index ec3bbd0..89fe6c7 100644
--- a/eucalyptus-editor/src/spawn.rs
+++ b/eucalyptus-editor/src/spawn.rs
@@ -9,14 +9,15 @@ use dropbear_engine::model::Model;
 use dropbear_engine::utils::{ResourceReference, ResourceReferenceType};
 use eucalyptus_core::camera::CameraComponent;
 use eucalyptus_core::scene::SceneEntity;
-pub(crate) use eucalyptus_core::spawn::{PENDING_SPAWNS, PendingSpawnController};
+pub(crate) use eucalyptus_core::spawn::{PendingSpawnController, PENDING_SPAWNS};
 use eucalyptus_core::states::{
-    Light as LightConfig, CustomProperties, Script, SerializedMeshRenderer,
+    Light as LightConfig, Script, SerializedMeshRenderer,
 };
 use eucalyptus_core::utils::ResolveReference;
 use eucalyptus_core::{fatal, success};
 use hecs::EntityBuilder;
 use std::sync::Arc;
+use eucalyptus_core::properties::CustomProperties;
 
 fn component_ref<'a, T: 'static>(entity: &'a SceneEntity) -> Option<&'a T> {
     entity
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 3eb05ee..5c470be 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,5 +1,5 @@
 [versions]
-kotlin = "2.1.0"
+kotlin = "2.3.0"
 kotlinxSerialization = "1.8.0"
 
 [libraries]
diff --git a/headers/components/dropbear_camera.h b/headers/components/dropbear_camera.h
deleted file mode 100644
index ad3da02..0000000
--- a/headers/components/dropbear_camera.h
+++ /dev/null
@@ -1,37 +0,0 @@
-#ifndef DROPBEAR_CAMERA_H
-#define DROPBEAR_CAMERA_H
-
-#include "../dropbear_common.h"
-#include "../dropbear_math.h"
-
-typedef struct {
-    const char* label;
-    int64_t entity_id;
-
-    Vector3D eye;
-    Vector3D target;
-    Vector3D up;
-
-    double aspect;
-    double fov_y;
-    double znear;
-    double zfar;
-
-    double yaw;
-    double pitch;
-    double speed;
-    double sensitivity;
-} NativeCamera;
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-DROPBEAR_NATIVE dropbear_get_camera(const World* world_ptr, const char* label, NativeCamera* out_camera);
-DROPBEAR_NATIVE dropbear_get_attached_camera(const World* world_ptr, HANDLE entity_handle, NativeCamera* out_camera);
-DROPBEAR_NATIVE dropbear_set_camera(const World* world_ptr, NativeCamera camera);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_CAMERA_H
\ No newline at end of file
diff --git a/headers/components/dropbear_collider.h b/headers/components/dropbear_collider.h
deleted file mode 100644
index 232d88e..0000000
--- a/headers/components/dropbear_collider.h
+++ /dev/null
@@ -1,79 +0,0 @@
-#ifndef DROPBEAR_COLLIDER_H
-#define DROPBEAR_COLLIDER_H
-
-#include "../dropbear_common.h"
-#include "../dropbear_math.h"
-
-typedef enum ColliderShapeTag {
-    ColliderShape_Box = 0,
-    ColliderShape_Sphere = 1,
-    ColliderShape_Capsule = 2,
-    ColliderShape_Cylinder = 3,
-    ColliderShape_Cone = 4
-} ColliderShapeTag;
-
-// -------------------------------------------------------------- //
-
-typedef struct ColliderShapeBody_Box {
-    Vector3D half_extents;
-} ColliderShapeBody_Box;
-
-typedef struct ColliderShapeBody_Sphere {
-    float radius;
-} ColliderShapeBody_Sphere;
-
-typedef struct ColliderShapeBody_Capsule {
-    float half_height;
-    float radius;
-} ColliderShapeBody_Capsule;
-
-typedef struct ColliderShapeBody_Cylinder {
-    float half_height;
-    float radius;
-} ColliderShapeBody_Cylinder;
-
-typedef struct ColliderShapeBody_Cone {
-    float half_height;
-    float radius;
-} ColliderShapeBody_Cone;
-
-typedef struct ColliderShape {
-    ColliderShapeTag tag;
-
-    union {
-        ColliderShapeBody_Box box;
-        ColliderShapeBody_Sphere sphere;
-        ColliderShapeBody_Capsule capsule;
-        ColliderShapeBody_Cylinder cylinder;
-        ColliderShapeBody_Cone cone;
-    } data;
-} ColliderShape;
-
-// -------------------------------------------------------------- //
-
-typedef struct {
-    Index index;
-    unsigned int id;
-    HANDLE entity;
-    ColliderShape collider_shape;
-    double density;
-    double friction;
-    double restitution;
-    bool is_sensor;
-    Vector3D translation;
-    Vector3D rotation;
-} Collider;
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-DROPBEAR_NATIVE dropbear_set_collider(World* world_handle, PhysicsEngine* physics_engine, Collider collider);
-
-// free the memory of the colliders array
-DROPBEAR_NATIVE dropbear_free_colliders(Collider* colliders, unsigned int count);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_COLLIDER_H
\ No newline at end of file
diff --git a/headers/components/dropbear_entitytransform.h b/headers/components/dropbear_entitytransform.h
deleted file mode 100644
index 6a56825..0000000
--- a/headers/components/dropbear_entitytransform.h
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef DROPBEAR_ENTITYTRANSFORM_H
-#define DROPBEAR_ENTITYTRANSFORM_H
-
-#include "../dropbear_common.h"
-#include "../dropbear_math.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-DROPBEAR_NATIVE dropbear_get_transform(const World* world_ptr, HANDLE entity_handle, NativeEntityTransform* out_transform);
-DROPBEAR_NATIVE dropbear_propagate_transform(const World* world_ptr, HANDLE entity_id, NativeTransform* out_transform);
-DROPBEAR_NATIVE dropbear_set_transform(const World* world_ptr, HANDLE entity_id, NativeEntityTransform transform);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_ENTITYTRANSFORM_H
\ No newline at end of file
diff --git a/headers/components/dropbear_hierarchy.h b/headers/components/dropbear_hierarchy.h
deleted file mode 100644
index af5dc32..0000000
--- a/headers/components/dropbear_hierarchy.h
+++ /dev/null
@@ -1,17 +0,0 @@
-#ifndef DROPBEAR_HIERARCHY_H
-#define DROPBEAR_HIERARCHY_H
-
-#include "../dropbear_common.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-DROPBEAR_NATIVE dropbear_get_children(const World* world_ptr, HANDLE entity_id, HANDLE** out_children, size_t* out_count);
-DROPBEAR_NATIVE dropbear_get_child_by_label(const World* world_ptr, HANDLE entity_id, const char* label, HANDLE* out_child);
-DROPBEAR_NATIVE dropbear_get_parent(const World* world_ptr, HANDLE entity_id, HANDLE* out_parent);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_HIERARCHY_H
\ No newline at end of file
diff --git a/headers/components/dropbear_label.h b/headers/components/dropbear_label.h
deleted file mode 100644
index fe09db5..0000000
--- a/headers/components/dropbear_label.h
+++ /dev/null
@@ -1,15 +0,0 @@
-#ifndef DROPBEAR_LABEL_H
-#define DROPBEAR_LABEL_H
-
-#include "../dropbear_common.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-DROPBEAR_NATIVE dropbear_get_entity_name(const World* world_ptr, HANDLE entity_id, char* out_name, size_t max_len);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_LABEL_H
\ No newline at end of file
diff --git a/headers/components/dropbear_meshrenderer.h b/headers/components/dropbear_meshrenderer.h
deleted file mode 100644
index 9ebb2cf..0000000
--- a/headers/components/dropbear_meshrenderer.h
+++ /dev/null
@@ -1,27 +0,0 @@
-#ifndef DROPBEAR_MESHRENDERER_H
-#define DROPBEAR_MESHRENDERER_H
-
-#include "../dropbear_common.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-// model
-DROPBEAR_NATIVE dropbear_get_model(const World* world_ptr, const AssetRegistry* asset_ptr, HANDLE entity_handle, HANDLE* out_model_id);
-DROPBEAR_NATIVE dropbear_set_model(const World* world_ptr, const AssetRegistry* asset_ptr, HANDLE entity_handle, HANDLE model_id);
-DROPBEAR_NATIVE dropbear_is_model_handle(const AssetRegistry* asset_ptr, HANDLE handle, BOOL* out_is_model);
-DROPBEAR_NATIVE dropbear_is_using_model(const World* world_ptr, HANDLE entity_handle, HANDLE model_handle, BOOL* out_is_using);
-
-// textures
-DROPBEAR_NATIVE dropbear_get_texture(const World* world_ptr, const AssetRegistry* asset_ptr, HANDLE entity_handle, const char* name, HANDLE* out_texture_id);
-DROPBEAR_NATIVE dropbear_get_texture_name(const AssetRegistry* asset_ptr, HANDLE texture_handle, const char** out_name);
-DROPBEAR_NATIVE dropbear_set_texture(const World* world_ptr, const AssetRegistry* asset_ptr, HANDLE entity_handle, const char* old_material_name, HANDLE texture_id);
-DROPBEAR_NATIVE dropbear_is_texture_handle(const AssetRegistry* asset_ptr, HANDLE handle, BOOL* out_is_texture);
-DROPBEAR_NATIVE dropbear_is_using_texture(const World* world_ptr, HANDLE entity_handle, HANDLE texture_handle, BOOL* out_is_using);
-DROPBEAR_NATIVE dropbear_get_all_textures(const World* world_ptr, const AssetRegistry* asset_ptr, HANDLE entity_handle, const char*** out_textures, size_t* out_count);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_MESHRENDERER_H
\ No newline at end of file
diff --git a/headers/components/dropbear_properties.h b/headers/components/dropbear_properties.h
deleted file mode 100644
index dc684aa..0000000
--- a/headers/components/dropbear_properties.h
+++ /dev/null
@@ -1,30 +0,0 @@
-#ifndef DROPBEAR_PROPERTIES_H
-#define DROPBEAR_PROPERTIES_H
-
-#include "../dropbear_common.h"
-#include "../dropbear_math.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-// getters
-DROPBEAR_NATIVE dropbear_get_string_property(const World* world_ptr, HANDLE entity_handle, const char* label, const char** out_value);
-DROPBEAR_NATIVE dropbear_get_int_property(const World* world_ptr, HANDLE entity_handle, const char* label, int32_t* out_value);
-DROPBEAR_NATIVE dropbear_get_long_property(const World* world_ptr, HANDLE entity_handle, const char* label, int64_t* out_value);
-DROPBEAR_NATIVE dropbear_get_float_property(const World* world_ptr, HANDLE entity_handle, const char* label, double* out_value);
-DROPBEAR_NATIVE dropbear_get_bool_property(const World* world_ptr, HANDLE entity_handle, const char* label, BOOL* out_value);
-DROPBEAR_NATIVE dropbear_get_vec3_property(const World* world_ptr, HANDLE entity_handle, const char* label, Vector3D* out_value);
-
-// setters
-DROPBEAR_NATIVE dropbear_set_string_property(const World* world_ptr, HANDLE entity_handle, const char* label, const char* value);
-DROPBEAR_NATIVE dropbear_set_int_property(const World* world_ptr, HANDLE entity_handle, const char* label, int32_t value);
-DROPBEAR_NATIVE dropbear_set_long_property(const World* world_ptr, HANDLE entity_handle, const char* label, int64_t value);
-DROPBEAR_NATIVE dropbear_set_float_property(const World* world_ptr, HANDLE entity_handle, const char* label, double value);
-DROPBEAR_NATIVE dropbear_set_bool_property(const World* world_ptr, HANDLE entity_handle, const char* label, BOOL value);
-DROPBEAR_NATIVE dropbear_set_vec3_property(const World* world_ptr, HANDLE entity_handle, const char* label, Vector3D value);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_PROPERTIES_H
\ No newline at end of file
diff --git a/headers/components/dropbear_rigidbody.h b/headers/components/dropbear_rigidbody.h
deleted file mode 100644
index 4554344..0000000
--- a/headers/components/dropbear_rigidbody.h
+++ /dev/null
@@ -1,43 +0,0 @@
-#ifndef DROPBEAR_RIGIDBODY_H
-#define DROPBEAR_RIGIDBODY_H
-
-#include "../dropbear_common.h"
-#include "../dropbear_math.h"
-#include "dropbear_collider.h"
-
-typedef enum {
-    RIGIDBODY_MODE_DYNAMIC,
-    RIGIDBODY_MODE_FIXED,
-    RIGIDBODY_MODE_KINEMATIC_POSITION,
-    RIGIDBODY_MODE_KINEMATIC_VELOCITY,
-} RigidBodyMode;
-
-typedef struct {
-    Index index;
-    HANDLE entity;
-    RigidBodyMode mode;
-    double gravity_scale;
-    bool can_sleep;
-    bool ccd_enabled;
-    Vector3D linear_velocity;
-    Vector3D angualar_velocity;
-    double linear_damping;
-    double angular_damping;
-    AxisLock lock_translation;
-    AxisLock lock_rotation;
-} RigidBody;
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-DROPBEAR_NATIVE dropbear_apply_impulse(PhysicsEngine* physics_engine, Index index, Vector3D impulse);
-DROPBEAR_NATIVE dropbear_apply_torque_impulse(PhysicsEngine* physics_engine, Index index, Vector3D torque_impulse);
-
-DROPBEAR_NATIVE dropbear_set_rigidbody(World* world_handle, PhysicsEngine* physics_engine, RigidBody rigidbody);
-DROPBEAR_NATIVE dropbear_get_child_colliders(World* world_handle, PhysicsEngine* physics_engine, Index parent_index, Collider** out_colliders, unsigned int* out_count);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_RIGIDBODY_H
\ No newline at end of file
diff --git a/headers/dropbear.h b/headers/dropbear.h
deleted file mode 100644
index 47c4a38..0000000
--- a/headers/dropbear.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * dropbear-engine native header definitions. Created by tirbofish as part of the dropbear project.
- *
- * Primarily used for Kotlin/Native, however nothing is stopping you from implementing it to your own language.
- * Exports are located at `eucalyptus_core::scripting::native::exports`.
- *
- * Note: This does not include JNI definitions, only native exports from the eucalyptus-core dynamic library.
- *       For JNI definitions, take a look at `eucalyptus_core::scripting::jni::exports` or even better, take a
- *       look at the JNINative class for all JNI functions that exist.
- *
- * Warning: This header file is not always up to date with the existing JNI functions (some funcs may not be implemented),
- *          So please open a issue if there is something missing, or help us by creating a PR implementing them.
- *
- * Licensed under MIT or Apache 2.0 depending on your mood.
- */
-
-#ifndef DROPBEAR_H
-#define DROPBEAR_H
-
-#include "dropbear_common.h"
-#include "dropbear_math.h"
-#include "dropbear_utils.h"
-
-#include "dropbear_input.h"
-#include "dropbear_scene.h"
-#include "dropbear_engine.h"
-#include "dropbear_physics.h"
-
-#include "components/dropbear_camera.h"
-#include "components/dropbear_entitytransform.h"
-#include "components/dropbear_hierarchy.h"
-#include "components/dropbear_label.h"
-#include "components/dropbear_meshrenderer.h"
-#include "components/dropbear_properties.h"
-#include "components/dropbear_collider.h"
-#include "components/dropbear_rigidbody.h"
-
-#endif // DROPBEAR_H
\ No newline at end of file
diff --git a/headers/dropbear_common.h b/headers/dropbear_common.h
deleted file mode 100644
index c107e89..0000000
--- a/headers/dropbear_common.h
+++ /dev/null
@@ -1,56 +0,0 @@
-#ifndef DROPBEAR_COMMON_H
-#define DROPBEAR_COMMON_H
-
-#include <stddef.h>
-#include <stdint.h>
-#include <stdbool.h>
-
-/**
-* @brief Return function for a DropbearNativeError. 0 on success, otherwise look
-*        at `eucalyptus_core::scripting::native::DropbearNativeError`
-*/
-#define DROPBEAR_NATIVE int
-
-/**
-* @brief The handle/id of an object, as a long.
-*
-* Kotlin/Native requires
-* this to have an int64_t as a Long (or use a long long).
-*/
-#define HANDLE int64_t
-
-/**
-* @brief A helper type that defines a value that can either be a 0 or 1
-*/
-#define BOOL int
-
-typedef struct World World; // opaque pointer
-typedef struct InputState InputState; // opaque pointer
-typedef struct CommandBuffer CommandBuffer; // opaque pointer
-typedef struct AssetRegistry AssetRegistry; // opaque pointer
-typedef struct SceneLoader SceneLoader; // opaque pointer
-typedef struct PhysicsEngine PhysicsEngine; // opaque pointer
-
-/// Describes all the different pointers that can be passed into a scripting
-/// module.
-typedef struct {
-    World* world;
-    InputState* input;
-    CommandBuffer* graphics;
-    AssetRegistry* assets;
-    SceneLoader* scene_loader;
-    PhysicsEngine* physics_engine;
-} DropbearContext;
-
-typedef struct {
-    unsigned int index;
-    unsigned int generation;
-} Index;
-
-typedef struct {
-    bool x;
-    bool y;
-    bool z;
-} AxisLock;
-
-#endif // DROPBEAR_COMMON_H
\ No newline at end of file
diff --git a/headers/dropbear_engine.h b/headers/dropbear_engine.h
deleted file mode 100644
index c1b8563..0000000
--- a/headers/dropbear_engine.h
+++ /dev/null
@@ -1,43 +0,0 @@
-#ifndef DROPBEAR_ENGINE_H
-#define DROPBEAR_ENGINE_H
-
-#include "dropbear_common.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-/**
- * @brief Fetches an entity from the world/current scene by its label.
- *
- * Returns the entity's id.
- */
-DROPBEAR_NATIVE dropbear_get_entity(const char* label, const World* world_ptr, int64_t* out_entity);
-
-/**
- * @brief Fetches an asset from the asset registry as by its name.
- *
- * Returns the asset's handle.
- */
-DROPBEAR_NATIVE dropbear_get_asset(const AssetRegistry* asset_ptr, const char* label, HANDLE* out_asset_id);
-
-/**
- * @brief Quits the currently running app or game.
- *
- * Does not return anything. If any issues occur, the program will terminate.
- *
- * @note Behaviours:
- * - eucalyptus-editor: When called, this exits your Play Mode session and brings
- *                      you back to EditorState::Editing
- * - redback-runtime: When called, this will exit your current process and kill
- *                    the app as is. It will also drop any pointers and do any
- *                    additional clean-up.
- *
- * @return void
- */
-void dropbear_quit(const CommandBuffer* command_ptr);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_ENGINE_H
\ No newline at end of file
diff --git a/headers/dropbear_input.h b/headers/dropbear_input.h
deleted file mode 100644
index 09633cf..0000000
--- a/headers/dropbear_input.h
+++ /dev/null
@@ -1,87 +0,0 @@
-#ifndef DROPBEAR_INPUT_H
-#define DROPBEAR_INPUT_H
-
-#include "dropbear_common.h"
-#include "dropbear_math.h"
-
-/**
- * @brief A struct that represents an external input device in the shape of a controller.
- */
-typedef struct {
-    int id;
-    Vector2D left_stick_pos;
-    Vector2D right_stick_pos;
-} Gamepad;
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-/**
- * @brief Prints the input state to the console. Does not return anything, failure does not do anything.
- *
- * Can be useful for debugging.
- */
-DROPBEAR_NATIVE dropbear_print_input_state(const InputState* input_ptr);
-
-/**
- * @brief Checks if a key is currently pressed. If pressed, returns 1, otherwise 0.
- */
-DROPBEAR_NATIVE dropbear_is_key_pressed(const InputState* input_ptr, int32_t key_ordinal, BOOL* out_pressed);
-
-/**
- * @brief Fetches the current mouse position for that frame.
- */
-DROPBEAR_NATIVE dropbear_get_mouse_position(const InputState* input_ptr, float* out_x, float* out_y);
-
-/**
- * @brief Checks if a mouse button is currently pressed. If pressed, returns 1, otherwise 0.
- */
-DROPBEAR_NATIVE dropbear_is_mouse_button_pressed(const InputState* input_ptr, int32_t button_ordinal, BOOL* out_pressed);
-
-/**
- * @brief Fetches the delta of the mouse position since the last frame.
- */
-DROPBEAR_NATIVE dropbear_get_mouse_delta(const InputState* input_ptr, float* out_dx, float* out_dy);
-
-/**
- * @brief Checks if the cursor is currently locked. If locked, returns 1, otherwise 0.
- */
-DROPBEAR_NATIVE dropbear_is_cursor_locked(const InputState* input_ptr, BOOL* out_locked);
-
-/**
- * @brief Sets the mouse cursor to be locked or unlocked.
- */
-DROPBEAR_NATIVE dropbear_set_cursor_locked(InputState* input_ptr, CommandBuffer* graphics_ptr, BOOL locked);
-
-/**
- * @brief Fetches the mouse position of the previous frame.
- *
- * Can be used to calculate the delta of the mouse position.
- */
-DROPBEAR_NATIVE dropbear_get_last_mouse_pos(const InputState* input_ptr, float* out_x, float* out_y);
-
-/**
- * @brief Checks if the cursor is currently hidden. If hidden, returns 1, otherwise 0.
- */
-DROPBEAR_NATIVE dropbear_is_cursor_hidden(const InputState* input_ptr, BOOL* out_hidden);
-
-/**
- * @brief Sets the cursor to either hidden (invisible) or visible
- */
-DROPBEAR_NATIVE dropbear_set_cursor_hidden(InputState* input_ptr, CommandBuffer* graphics_ptr, BOOL hidden);
-
-/**
- * @brief Fetches all available connected gamepads in the input state.
- */
-DROPBEAR_NATIVE dropbear_get_connected_gamepads(InputState* input_ptr, const Gamepad** out_gamepads, int32_t* out_count);
-
-/**
- * @brief Checks if a button has been pressed on a specific gamepad.
- */
-DROPBEAR_NATIVE dropbear_is_gamepad_button_pressed(const InputState* input_ptr, HANDLE gamepad_id, int ordinal, BOOL* out_pressed);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_INPUT_H
\ No newline at end of file
diff --git a/headers/dropbear_math.h b/headers/dropbear_math.h
deleted file mode 100644
index d4cf5aa..0000000
--- a/headers/dropbear_math.h
+++ /dev/null
@@ -1,54 +0,0 @@
-#ifndef DROPBEAR_MATH_H
-#define DROPBEAR_MATH_H
-
-#include "dropbear_common.h"
-
-/**
- * @brief Represents a `glam::DVec2` in a C struct form.
- */
-typedef struct {
-    double x;
-    double y;
-} Vector2D;
-
-/**
- * @brief Represents a `glam::DVec3` in a C struct form.
- */
-typedef struct {
-    double x;
-    double y;
-    double z;
-} Vector3D;
-
-/**
- * @brief Represents a `dropbear_engine::entity::Transform` in a C struct form.
- */
-typedef struct {
-    double position_x;
-    double position_y;
-    double position_z;
-    double rotation_x;
-    double rotation_y;
-    double rotation_z;
-    double rotation_w;
-    double scale_x;
-    double scale_y;
-    double scale_z;
-} NativeTransform;
-
-/**
- * @brief Represents an `dropbear_engine::entity::EntityTransform` in a C struct form.
- */
-typedef struct {
-    NativeTransform local;
-    NativeTransform world;
-} NativeEntityTransform;
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_MATH_H
\ No newline at end of file
diff --git a/headers/dropbear_physics.h b/headers/dropbear_physics.h
deleted file mode 100644
index 6f9b9ad..0000000
--- a/headers/dropbear_physics.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#ifndef DROPBEAR_PHYSICS_H
-#define DROPBEAR_PHYSICS_H
-
-#include "dropbear_common.h"
-#include "dropbear_math.h"
-#include "components/dropbear_rigidbody.h"
-#include "components/dropbear_collider.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-DROPBEAR_NATIVE dropbear_set_physics_enabled(World* world_handle, PhysicsEngine* physics_handle, HANDLE entity_handle, bool enabled);
-DROPBEAR_NATIVE dropbear_is_physics_enabled(World* world_handle, PhysicsEngine* physics_handle, HANDLE entity_handle, bool* out_enabled);
-DROPBEAR_NATIVE dropbear_get_rigidbody(World* world_handle, PhysicsEngine* physics_handle, HANDLE entity_handle, RigidBody* out_rigidbody);
-DROPBEAR_NATIVE dropbear_get_all_colliders(World* world_handle, PhysicsEngine* physics_handle, HANDLE entity_handle, Collider** out_colliders, unsigned int* out_count);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_PHYSICS_H
\ No newline at end of file
diff --git a/headers/dropbear_scene.h b/headers/dropbear_scene.h
deleted file mode 100644
index e107247..0000000
--- a/headers/dropbear_scene.h
+++ /dev/null
@@ -1,82 +0,0 @@
-#ifndef DROPBEAR_SCENE_H
-#define DROPBEAR_SCENE_H
-
-#include "dropbear_common.h"
-#include "dropbear_utils.h"
-
-/**
- * @brief The sister to `eucalyptus_core::scene::loading::SceneLoadResult`, which provides C-compatible enum values.
- */
-typedef enum {
-    SCENE_LOAD_PENDING,
-    SCENE_LOAD_SUCCESS,
-    SCENE_LOAD_ERROR
-} SceneLoadResult;
-
-/**
- * @brief The sister handle to `eucalyptus_core::scene::loading::SceneLoadHandle`, which provides C-compatible values.
- */
-typedef struct {
-    HANDLE id;
-    const char* name;
-} SceneLoadHandle;
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-/**
- * @brief Loads a scene asynchronously.
- *
- * Returns a handle (in the form of an integer)
- * to the scene load operation.
- */
-DROPBEAR_NATIVE dropbear_load_scene_async_1(const CommandBuffer* command_ptr, const SceneLoader* scene_loader_ptr, const char* name, SceneLoadHandle* sceneLoadHandle);
-
-/**
- * @brief Loads a scene asynchronously. Allows you to include a loading_scene_name,
- * which will be displayed while the scene is loading.
- *
- * Returns a handle (in the form of an integer)
- * to the scene load operation.
- */
-DROPBEAR_NATIVE dropbear_load_scene_async_2(const CommandBuffer* command_ptr, const SceneLoader* scene_loader_ptr, const char* name, const char* loadingScene, SceneLoadHandle* sceneLoadHandle);
-
-/**
- * @brief Switches to a scene asynchronously.
- *
- * This must be run after you initialise the scene loading (using `dropbear_load_scene_async_1`
- * or `dropbear_load_scene_async_2`). If this function is called before you have checked the progress
- * (with the `dropbear_get_scene_load_status` function), it will return `-10` or `DropbearNativeError::PrematureSceneSwitch`.
- */
-DROPBEAR_NATIVE dropbear_switch_to_scene_async(const CommandBuffer* command_ptr, SceneLoadHandle handle);
-
-/**
- * @brief Switches to a scene immediately.
- *
- *
- *
- * This will block your main thread and freeze the window. It will be extremely inconvenient for
- * your players, and is recommended to use `dropbear_load_scene_async_1` or
- * `dropbear_load_scene_async_2`.
- */
-DROPBEAR_NATIVE dropbear_switch_to_scene_immediate(const CommandBuffer* command_ptr, const char* name);
-
-/**
- * @brief Gets the progress of a scene load operation.
- *
- * Returns a `Progress` and a `DropbearNativeReturn`
- */
-DROPBEAR_NATIVE dropbear_get_scene_load_progress(const SceneLoader* scene_loader_ptr, SceneLoadHandle handle, Progress* progress);
-
-/**
- * @brief Gets the status of a scene load operation
- *
- * Returns a `SceneLoadResult` and a `DropbearNativeReturn`
- */
-DROPBEAR_NATIVE dropbear_get_scene_load_status(const SceneLoader* scene_loader_ptr, SceneLoadHandle handle, SceneLoadResult* result);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_SCENE_H
\ No newline at end of file
diff --git a/headers/dropbear_utils.h b/headers/dropbear_utils.h
deleted file mode 100644
index 6ab2f94..0000000
--- a/headers/dropbear_utils.h
+++ /dev/null
@@ -1,23 +0,0 @@
-#ifndef DROPBEAR_UTILS_H
-#define DROPBEAR_UTILS_H
-
-#include "dropbear_common.h"
-
-/**
- * @brief The sister to [`eucalyptus_core::utils::Progress`], which provides C-compatible values.
- */
-typedef struct {
-    double current;
-    double total;
-    char* message;
-} Progress;
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_UTILS_H
\ No newline at end of file
diff --git a/include/dropbear.h b/include/dropbear.h
new file mode 100644
index 0000000..ff9c9aa
--- /dev/null
+++ b/include/dropbear.h
@@ -0,0 +1,19 @@
+#pragma once
+
+typedef struct World World; // opaque pointer
+typedef struct InputState InputState; // opaque pointer
+typedef struct CommandBuffer CommandBuffer; // opaque pointer
+typedef struct AssetRegistry AssetRegistry; // opaque pointer
+typedef struct SceneLoader SceneLoader; // opaque pointer
+typedef struct PhysicsEngine PhysicsEngine; // opaque pointer
+
+/// Describes all the different pointers that can be passed into a scripting
+/// module.
+typedef struct {
+    World* world;
+    InputState* input;
+    CommandBuffer* graphics;
+    AssetRegistry* assets;
+    SceneLoader* scene_loader;
+    PhysicsEngine* physics_engine;
+} DropbearContext;
diff --git a/magna-carta/src/generator/jvm.rs b/magna-carta/src/generator/jvm.rs
index 1c1fd79..bc87cf9 100644
--- a/magna-carta/src/generator/jvm.rs
+++ b/magna-carta/src/generator/jvm.rs
@@ -52,7 +52,7 @@ impl Generator for KotlinJVMGenerator {
         writeln!(output, "object RunnableRegistry {{")?;
         writeln!(
             output,
-            "    private val tagRegistry = mutableMapOf<String, MutableList<() -> com.dropbear.System>>()"
+            "    private val tagRegistry = mutableMapOf<String, MutableList<() -> com.dropbear.ecs.System>>()"
         )?;
         writeln!(output)?;
 
@@ -84,7 +84,7 @@ impl Generator for KotlinJVMGenerator {
 
         writeln!(
             output,
-            "    fun getScriptFactories(tag: String): List<() -> com.dropbear.System> {{"
+            "    fun getScriptFactories(tag: String): List<() -> com.dropbear.ecs.System> {{"
         )?;
         writeln!(
             output,
@@ -95,7 +95,7 @@ impl Generator for KotlinJVMGenerator {
 
         writeln!(
             output,
-            "    fun instantiateScripts(tag: String): List<com.dropbear.System> {{"
+            "    fun instantiateScripts(tag: String): List<com.dropbear.ecs.System> {{"
         )?;
         writeln!(
             output,
diff --git a/magna-carta/src/generator/native.rs b/magna-carta/src/generator/native.rs
index 67f44e4..54d0203 100644
--- a/magna-carta/src/generator/native.rs
+++ b/magna-carta/src/generator/native.rs
@@ -28,7 +28,7 @@ impl Generator for KotlinNativeGenerator {
         writeln!(
             output,
             r#"import com.dropbear.DropbearEngine
-import com.dropbear.System
+import com.dropbear.ecs.System
 import com.dropbear.ffi.NativeEngine
 import com.dropbear.ffi.generated.DropbearContext
 import com.dropbear.logging.Logger
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 5a41f17..d353976 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -6,14 +6,14 @@ pluginManagement {
         gradlePluginPortal()
     }
 }
+
 plugins {
     id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
 }
-include("magna-carta-plugin")
+//include("magna-carta-plugin")
 
 buildCache {
     local {
         isEnabled = true
     }
-}
-include("dropbear-gradle-plugin")
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/README.md b/src/README.md
index e69de29..17179a8 100644
--- a/src/README.md
+++ b/src/README.md
@@ -0,0 +1,4 @@
+# dropbear
+
+This is the scripting module API interface used for adding logic to entities and scenes on eucalyptus-editor based
+games.
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/Camera.kt b/src/commonMain/kotlin/com/dropbear/Camera.kt
deleted file mode 100644
index 76b1120..0000000
--- a/src/commonMain/kotlin/com/dropbear/Camera.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-package com.dropbear
-
-import com.dropbear.math.Vector3D
-
-/**
- * Describes a 3D camera. 
- */
-class Camera(
-    val label: String,
-    val id: EntityId, // it could be attached to nothing or an AdoptedEntity
-    var eye: Vector3D = Vector3D.zero(),
-    var target: Vector3D = Vector3D.zero(),
-    var up: Vector3D = Vector3D.zero(),
-    val aspect: Double = 0.0,
-    var fov_y: Double = 0.0,
-    var znear: Double = 0.0,
-    var zfar: Double = 0.0,
-    var yaw: Double = 0.0,
-    var pitch: Double = 0.0,
-    var speed: Double = 0.0,
-    var sensitivity: Double = 0.0
-) {
-    internal lateinit var engine: DropbearEngine
-
-    /**
-     * Pushes the camera values to the world to be updated.
-     */
-    fun setCamera() {
-        engine.native.setCamera(this)
-    }
-
-    override fun toString(): String {
-        return "Camera '${label}' of id $id \n" +
-                "eye: $eye\n" +
-                "target: $target\n" +
-                "up: $up\n " +
-                "aspect: $aspect \n" +
-                "fov_y: $fov_y \n" +
-                "znear: $znear \n" +
-                "zfar: $zfar \n" +
-                "yaw: $yaw \n" +
-                "pitch: $pitch" +
-                "speed: $speed" +
-                "sensitivity: $sensitivity"
-    }
-}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/DropbearEngine.kt b/src/commonMain/kotlin/com/dropbear/DropbearEngine.kt
index 12ba71a..141bab0 100644
--- a/src/commonMain/kotlin/com/dropbear/DropbearEngine.kt
+++ b/src/commonMain/kotlin/com/dropbear/DropbearEngine.kt
@@ -1,11 +1,11 @@
 package com.dropbear
 
 import com.dropbear.asset.AssetHandle
+import com.dropbear.components.Camera
 import com.dropbear.ffi.NativeEngine
 import com.dropbear.input.InputState
 import com.dropbear.logging.Logger
 import com.dropbear.scene.SceneManager
-import com.dropbear.ui.UIManager
 
 internal var exceptionOnError: Boolean = false
 var lastErrorMessage: String? = null
@@ -17,11 +17,16 @@ var lastErrorMessage: String? = null
  * which contains a [NativeEngine] (contains native functions). 
  */
 class DropbearEngine(val native: NativeEngine) {
-    private var inputState: InputState? = null
-    private var sceneManager: SceneManager? = null
-    private var uiManager: UIManager? = null
+    val inputState: InputState = InputState()
+    val sceneManager: SceneManager = SceneManager()
+
+    init {
+        Companion.native = native
+    }
 
     companion object {
+        lateinit var native: NativeEngine
+
         fun getLastErrMsg(): String? {
             return lastErrorMessage
         }
@@ -40,57 +45,12 @@ class DropbearEngine(val native: NativeEngine) {
      * Fetches an [EntityRef] with the given label.
      */
     fun getEntity(label: String): EntityRef? {
-        val entityId = native.getEntity(label)
+        val entityId = com.dropbear.getEntity(label)
         val entityRef = if (entityId != null) EntityRef(EntityId(entityId)) else null
-        entityRef?.engine = this
         return entityRef
     }
 
     /**
-     * Fetches the information of the camera with the given label.
-     */
-    fun getCamera(label: String): Camera? {
-        val result = native.getCamera(label)
-        if (result != null) {
-            result.engine = this
-        }
-        return result
-    }
-
-    /**
-     * Gets the current [InputState] for that frame.
-     */
-    fun getInputState(): InputState {
-        if (this.inputState == null) {
-            Logger.trace("InputState not initialised, creating new one")
-            this.inputState = InputState(native)
-        }
-        return this.inputState!!
-    }
-
-    /**
-     * Gets the current [SceneManager] for that frame.
-     */
-    fun getSceneManager(): SceneManager {
-        if (this.sceneManager == null) {
-            Logger.trace("SceneManager not initialised, creating new one")
-            this.sceneManager = SceneManager(native)
-        }
-        return this.sceneManager!!
-    }
-
-    /**
-     * Gets the current [UIManager] for that frame.
-     */
-    fun getUIManager(): UIManager {
-        if (this.uiManager == null) {
-            Logger.trace("UiManager not initialised, creating new one")
-            this.uiManager = UIManager(native)
-        }
-        return this.uiManager!!
-    }
-
-    /**
      * Fetches the asset information from the internal AssetRegistry (located in
      * `dropbear_engine::asset::AssetRegistry`).
      *
@@ -98,7 +58,7 @@ class DropbearEngine(val native: NativeEngine) {
      * The eucalyptus asset URI (or `euca://`) is case-sensitive.
      */
     fun getAsset(eucaURI: String): AssetHandle? {
-        val id = native.getAsset(eucaURI)
+        val id = com.dropbear.getAsset(eucaURI)
         return if (id != null) AssetHandle(id) else null
     }
 
@@ -110,7 +70,7 @@ class DropbearEngine(val native: NativeEngine) {
     fun callExceptionOnError(toggle: Boolean) = DropbearEngine.callExceptionOnError(toggle)
 
     /**
-     * Quits the currently running app or game.
+     * Quits the currently running app or game elegantly.
      * 
      * This function can have different behaviours depending on where it is ran. 
      * - eucalyptus-editor - When called, this exits your Play Mode session and returns you back to
@@ -119,6 +79,10 @@ class DropbearEngine(val native: NativeEngine) {
      *                     also drop any pointers and do any additional cleanup.
      */
     fun quit() {
-        native.quit()
+        com.dropbear.quit()
     }
-}
\ No newline at end of file
+}
+
+expect fun getEntity(label: String): Long?
+expect fun getAsset(eucaURI: String): Long?
+expect fun quit()
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/EntityId.kt b/src/commonMain/kotlin/com/dropbear/EntityId.kt
index b3c4d72..9d00d24 100644
--- a/src/commonMain/kotlin/com/dropbear/EntityId.kt
+++ b/src/commonMain/kotlin/com/dropbear/EntityId.kt
@@ -3,4 +3,4 @@ package com.dropbear
 /**
  * The ID of an entity (represented as a [Long])
  */
-data class EntityId(val id: Long)
\ No newline at end of file
+data class EntityId(val raw: Long)
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/EntityRef.kt b/src/commonMain/kotlin/com/dropbear/EntityRef.kt
index af2fde8..0d6e7b7 100644
--- a/src/commonMain/kotlin/com/dropbear/EntityRef.kt
+++ b/src/commonMain/kotlin/com/dropbear/EntityRef.kt
@@ -1,12 +1,7 @@
 package com.dropbear
 
-import com.dropbear.asset.ModelHandle
-import com.dropbear.asset.TextureHandle
-import com.dropbear.math.Transform
-import com.dropbear.math.Vector3
-import com.dropbear.math.Vector3D
-import com.dropbear.physics.Collider
-import com.dropbear.physics.RigidBody
+import com.dropbear.ecs.Component
+import com.dropbear.ecs.ComponentType
 
 /**
  * A reference to an ECS Entity stored inside the dropbear engine.
@@ -21,172 +16,21 @@ import com.dropbear.physics.RigidBody
  *              playthroughs, so it is recommended not to store this value.
  */
 class EntityRef(val id: EntityId = EntityId(0L)) {
-    lateinit var engine: DropbearEngine
-
-    override fun toString(): String {
-        return "EntityRef(id=$id)"
-    }
-
-    /**
-     * Fetches the [EntityTransform] component for the entity.
-     */
-    fun getTransform(): EntityTransform? {
-        return engine.native.getTransform(id)
-    }
-
-    /**
-     * Walks up the world hierarchy to find the transform of the parent, then multiply 
-     * to create a propagated [Transform].
-     * 
-     * This will update the entity's world transform based on its parent's world transform.
-     */
-    fun propagate(): Transform? {
-        return engine.native.propagateTransform(id)
-    }
-
-    /**
-     * Sets and replaces the [EntityTransform] component for the entity.
-     */
-    fun setTransform(transform: EntityTransform?) {
-        if (transform == null) return
-        return engine.native.setTransform(id, transform)
-    }
-
-    /**
-     * Fetches the property of the ModelProperty component on the entity.
-     */
-    inline fun <reified T> getProperty(key: String): T? {
-        return when (T::class) {
-            String::class -> engine.native.getStringProperty(id.id, key) as T?
-            Long::class -> engine.native.getLongProperty(id.id, key) as T?
-            Int::class -> engine.native.getIntProperty(id.id, key) as T?
-            Double::class -> engine.native.getDoubleProperty(id.id, key) as T?
-
-            Float::class -> engine.native.getFloatProperty(id.id, key) as T?
-            Boolean::class -> engine.native.getBoolProperty(id.id, key) as T?
-            FloatArray::class -> engine.native.getVec3Property(id.id, key) as T?
-            else -> throw IllegalArgumentException("Unsupported property type: ${T::class}")
-        }
-    }
-
     /**
-     * Sets a property of the ModelProperty component on the entity.
+     * The `Label` component (the entity name).
      *
-     * # Supported types
-     * - [kotlin.String]
-     * - [kotlin.Long]
-     * - [kotlin.Int]
-     * - [kotlin.Double]
-     * - [kotlin.Float]
-     * - [kotlin.Boolean]
-     * - [com.dropbear.math.Vector3]
-     */
-    /**
-     * Sets a property of the ModelProperty component on the entity.
-     *
-     * # Supported types
-     * - [kotlin.String]
-     * - [kotlin.Long]
-     * - [kotlin.Int]
-     * - [kotlin.Double]
-     * - [kotlin.Float]
-     * - [kotlin.Boolean]
-     * - [com.dropbear.math.Vector3]
-     */
-    fun setProperty(key: String, value: Any) {
-        when (value) {
-            is String -> engine.native.setStringProperty(id.id, key, value)
-            is Long -> engine.native.setLongProperty(id.id, key, value)
-            is Int -> engine.native.setIntProperty(id.id, key, value)
-            is Double -> engine.native.setFloatProperty(id.id, key, value)
-            is Float -> engine.native.setFloatProperty(id.id, key, value.toDouble())
-            is Boolean -> engine.native.setBoolProperty(id.id, key, value)
-            is Vector3<*> -> {
-                val vec = value.asDoubleVector()
-                engine.native.setVec3Property(id.id, key, floatArrayOf(vec.x.toFloat(), vec.y.toFloat(),
-                    vec.z.toFloat()
-                ))
-            }
-            is FloatArray -> {
-                require(value.size == 3) { "Vec3 property must have exactly 3 elements" }
-                engine.native.setVec3Property(id.id, key, value)
-            }
-            else -> throw IllegalArgumentException("Unsupported property type: ${value::class}")
-        }
-    }
-
-    /**
-     * Fetches the attached camera for the entity.
-     *
-     * Returns null if no camera is attached as a component according to the editor.
-     */
-    fun getAttachedCamera(): Camera? {
-        val result = engine.native.getAttachedCamera(id)
-        if (result != null) {
-            result.engine = this.engine
-        }
-        return result
-    }
-
-    /**
-     * Fetches the texture for the given material name in the model.
-     */
-    fun getTexture(materialName: String): TextureHandle? {
-        val result = engine.native.getTexture(id.id, materialName)
-        return if (result == null) {
-            null
-        } else {
-            TextureHandle(result)
-        }
-    }
-
-    /**
-     * Returns an array containing the texture identifiers applied to this entity's model.
-     */
-    fun getAllTextures(): Array<String> {
-        return engine.native.getAllTextures(id.id)
-    }
-
-    /**
-     * Checks if the current model being rendered by this entity contains the texture with the given [TextureHandle]
-     */
-    fun hasTexture(textureHandle: TextureHandle): Boolean {
-        return engine.native.isUsingTexture(id.id, textureHandle.raw())
-    }
-
-    /**
-     * Fetches the active model that is currently being used
-     */
-    fun getModel(): ModelHandle? {
-        val result = engine.native.getModel(id.id)
-        return if (result == null) {
-            null
-        } else {
-            ModelHandle(result)
-        }
-    }
-
-    /**
-     * Sets the active model for the entity from a ModelHandle
+     * All entities have a `Label` component. If one does not, it is considered a bug in the engine or *you* did something
+     * to break this. Anyhow, it will throw an [Exception].
      */
-    fun setModel(modelHandle: ModelHandle) {
-        engine.native.setModel(id.id, modelHandle.raw())
-    }
+    val label: String
+        get() = getEntityLabel(id)
 
-    /**
-     * Checks if the entity is currently using the given model handle.
-     *
-     * Returns false if not using, true if is.
-     */
-    fun usingModel(modelHandle: ModelHandle): Boolean {
-        return engine.native.isUsingModel(id.id, modelHandle.raw())
+    override fun toString(): String {
+        return "EntityRef(id=$id)"
     }
 
-    /**
-     * Sets a texture override for the given material on the active model.
-     */
-    fun setTextureOverride(materialName: String, textureHandle: TextureHandle) {
-        engine.native.setTextureOverride(id.id, materialName, textureHandle)
+    fun <T : Component> getComponent(type: ComponentType<T>): T? {
+        return type.get(id)
     }
 
     /**
@@ -204,7 +48,7 @@ class EntityRef(val id: EntityId = EntityId(0L)) {
      * By running [getChildren] on `cat`, it will return `[ wizard_hat ]`, not `pom_pom`.
      */
     fun getChildren(): Array<EntityRef>? {
-        return engine.native.getChildren(id)
+        return getChildren(id)
     }
 
     /**
@@ -213,7 +57,7 @@ class EntityRef(val id: EntityId = EntityId(0L)) {
      * Returns `null` if an error occurred or no child exists, otherwise the entity.
      */
     fun getChildByLabel(label: String): EntityRef? {
-        return engine.native.getChildByLabel(id, label)
+        return getChildByLabel(id, label)
     }
 
     /**
@@ -232,60 +76,11 @@ class EntityRef(val id: EntityId = EntityId(0L)) {
      * Calling [getParent] on `cat` will return `null`, as the Scene is not an entity.
      */
     fun getParent(): EntityRef? {
-        return engine.native.getParent(id)
-    }
-
-    /**
-     * Fetches the `Label` component (the entity name).
-     *
-     * All entities have a `Label` component. If one does not, it is considered a bug in the engine or *you* did something
-     * to break this. Anyhow, it will throw an [Exception].
-     */
-    fun getLabel(): String {
-        return engine.native.getEntityLabel(id.id) ?: throw Exception("Entity has no label, expected to contain. Likely engine bug")
-    }
-
-    // physics stuff
-
-    /**
-     * Sets physics enabled.
-     *
-     * If physics is enabled, the entity will be under the influence of gravity. Using position based movement will
-     * not be possible, and will have to be done with physics based movement.
-     *
-     * If physics is disabled, the entity will **not** be under the influence of gravity and will stand still.
-     * Other physic-based entities (such as walls and floors) will not affect the player, and collision based
-     * functions will not work.
-     */
-    fun setPhysicsEnabled(enabled: Boolean) {
-        return engine.native.setPhysicsEnabled(id.id, enabled)
-    }
-
-    /**
-     * Checks if physics is applied to the entity.
-     *
-     * If physics is enabled, it will return true. If not, it will return false.
-     */
-    fun isPhysicsEnabled(): Boolean {
-        return engine.native.isPhysicsEnabled(id.id)
+        return getParent(id)
     }
+}
 
-    /**
-     * Fetches the [`RigidBody`] component.
-     *
-     * Can return `null` if no component exists within the entity.
-     */
-    fun getRigidBody(): RigidBody? {
-        return engine.native.getRigidBody(id.id)
-    }
-
-    /**
-     * Fetches all available colliders under the `ColliderGroup` component of the entity.
-     *
-     * Can return an [emptyArray] if no Collider's exist within the group, or `null` if no
-     * such component (`ColliderGroup`) is available on the entity.
-     */
-    fun getAllColliders(): List<Collider>? {
-        return engine.native.getAllColliders(id.id)
-    }
-}
\ No newline at end of file
+expect fun EntityRef.getEntityLabel(entity: EntityId): String
+expect fun EntityRef.getChildren(entityId: EntityId): Array<EntityRef>?
+expect fun EntityRef.getChildByLabel(entityId: EntityId, label: String): EntityRef?
+expect fun EntityRef.getParent(entityId: EntityId): EntityRef?
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/EntityTransform.kt b/src/commonMain/kotlin/com/dropbear/EntityTransform.kt
deleted file mode 100644
index d60d059..0000000
--- a/src/commonMain/kotlin/com/dropbear/EntityTransform.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.dropbear
-
-import com.dropbear.math.Transform
-
-/**
- * A component that contains the local and world [Transform] of an entity.
- */
-class EntityTransform(var local: Transform, var world: Transform) {
-    override fun toString(): String {
-        return "EntityTransform(local=$local, world=$world)"
-    }
-}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/Runnable.kt b/src/commonMain/kotlin/com/dropbear/Runnable.kt
index ac126c0..3183324 100644
--- a/src/commonMain/kotlin/com/dropbear/Runnable.kt
+++ b/src/commonMain/kotlin/com/dropbear/Runnable.kt
@@ -7,7 +7,10 @@ package com.dropbear
  * the `magna-carta` manifest generator tool. 
  * 
  * The tags correspond to the tags provided to the entity
- * with the Script. 
+ * with the Script.
+ *
+ * # Note
+ * This annotation only works on classes that implement [com.dropbear.ecs.System], **not** [com.dropbear.ecs.Component]
  */
 @Target(AnnotationTarget.CLASS)
 @Retention(AnnotationRetention.SOURCE)
diff --git a/src/commonMain/kotlin/com/dropbear/ScriptRegistration.kt b/src/commonMain/kotlin/com/dropbear/ScriptRegistration.kt
index 63b8e57..8b50f43 100644
--- a/src/commonMain/kotlin/com/dropbear/ScriptRegistration.kt
+++ b/src/commonMain/kotlin/com/dropbear/ScriptRegistration.kt
@@ -1,5 +1,10 @@
 package com.dropbear
 
+import com.dropbear.ecs.System
+
+/**
+ * Internal data class to register scripts with tags.
+ */
 data class ScriptRegistration(
     val tags: List<String>,
     val script: System
diff --git a/src/commonMain/kotlin/com/dropbear/System.kt b/src/commonMain/kotlin/com/dropbear/System.kt
deleted file mode 100644
index a1a62b6..0000000
--- a/src/commonMain/kotlin/com/dropbear/System.kt
+++ /dev/null
@@ -1,93 +0,0 @@
-package com.dropbear
-
-/**
- * A class that contains the basic information of a system. 
- * 
- * The dropbear engine follows an ECS paradigm, with logic being
- * provided as Systems.
- */
-open class System {
-    /**
-     * The current entity that is being run in the system. Most often than not, it
-     * will have an [EntityRef] attached (because all Script components must be part
-     * of an entity).
-     */
-    var currentEntity: EntityRef? = null
-        private set
-
-    private var engineRef: DropbearEngine? = null
-
-    /**
-     * This function is called when the script module is initialised.
-     *
-     * It is only called once during scene execution. If you re-switch back
-     * to this scene after running the class, it will be run again.
-     */
-    open fun load(engine: DropbearEngine) {}
-
-    /**
-     * This function is called for each update.
-     *
-     * It is run once for each frame, and for every frame. Since this is synced to the frame rate, using
-     * the [deltaTime] variable can aid you in creating uniform player speeds (or something like that).
-     *
-     * @param deltaTime - This specifies the time elapsed since the last update.
-     */
-    open fun update(engine: DropbearEngine, deltaTime: Float) {}
-
-    /**
-     * This function is called for each update that is related to physics.
-     *
-     * It can be run 0, 1, 2 or more times per frame. Updating physics is done at a constant
-     * rate/tick (at roughly 50Hz or 0.02), which is why it is not ran as often as a standard [update].
-     *
-     * @param deltaTime - This specifies the time elapsed since the last frame update. Likely, it's going
-     *                    to be somewhere around 50Hz. For the most part, you might not need this.
-     */
-    open fun physicsUpdate(engine: DropbearEngine, deltaTime: Float) {}
-
-    /**
-     * This function is called at the end of the script execution.
-     *
-     * It is run at the end of execution of a scene, such as when the scene switches. It is also ran once throughout
-     * the lifecycle of a script class. It is best to think about it like `sceneExit()` instead of `appExit()`
-     *
-     * It would be used to clean up any memory related resources (such as `SceneLoadHandle` or any memory related items).
-     *
-     * # Note
-     *
-     * The script module does not lose state (such as variables) when destroyed. It is cached internally (within the system manager),
-     * therefore counters and other related stuff will not lose track.
-     */
-    open fun destroy(engine: DropbearEngine) {}
-
-    /**
-     * Internal: This attaches the [DropbearEngine] fascade (typically created through some external location)
-     * to the existing system to be used.
-     */
-    fun attachEngine(engine: DropbearEngine) {
-        engineRef = engine
-        currentEntity?.engine = engine
-    }
-
-    /**
-     * Internal: Sets the current entity of this [System] to something.
-     */
-    fun setCurrentEntity(entity: Long) {
-        val engine = engineRef ?: run {
-            currentEntity = null
-            return
-        }
-
-        val reference = EntityRef(EntityId(entity))
-        reference.engine = engine
-        currentEntity = reference
-    }
-
-    /**
-     * Internal: Clears the current entity used.
-     */
-    fun clearCurrentEntity() {
-        currentEntity = null
-    }
-}
diff --git a/src/commonMain/kotlin/com/dropbear/asset/AssetHandle.kt b/src/commonMain/kotlin/com/dropbear/asset/AssetHandle.kt
index ab765ee..53fb47f 100644
--- a/src/commonMain/kotlin/com/dropbear/asset/AssetHandle.kt
+++ b/src/commonMain/kotlin/com/dropbear/asset/AssetHandle.kt
@@ -11,8 +11,8 @@ class AssetHandle(private val id: Long): Handle(id) {
      *
      * It can return null if the asset is not a model.
      */
-    fun asModelHandle(engine: DropbearEngine): ModelHandle? {
-        val result = engine.native.isModelHandle(id)
+    fun asModelHandle(): ModelHandle? {
+        val result = isModelHandle(id)
         return if (result) {
             ModelHandle(id)
         } else {
@@ -20,25 +20,24 @@ class AssetHandle(private val id: Long): Handle(id) {
         }
     }
 
+    override fun asAssetHandle(): AssetHandle {
+        return this
+    }
+
     /**
      * Converts an [AssetHandle] to a [TextureHandle].
      *
      * It can return null if the asset is not a texture.
      */
-    fun asTextureHandle(engine: DropbearEngine): TextureHandle? {
-        val result = engine.native.isTextureHandle(id)
-        return if (result) {
-            TextureHandle(id)
-        } else {
-            null
-        }
-    }
-
-    override fun asAssetHandle(): AssetHandle {
-        return this
+    fun asTextureHandle(): TextureHandle? {
+        return if (isTextureHandle(id)) TextureHandle(id) else null
     }
 
     override fun toString(): String {
         return "AssetHandle(id=$id)"
     }
-}
\ No newline at end of file
+}
+
+
+expect fun isTextureHandle(id: Long): Boolean
+expect fun isModelHandle(id: Long): Boolean
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/asset/Handle.kt b/src/commonMain/kotlin/com/dropbear/asset/Handle.kt
index 0528615..57f134c 100644
--- a/src/commonMain/kotlin/com/dropbear/asset/Handle.kt
+++ b/src/commonMain/kotlin/com/dropbear/asset/Handle.kt
@@ -5,8 +5,14 @@ package com.dropbear.asset
  *
  * Aims to allow people to group up different handle types ([AssetHandle], [ModelHandle] etc...)
  * into a list or a vector.
+ *
+ * All handles must be positive, non-zero values. If the id does not follow that rule, it is considered invalid.
  */
 abstract class Handle(private val id: Long) {
+    init {
+        require(id > 0) { "Handle id must be a positive, non-zero value. Got: $id" }
+    }
+
     /**
      * Returns the raw id of the handle
      */
diff --git a/src/commonMain/kotlin/com/dropbear/asset/TextureHandle.kt b/src/commonMain/kotlin/com/dropbear/asset/TextureHandle.kt
index 3d658bb..6690a1e 100644
--- a/src/commonMain/kotlin/com/dropbear/asset/TextureHandle.kt
+++ b/src/commonMain/kotlin/com/dropbear/asset/TextureHandle.kt
@@ -1,7 +1,5 @@
 package com.dropbear.asset
 
-import com.dropbear.DropbearEngine
-
 /**
  * Another type of asset handle, this wraps the id into 
  * another form that only texture related functions can use. 
@@ -12,11 +10,13 @@ class TextureHandle(private val id: Long): Handle(id) {
     /**
      * Fetches the name of that specific texture
      */
-    fun getName(engine: DropbearEngine): String? {
-        return engine.native.getTextureName(id)
+    fun getName(): String? {
+        return getTextureName(id)
     }
 
     override fun toString(): String {
         return "TextureHandle(id=$id)"
     }
-}
\ No newline at end of file
+}
+
+expect fun TextureHandle.getTextureName(id: Long): String?
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/components/Camera.kt b/src/commonMain/kotlin/com/dropbear/components/Camera.kt
new file mode 100644
index 0000000..737f23a
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/components/Camera.kt
@@ -0,0 +1,91 @@
+package com.dropbear.components
+
+import com.dropbear.EntityId
+import com.dropbear.ecs.Component
+import com.dropbear.ecs.ComponentType
+import com.dropbear.math.Vector3d
+
+/**
+ * Describes a 3D camera, as defined in `dropbear_engine::camera::Camera`
+ */
+class Camera(
+    internal val entity: EntityId,
+): Component(entity, "Camera3D") {
+    var eye: Vector3d
+        get() = getCameraEye(entity)
+        set(value) = setCameraEye(entity, value)
+    var target: Vector3d
+        get() = getCameraTarget(entity)
+        set(value) = setCameraTarget(entity, value)
+    var up: Vector3d
+        get() = getCameraUp(entity)
+        set(value) = setCameraUp(entity, value)
+    val aspect: Double
+        get() = getCameraAspect(entity)
+    var fov_y: Double
+        get() = getCameraFovY(entity)
+        set(value) = setCameraFovY(entity, value)
+    var znear: Double
+        get() = getCameraZNear(entity)
+        set(value) = setCameraZNear(entity, value)
+    var zfar: Double
+        get() = getCameraZFar(entity)
+        set(value) = setCameraZFar(entity, value)
+    var yaw: Double
+        get() = getCameraYaw(entity)
+        set(value) = setCameraYaw(entity, value)
+    var pitch: Double
+        get() = getCameraPitch(entity)
+        set(value) = setCameraPitch(entity, value)
+    var speed: Double
+        get() = getCameraSpeed(entity)
+        set(value) = setCameraSpeed(entity, value)
+    var sensitivity: Double
+        get() = getCameraSensitivity(entity)
+        set(value) = setCameraSensitivity(entity, value)
+
+    override fun toString(): String {
+        return "Camera component of entity $entity \n" +
+                "eye: $eye\n" +
+                "target: $target\n" +
+                "up: $up\n " +
+                "aspect: $aspect \n" +
+                "fov_y: $fov_y \n" +
+                "znear: $znear \n" +
+                "zfar: $zfar \n" +
+                "yaw: $yaw \n" +
+                "pitch: $pitch" +
+                "speed: $speed" +
+                "sensitivity: $sensitivity"
+    }
+
+    companion object : ComponentType<Camera> {
+        override fun get(entityId: EntityId): Camera? {
+            return if (cameraExistsForEntity(entityId)) Camera(entityId) else null
+        }
+    }
+}
+
+expect fun Camera.getCameraEye(entity: EntityId): Vector3d
+expect fun Camera.setCameraEye(entity: EntityId, value: Vector3d)
+expect fun Camera.getCameraTarget(entity: EntityId): Vector3d
+expect fun Camera.setCameraTarget(entity: EntityId, value: Vector3d)
+expect fun Camera.getCameraUp(entity: EntityId): Vector3d
+expect fun Camera.setCameraUp(entity: EntityId, value: Vector3d)
+expect fun Camera.getCameraAspect(entity: EntityId): Double
+expect fun Camera.getCameraFovY(entity: EntityId): Double
+expect fun Camera.setCameraFovY(entity: EntityId, value: Double)
+expect fun Camera.getCameraZNear(entity: EntityId): Double
+expect fun Camera.setCameraZNear(entity: EntityId, value: Double)
+expect fun Camera.getCameraZFar(entity: EntityId): Double
+expect fun Camera.setCameraZFar(entity: EntityId, value: Double)
+expect fun Camera.getCameraYaw(entity: EntityId): Double
+expect fun Camera.setCameraYaw(entity: EntityId, value: Double)
+expect fun Camera.getCameraPitch(entity: EntityId): Double
+expect fun Camera.setCameraPitch(entity: EntityId, value: Double)
+expect fun Camera.getCameraSpeed(entity: EntityId): Double
+expect fun Camera.setCameraSpeed(entity: EntityId, value: Double)
+expect fun Camera.getCameraSensitivity(entity: EntityId): Double
+expect fun Camera.setCameraSensitivity(entity: EntityId, value: Double)
+
+expect fun cameraExistsForEntity(entity: EntityId): Boolean
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/components/CustomProperties.kt b/src/commonMain/kotlin/com/dropbear/components/CustomProperties.kt
new file mode 100644
index 0000000..3aef136
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/components/CustomProperties.kt
@@ -0,0 +1,99 @@
+package com.dropbear.components
+
+import com.dropbear.EntityId
+import com.dropbear.ecs.Component
+import com.dropbear.ecs.ComponentType
+import com.dropbear.math.Vector3d
+import com.dropbear.math.Vector3f
+import com.dropbear.math.Vector3i
+
+class CustomProperties(val id: EntityId): Component(id, "CustomProperties") {
+    /**
+     * Fetches the property of the ModelProperty component on the entity.
+     *
+     * @param T The type of the property to fetch.
+     * # Supported types
+     * - [kotlin.String]
+     * - [Long]
+     * - [Int]
+     * - [Double]
+     * - [Float]
+     * - [Boolean]
+     * - [Vector3d]
+     * - [Vector3f]
+     * - [Vector3i]
+     * @param key The key of the property to fetch.
+     * @return The property value, or null if the property does not exist.
+     * @throws IllegalArgumentException if the property type is not supported.
+     */
+    inline fun <reified T> getProperty(key: String): T? {
+        return when (T::class) {
+            String::class -> getStringProperty(id.raw, key) as T?
+            Long::class -> getLongProperty(id.raw, key) as T?
+            Int::class -> getIntProperty(id.raw, key) as T?
+            Double::class -> getDoubleProperty(id.raw, key) as T?
+
+            Float::class -> getFloatProperty(id.raw, key) as T?
+            Boolean::class -> getBoolProperty(id.raw, key) as T?
+            Vector3d::class -> getVec3Property(id.raw, key) as T?
+            Vector3f::class -> getVec3Property(id.raw, key)?.toFloat() as T?
+            Vector3i::class -> getVec3Property(id.raw, key)?.toInt() as T?
+            else -> throw IllegalArgumentException("Unsupported property type: ${T::class}")
+        }
+    }
+
+    /**
+     * Sets a property of the ModelProperty component on the entity.
+     *
+     * @param key The key of the property to set.
+     * @param value The type of the property to set.
+     * # Supported types
+     * - [kotlin.String]
+     * - [Long]
+     * - [Int]
+     * - [Double]
+     * - [Float]
+     * - [Boolean]
+     * - [Vector3d]
+     * - [Vector3f]
+     * - [Vector3i]
+     * @throws IllegalArgumentException if the property type is not supported.
+     */
+    fun setProperty(key: String, value: Any) {
+        when (value) {
+            is String -> setStringProperty(id.raw, key, value)
+            is Long -> setLongProperty(id.raw, key, value)
+            is Int -> setIntProperty(id.raw, key, value)
+            is Double -> setFloatProperty(id.raw, key, value)
+            is Float -> setFloatProperty(id.raw, key, value.toDouble())
+            is Boolean -> setBoolProperty(id.raw, key, value)
+            is Vector3d -> setVec3Property(id.raw, key, value)
+            is Vector3f -> setVec3Property(id.raw ,key, value.toDouble())
+            is Vector3i -> setVec3Property(id.raw, key, value.toDouble())
+            else -> throw IllegalArgumentException("Unsupported property type: ${value::class}")
+        }
+    }
+
+    companion object : ComponentType<CustomProperties> {
+        override fun get(entityId: EntityId): CustomProperties? {
+            return if (customPropertiesExistsForEntity(entityId)) CustomProperties(entityId) else null
+        }
+    }
+}
+
+expect fun CustomProperties.getStringProperty(entityHandle: Long, label: String): String?
+expect fun CustomProperties.getIntProperty(entityHandle: Long, label: String): Int?
+expect fun CustomProperties.getLongProperty(entityHandle: Long, label: String): Long?
+expect fun CustomProperties.getDoubleProperty(entityHandle: Long, label: String): Double?
+expect fun CustomProperties.getFloatProperty(entityHandle: Long, label: String): Float?
+expect fun CustomProperties.getBoolProperty(entityHandle: Long, label: String): Boolean?
+expect fun CustomProperties.getVec3Property(entityHandle: Long, label: String): Vector3d?
+
+expect fun CustomProperties.setStringProperty(entityHandle: Long, label: String, value: String)
+expect fun CustomProperties.setIntProperty(entityHandle: Long, label: String, value: Int)
+expect fun CustomProperties.setLongProperty(entityHandle: Long, label: String, value: Long)
+expect fun CustomProperties.setFloatProperty(entityHandle: Long, label: String, value: Double)
+expect fun CustomProperties.setBoolProperty(entityHandle: Long, label: String, value: Boolean)
+expect fun CustomProperties.setVec3Property(entityHandle: Long, label: String, value: Vector3d)
+
+expect fun customPropertiesExistsForEntity(entityId: EntityId): Boolean
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/components/EntityTransform.kt b/src/commonMain/kotlin/com/dropbear/components/EntityTransform.kt
new file mode 100644
index 0000000..4d010e2
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/components/EntityTransform.kt
@@ -0,0 +1,60 @@
+package com.dropbear.components
+
+import com.dropbear.EntityId
+import com.dropbear.ecs.Component
+import com.dropbear.ecs.ComponentType
+import com.dropbear.math.Transform
+
+/**
+ * A component that contains the local and world [Transform] of an entity.
+ */
+class EntityTransform(val id: EntityId): Component(id, "EntityTransform") {
+    var local: Transform
+        get() = getLocalTransform(id)
+        set(value) = setLocalTransform(id, value)
+    var world: Transform
+        get() = getWorldTransform(id)
+        set(value) = setWorldTransform(id, value)
+
+    override fun toString(): String {
+        return "EntityTransform(id: $id, local=$local, world=$world)"
+    }
+
+    /**
+     * Walks up the world hierarchy to find the transform of the parent, then multiply/add
+     * to create a propagated [Transform].
+     */
+    fun propagate(): Transform? {
+        return propagateTransform(id)
+    }
+
+    /**
+     * Merges the local and world transforms and return the merged [Transform]
+     */
+    fun sync(): Transform {
+        val scaledPos = local.position * world.scale
+        val rotatedPos = world.rotation * scaledPos
+        val finalPos = world.position + rotatedPos
+        val finalRot = world.rotation * local.rotation
+        val finalScale = world.scale * local.scale
+        return Transform(finalPos, finalRot, finalScale)
+    }
+
+    companion object : ComponentType<EntityTransform> {
+        override fun get(entityId: EntityId): EntityTransform? {
+            return if (entityTransformExistsForEntity(entityId)) {
+                EntityTransform(entityId)
+            } else {
+                null
+            }
+        }
+    }
+}
+
+expect fun EntityTransform.getLocalTransform(entityId: EntityId): Transform
+expect fun EntityTransform.setLocalTransform(entityId: EntityId, transform: Transform)
+expect fun EntityTransform.getWorldTransform(entityId: EntityId): Transform
+expect fun EntityTransform.setWorldTransform(entityId: EntityId, transform: Transform)
+expect fun EntityTransform.propagateTransform(entityId: EntityId): Transform?
+
+expect fun entityTransformExistsForEntity(entityId: EntityId): Boolean
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/components/MeshRenderer.kt b/src/commonMain/kotlin/com/dropbear/components/MeshRenderer.kt
new file mode 100644
index 0000000..93af8a3
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/components/MeshRenderer.kt
@@ -0,0 +1,64 @@
+package com.dropbear.components
+
+import com.dropbear.EntityId
+import com.dropbear.asset.ModelHandle
+import com.dropbear.asset.TextureHandle
+import com.dropbear.ecs.Component
+import com.dropbear.ecs.ComponentType
+
+class MeshRenderer(val id: EntityId) : Component(id, "MeshRenderer") {
+
+    /**
+     * The active model currently assigned to this entity.
+     * Setting this to null removes the model.
+     *
+     * Usage:
+     * ```
+     * val handle = renderer.model
+     * renderer.model = newHandle
+     * ```
+     */
+    var model: ModelHandle?
+        get() {
+            return getModel(id)
+        }
+        set(value) {
+            setModel(id, value)
+        }
+
+    /**
+     * A list of all active Texture handles currently applied to the model.
+     */
+    val textures: List<TextureHandle>?
+        get() = getAllTextureIds(id)
+
+    /**
+     * Fetches the texture assigned to a specific material slot.
+     * Returns null if the material doesn't exist or has no texture.
+     */
+    fun getTexture(materialName: String): TextureHandle? {
+        val rawId = getTexture(id, materialName)
+        return if (rawId == 0L || rawId == null) null else TextureHandle(rawId)
+    }
+
+    /**
+     * Overrides the texture for a specific material on the active model.
+     */
+    fun setTextureOverride(materialName: String, textureHandle: TextureHandle) {
+        setTextureOverride(id, materialName, textureHandle.raw())
+    }
+
+    companion object : ComponentType<MeshRenderer> {
+        override fun get(entityId: EntityId): MeshRenderer? {
+            return if (meshRendererExistsForEntity(entityId)) MeshRenderer(entityId) else null
+        }
+    }
+}
+
+expect fun MeshRenderer.getModel(id: EntityId): ModelHandle?
+expect fun MeshRenderer.setModel(id: EntityId, model: ModelHandle?)
+expect fun MeshRenderer.getAllTextureIds(id: EntityId): List<TextureHandle>?
+expect fun MeshRenderer.getTexture(id: EntityId, materialName: String): Long?
+expect fun MeshRenderer.setTextureOverride(id: EntityId, materialName: String, textureHandle: Long)
+
+expect fun meshRendererExistsForEntity(entityId: EntityId): Boolean
diff --git a/src/commonMain/kotlin/com/dropbear/ecs/Component.kt b/src/commonMain/kotlin/com/dropbear/ecs/Component.kt
new file mode 100644
index 0000000..4975ddd
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/ecs/Component.kt
@@ -0,0 +1,24 @@
+package com.dropbear.ecs
+
+import com.dropbear.EntityId
+
+/**
+ * A custom property that *can* be attached to an entity in the dropbear ECS system.
+ *
+ * @param parentEntity - The [com.dropbear.EntityId] to which this component is attached.
+ * @param typeName - The string name of the component type as defined in Rust. The name is
+ *                   found as the type name, such as `component_registry.register_with_default::<Camera3D>();`,
+ *                   where the type name would be `"Camera3D"`.
+ */
+abstract class Component(
+    val parentEntity: EntityId,
+    val typeName: String,
+) {
+    override fun toString(): String {
+        return "Component(parentEntity: ${this.parentEntity}, typeName: $typeName)"
+    }
+}
+
+interface ComponentType<T : Component> {
+    fun get(entityId: EntityId): T?
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/ecs/System.kt b/src/commonMain/kotlin/com/dropbear/ecs/System.kt
new file mode 100644
index 0000000..827d4b4
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/ecs/System.kt
@@ -0,0 +1,90 @@
+package com.dropbear.ecs
+
+import com.dropbear.DropbearEngine
+import com.dropbear.EntityId
+import com.dropbear.EntityRef
+
+/**
+ * A class that contains the basic information of a system.
+ *
+ * The dropbear engine follows an ECS paradigm, with logic being
+ * provided as Systems.
+ */
+open class System {
+    /**
+     * The current entity that is being run in the system. Most often than not, it
+     * will have an [com.dropbear.EntityRef] attached (because all Script components must be part
+     * of an entity).
+     */
+    var currentEntity: EntityRef? = null
+        private set
+
+    private var engineRef: DropbearEngine? = null
+
+    /**
+     * This function is called when the script module is initialised.
+     *
+     * It is only called once during scene execution. If you re-switch back
+     * to this scene after running the class, it will be run again.
+     */
+    open fun load(engine: DropbearEngine) {}
+
+    /**
+     * This function is called for each update.
+     *
+     * It is run once for each frame, and for every frame. Since this is synced to the frame rate, using
+     * the [deltaTime] variable can aid you in creating uniform player speeds (or something like that).
+     *
+     * @param deltaTime - This specifies the time elapsed since the last update.
+     */
+    open fun update(engine: DropbearEngine, deltaTime: Float) {}
+
+    /**
+     * This function is called for each update that is related to physics.
+     *
+     * It can be run 0, 1, 2 or more times per frame. Updating physics is done at a constant
+     * rate/tick (at roughly 50Hz or 0.02), which is why it is not ran as often as a standard [update].
+     *
+     * @param deltaTime - This specifies the time elapsed since the last frame update. Likely, it's going
+     *                    to be somewhere around 50Hz. For the most part, you might not need this.
+     */
+    open fun physicsUpdate(engine: DropbearEngine, deltaTime: Float) {}
+
+    /**
+     * This function is called at the end of the script execution.
+     *
+     * It is run at the end of execution of a scene, such as when the scene switches. It is also ran once throughout
+     * the lifecycle of a script class. It is best to think about it like `sceneExit()` instead of `appExit()`
+     *
+     * It would be used to clean up any memory related resources (such as `SceneLoadHandle` or any memory related items).
+     *
+     * # Note
+     *
+     * The script module does not lose state (such as variables) when destroyed. It is cached internally (within the system manager),
+     * therefore counters and other related stuff will not lose track.
+     */
+    open fun destroy(engine: DropbearEngine) {}
+
+    /**
+     * Internal: This attaches the [DropbearEngine] fascade (typically created through some external location)
+     * to the existing system to be used.
+     */
+    fun attachEngine(engine: DropbearEngine) {
+        engineRef = engine
+    }
+
+    /**
+     * Internal: Sets the current entity of this [System] to something.
+     */
+    fun setCurrentEntity(entity: Long) {
+        val reference = EntityRef(EntityId(entity))
+        currentEntity = reference
+    }
+
+    /**
+     * Internal: Clears the current entity used.
+     */
+    fun clearCurrentEntity() {
+        currentEntity = null
+    }
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/ffi/NativeEngine.kt b/src/commonMain/kotlin/com/dropbear/ffi/NativeEngine.kt
index 9a5507a..7d2b758 100644
--- a/src/commonMain/kotlin/com/dropbear/ffi/NativeEngine.kt
+++ b/src/commonMain/kotlin/com/dropbear/ffi/NativeEngine.kt
@@ -1,123 +1,9 @@
 package com.dropbear.ffi
 
-import com.dropbear.Camera
-import com.dropbear.EntityId
-import com.dropbear.EntityRef
-import com.dropbear.EntityTransform
-import com.dropbear.asset.AssetHandle
-import com.dropbear.asset.ModelHandle
-import com.dropbear.asset.TextureHandle
-import com.dropbear.input.Gamepad
-import com.dropbear.input.GamepadButton
-import com.dropbear.input.KeyCode
-import com.dropbear.input.MouseButton
-import com.dropbear.math.Transform
-import com.dropbear.math.Vector2D
-import com.dropbear.math.Vector3D
-import com.dropbear.physics.Collider
-import com.dropbear.physics.Index
-import com.dropbear.physics.RigidBody
-import com.dropbear.scene.SceneLoadHandle
-import com.dropbear.scene.SceneLoadStatus
-import com.dropbear.utils.Progress
-
 /**
- * Native functions
+ * Native information about dropbear engine.
  *
- * Class that describes all the functions that can
- * be communicated with the `eucalyptus_core` dynamic library
+ * This class contains handles to various native subsystems such as world, input, graphics, and more. Different
+ * targets (JVM, Native) will have different implementations for managing these handles. Check them out!
  */
-expect class NativeEngine {
-    fun getEntity(label: String): Long?
-    fun getAsset(eucaURI: String): Long?
-
-    fun getEntityLabel(entityHandle: Long): String?
-
-    fun getModel(entityHandle: Long): Long?
-    fun setModel(entityHandle: Long, modelHandle: Long)
-    fun isUsingModel(entityHandle: Long, modelHandle: Long): Boolean
-    fun isModelHandle(id: Long): Boolean
-
-    fun getTexture(entityHandle: Long, name: String): Long?
-    fun setTextureOverride(entityHandle: Long, oldMaterialName: String, newTextureHandle: TextureHandle)
-    fun isUsingTexture(entityHandle: Long, textureHandle: Long): Boolean
-    fun isTextureHandle(id: Long): Boolean
-    fun getTextureName(textureHandle: Long): String?
-    fun getAllTextures(entityHandle: Long): Array<String>
-
-    fun getCamera(label: String): Camera?
-    fun getAttachedCamera(entityId: EntityId): Camera?
-    fun setCamera(camera: Camera);
-
-    fun getTransform(entityId: EntityId): EntityTransform?
-    fun propagateTransform(entityId: EntityId): Transform?
-    fun setTransform(entityId: EntityId, transform: EntityTransform)
-
-    fun getChildren(entityId: EntityId): Array<EntityRef>?
-    fun getChildByLabel(entityId: EntityId, label: String): EntityRef?
-    fun getParent(entityId: EntityId): EntityRef?
-
-    fun switchToSceneImmediate(sceneName: String)
-    fun loadSceneAsync(sceneName: String): SceneLoadHandle?
-    fun loadSceneAsync(sceneName: String, loadingScene: String): SceneLoadHandle?
-    fun switchToSceneAsync(sceneLoadHandle: SceneLoadHandle)
-    fun getSceneLoadProgress(sceneLoadHandle: SceneLoadHandle): Progress
-    fun getSceneLoadStatus(sceneLoadHandle: SceneLoadHandle): SceneLoadStatus
-
-    // ------------------------ MODEL PROPERTIES -------------------------
-
-    fun getStringProperty(entityHandle: Long, label: String): String?
-    fun getIntProperty(entityHandle: Long, label: String): Int?
-    fun getLongProperty(entityHandle: Long, label: String): Long?
-    fun getDoubleProperty(entityHandle: Long, label: String): Double?
-    fun getFloatProperty(entityHandle: Long, label: String): Float?
-    fun getBoolProperty(entityHandle: Long, label: String): Boolean?
-    fun getVec3Property(entityHandle: Long, label: String): FloatArray?
-
-    fun setStringProperty(entityHandle: Long, label: String, value: String)
-    fun setIntProperty(entityHandle: Long, label: String, value: Int)
-    fun setLongProperty(entityHandle: Long, label: String, value: Long)
-    fun setFloatProperty(entityHandle: Long, label: String, value: Double)
-    fun setBoolProperty(entityHandle: Long, label: String, value: Boolean)
-    fun setVec3Property(entityHandle: Long, label: String, value: FloatArray)
-
-
-    // --------------------------- INPUT STATE ---------------------------
-
-    /**
-     * Prints the input state, typically used for debugging.
-     */
-    fun printInputState()
-    fun isKeyPressed(key: KeyCode): Boolean
-    fun getMousePosition(): Vector2D?
-    fun isMouseButtonPressed(button: MouseButton): Boolean
-    fun getMouseDelta(): Vector2D?
-    fun isCursorLocked(): Boolean
-    fun setCursorLocked(locked: Boolean)
-    fun isCursorHidden(): Boolean
-    fun setCursorHidden(hidden: Boolean)
-    fun getLastMousePos(): Vector2D?
-    fun getConnectedGamepads(): List<Gamepad>
-    fun isGamepadButtonPressed(id: Long, button: GamepadButton): Boolean
-
-    // -------------------------------------------------------------------
-
-    // physics stuff
-
-    // EntityRef.kt
-    fun setPhysicsEnabled(entityId: Long, enabled: Boolean)
-    fun isPhysicsEnabled(entityId: Long): Boolean
-    fun getRigidBody(entityId: Long): RigidBody?
-    fun getAllColliders(entityId: Long): List<Collider>
-
-    // physics/RigidBody.kt
-    fun applyImpulse(index: Index, x: Double, y: Double, z: Double)
-    fun applyTorqueImpulse(index: Index, x: Double, y: Double, z: Double)
-    fun setRigidbody(rigidBody: RigidBody)
-    fun getChildColliders(index: Index): List<Collider>
-
-    // physics/Collider.kt
-    fun setCollider(collider: Collider)
-
-    fun quit()
-}
\ No newline at end of file
+expect class NativeEngine
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/input/Gamepad.kt b/src/commonMain/kotlin/com/dropbear/input/Gamepad.kt
index 920a9a0..c15f325 100644
--- a/src/commonMain/kotlin/com/dropbear/input/Gamepad.kt
+++ b/src/commonMain/kotlin/com/dropbear/input/Gamepad.kt
@@ -1,22 +1,28 @@
 package com.dropbear.input
 
 import com.dropbear.ffi.NativeEngine
-import com.dropbear.math.Vector2D
+import com.dropbear.math.Vector2d
 
 /**
  * Information about a specific gamepad for that time in frame.
  */
 class Gamepad(
     val id: Long,
-    val leftStickPosition: Vector2D,
-    val rightStickPosition: Vector2D,
-    val native: NativeEngine,
 ) {
+    val leftStickPosition: Vector2d
+        get() = getLeftStickPosition(id)
+    val rightStickPosition: Vector2d
+        get() = getRightStickPosition(id)
+
     fun isButtonPressed(button: GamepadButton): Boolean {
-        return native.isGamepadButtonPressed(id, button)
+        return isGamepadButtonPressed(id, button)
     }
 
     override fun toString(): String {
-        return "Gamepad $id @ ($leftStickPosition ; $rightStickPosition)"
+        return "Gamepad(id=$id)"
     }
-}
\ No newline at end of file
+}
+
+expect fun Gamepad.isGamepadButtonPressed(id: Long, button: GamepadButton): Boolean
+expect fun Gamepad.getLeftStickPosition(id: Long): Vector2d
+expect fun Gamepad.getRightStickPosition(id: Long): Vector2d
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/input/InputState.kt b/src/commonMain/kotlin/com/dropbear/input/InputState.kt
index 38a97e7..5a29d91 100644
--- a/src/commonMain/kotlin/com/dropbear/input/InputState.kt
+++ b/src/commonMain/kotlin/com/dropbear/input/InputState.kt
@@ -1,8 +1,6 @@
 package com.dropbear.input
 
-import com.dropbear.DropbearEngine
-import com.dropbear.ffi.NativeEngine
-import com.dropbear.math.Vector2D
+import com.dropbear.math.Vector2d
 
 /**
  * The current state of the input system.
@@ -13,49 +11,16 @@ import com.dropbear.math.Vector2D
  * The InputState does not have any values that can be mutated, only
  * queried. 
  */
-class InputState(private val native: NativeEngine) {
-
-    fun printInputState() {
-        native.printInputState()
-    }
-
-    fun isKeyPressed(key: KeyCode): Boolean {
-        return native.isKeyPressed(key)
-    }
-
-    fun getMousePosition(): Vector2D {
-        return native.getMousePosition() ?: Vector2D(0.0, 0.0)
-    }
-
-    fun isMouseButtonPressed(button: MouseButton): Boolean {
-        return native.isMouseButtonPressed(button)
-    }
-
-    fun getMouseDelta(): Vector2D {
-        return native.getMouseDelta() ?: Vector2D(0.0, 0.0)
-    }
-
-    fun isCursorLocked(): Boolean {
-        return native.isCursorLocked()
-    }
-
-    fun setCursorLocked(locked: Boolean) {
-        return native.setCursorLocked(locked)
-    }
-
-    fun getLastMousePos(): Vector2D {
-        return native.getLastMousePos() ?: Vector2D(0.0, 0.0)
-    }
-
-    fun isCursorHidden(): Boolean {
-        return native.isCursorHidden()
-    }
-
-    fun setCursorHidden(hidden: Boolean) {
-        return native.setCursorHidden(hidden)
-    }
-
-    fun getConnectedGamepads(): List<Gamepad> {
-        return native.getConnectedGamepads()
-    }
+expect class InputState() {
+    fun printInputState()
+    fun isKeyPressed(key: KeyCode): Boolean
+    fun getMousePosition(): Vector2d
+    fun isMouseButtonPressed(button: MouseButton): Boolean
+    fun getMouseDelta(): Vector2d
+    fun isCursorLocked(): Boolean
+    fun setCursorLocked(locked: Boolean)
+    fun getLastMousePos(): Vector2d
+    fun isCursorHidden(): Boolean
+    fun setCursorHidden(hidden: Boolean)
+    fun getConnectedGamepads(): List<Gamepad>
 }
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/logging/StdoutWriter.kt b/src/commonMain/kotlin/com/dropbear/logging/StdoutWriter.kt
index 0a4791b..845dfdc 100644
--- a/src/commonMain/kotlin/com/dropbear/logging/StdoutWriter.kt
+++ b/src/commonMain/kotlin/com/dropbear/logging/StdoutWriter.kt
@@ -1,8 +1,8 @@
 package com.dropbear.logging
 
-import kotlinx.datetime.Clock
 import kotlinx.datetime.TimeZone
 import kotlinx.datetime.toLocalDateTime
+import kotlin.time.Clock
 
 class StdoutWriter: LogWriter {
     override fun log(
diff --git a/src/commonMain/kotlin/com/dropbear/math/Quaternion.kt b/src/commonMain/kotlin/com/dropbear/math/Quaternion.kt
index 9e1cae3..3cddb8a 100644
--- a/src/commonMain/kotlin/com/dropbear/math/Quaternion.kt
+++ b/src/commonMain/kotlin/com/dropbear/math/Quaternion.kt
@@ -1,18 +1,12 @@
 package com.dropbear.math
 
 import kotlin.jvm.JvmField
-import kotlin.math.PI
-import kotlin.math.acos
-import kotlin.math.asin
-import kotlin.math.atan2
-import kotlin.math.cos
-import kotlin.math.max
-import kotlin.math.min
-import kotlin.math.sin
-import kotlin.math.sqrt
-
-typealias QuaternionD = Quaternion<Double>
+import kotlin.jvm.JvmStatic
 
+/**
+ * Generic Quaternion wrapper.
+ * WARNING: Uses boxing. Prefer [Quaterniond]/[Quaternionf] for performance.
+ */
 class Quaternion<T: Number>(
     @JvmField var x: T,
     @JvmField var y: T,
@@ -20,322 +14,34 @@ class Quaternion<T: Number>(
     @JvmField var w: T
 ) {
     companion object {
-        fun identity(): QuaternionD {
-            return Quaternion(0.0, 0.0, 0.0, 1.0)
-        }
-
-        fun fromEulerAngles(pitch: Double, yaw: Double, roll: Double): QuaternionD {
-            val halfPitch = pitch * 0.5
-            val halfYaw = yaw * 0.5
-            val halfRoll = roll * 0.5
-            val sp = sin(halfPitch)
-            val cp = cos(halfPitch)
-            val sy = sin(halfYaw)
-            val cy = cos(halfYaw)
-            val sr = sin(halfRoll)
-            val cr = cos(halfRoll)
-
-            return Quaternion(
-                x = cy * sr * cp - sy * cr * sp,
-                y = sy * cr * cp + cy * sr * sp,
-                z = sy * sr * cp - cy * cr * sp,
-                w = cy * cr * cp + sy * sr * sp
-            )
-        }
-
-        fun fromAxisAngle(axis: Vector3<Double>, angleRadians: Double): QuaternionD {
-            val normalizedAxis = axis.normalizedCopy()
-            val halfAngle = angleRadians * 0.5
-            val sinHalf = sin(halfAngle)
-            return Quaternion(
-                normalizedAxis.x * sinHalf,
-                normalizedAxis.y * sinHalf,
-                normalizedAxis.z * sinHalf,
-                cos(halfAngle)
-            )
-        }
-
-        fun rotateX(angleRadians: Double): QuaternionD {
-            return fromAxisAngle(Vector3D(1.0, 0.0, 0.0), angleRadians)
-        }
-
-        fun rotateY(angleRadians: Double): QuaternionD {
-            return fromAxisAngle(Vector3D(0.0, 1.0, 0.0), angleRadians)
-        }
-
-        fun rotateZ(angleRadians: Double): QuaternionD {
-            return fromAxisAngle(Vector3D(0.0, 0.0, 1.0), angleRadians)
-        }
-
-        fun fromToRotation(from: Vector3<Double>, to: Vector3<Double>): QuaternionD {
-            val start = from.normalizedCopy()
-            val end = to.normalizedCopy()
-            val dot = (start.x * end.x) + (start.y * end.y) + (start.z * end.z)
-            if (dot >= 1.0 - 1e-6) {
-                return identity()
-            }
-            if (dot <= -1.0 + 1e-6) {
-                val orthogonal = if (kotlin.math.abs(start.x) < 0.9) {
-                    Vector3D(1.0, 0.0, 0.0)
-                } else {
-                    Vector3D(0.0, 1.0, 0.0)
-                }
-                val axis = start.cross(orthogonal).normalizedCopy()
-                return fromAxisAngle(axis, PI)
-            }
-            val axis = start.cross(end)
-            val angle = acos(dot.coerceIn(-1.0, 1.0))
-            return fromAxisAngle(axis, angle)
-        }
-    }
-
-    fun <T : Number> rotateX(angleRadians: T): Quaternion<T> {
-        val halfAngle = angleRadians.toDouble() * 0.5
-        @Suppress("UNCHECKED_CAST")
-        return Quaternion(sin(halfAngle), 0.0, 0.0, cos(halfAngle)) as Quaternion<T>
-    }
-
-    fun <T: Number> rotateY(angleRadians: T): Quaternion<T> {
-        val halfAngle = angleRadians.toDouble() * 0.5
-        @Suppress("UNCHECKED_CAST")
-        return Quaternion(0.0, sin(halfAngle), 0.0, cos(halfAngle)) as Quaternion<T>
-    }
-
-    fun <T: Number> rotateZ(angleRadians: T): Quaternion<T> {
-        val halfAngle = angleRadians.toDouble() * 0.5
-        @Suppress("UNCHECKED_CAST")
-        return Quaternion(0.0, 0.0, sin(halfAngle), cos(halfAngle)) as Quaternion<T>
-    }
-
-    fun asDoubleQuaternion(): QuaternionD {
-        return Quaternion(x.toDouble(), y.toDouble(), z.toDouble(), w.toDouble())
-    }
-
-    fun conjugate(): QuaternionD {
-        return Quaternion(-x.toDouble(), -y.toDouble(), -z.toDouble(), w.toDouble())
-    }
-
-    fun dot(other: Quaternion<*>): Double {
-        return x.toDouble() * other.x.toDouble() +
-                y.toDouble() * other.y.toDouble() +
-                z.toDouble() * other.z.toDouble() +
-                w.toDouble() * other.w.toDouble()
-    }
-
-    fun lengthSquared(): Double {
-        return dot(this)
-    }
-
-    fun length(): Double {
-        return sqrt(lengthSquared())
-    }
-
-    fun isNormalized(epsilon: Double = 1e-6): Boolean {
-        return kotlin.math.abs(length() - 1.0) <= epsilon
-    }
-
-    fun normalized(): QuaternionD {
-        val len = length()
-        if (len == 0.0) {
-            return identity()
-        }
-        val invLen = 1.0 / len
-        return Quaternion(
-            x.toDouble() * invLen,
-            y.toDouble() * invLen,
-            z.toDouble() * invLen,
-            w.toDouble() * invLen
-        )
-    }
-
-    fun normalizeInPlace(): Quaternion<T> {
-        val normalized = normalized()
-        @Suppress("UNCHECKED_CAST")
-        x = normalized.x as T
-        @Suppress("UNCHECKED_CAST")
-        y = normalized.y as T
-        @Suppress("UNCHECKED_CAST")
-        z = normalized.z as T
-        @Suppress("UNCHECKED_CAST")
-        w = normalized.w as T
-        return this
-    }
-
-    operator fun <R: Number> plus(other: Quaternion<R>): QuaternionD {
-        return Quaternion(
-            x.toDouble() + other.x.toDouble(),
-            y.toDouble() + other.y.toDouble(),
-            z.toDouble() + other.z.toDouble(),
-            w.toDouble() + other.w.toDouble()
-        )
-    }
-
-    operator fun <R: Number> minus(other: Quaternion<R>): QuaternionD {
-        return Quaternion(
-            x.toDouble() - other.x.toDouble(),
-            y.toDouble() - other.y.toDouble(),
-            z.toDouble() - other.z.toDouble(),
-            w.toDouble() - other.w.toDouble()
-        )
-    }
-
-    operator fun unaryMinus(): QuaternionD {
-        return Quaternion(-x.toDouble(), -y.toDouble(), -z.toDouble(), -w.toDouble())
-    }
-
-    operator fun times(scalar: Number): QuaternionD {
-        val value = scalar.toDouble()
-        return Quaternion(
-            x.toDouble() * value,
-            y.toDouble() * value,
-            z.toDouble() * value,
-            w.toDouble() * value
-        )
-    }
-
-    operator fun div(scalar: Number): QuaternionD {
-        val value = scalar.toDouble()
-        require(value != 0.0) { "Cannot divide Quaternion by zero." }
-        val inv = 1.0 / value
-        return Quaternion(
-            x.toDouble() * inv,
-            y.toDouble() * inv,
-            z.toDouble() * inv,
-            w.toDouble() * inv
-        )
-    }
-
-    operator fun <R: Number> times(other: Quaternion<R>): QuaternionD {
-        val ax = x.toDouble()
-        val ay = y.toDouble()
-        val az = z.toDouble()
-        val aw = w.toDouble()
-        val bx = other.x.toDouble()
-        val by = other.y.toDouble()
-        val bz = other.z.toDouble()
-        val bw = other.w.toDouble()
-        return Quaternion(
-            aw * bx + ax * bw + ay * bz - az * by,
-            aw * by - ax * bz + ay * bw + az * bx,
-            aw * bz + ax * by - ay * bx + az * bw,
-            aw * bw - ax * bx - ay * by - az * bz
-        )
-    }
-
-    fun inverse(): QuaternionD {
-        val lenSq = lengthSquared()
-        if (lenSq == 0.0) {
-            return identity()
-        }
-        val conjugate = conjugate()
-        val inv = 1.0 / lenSq
-        return Quaternion(
-            conjugate.x * inv,
-            conjugate.y * inv,
-            conjugate.z * inv,
-            conjugate.w * inv
-        )
-    }
+        @JvmStatic
+        fun identity(): Quaternion<Double> = Quaternion(0.0, 0.0, 0.0, 1.0)
 
-    fun rotate(vector: Vector3<T>): Vector3<Double> {
-        val doubleVector = vector.asDoubleVector()
-        val vectorQuat = Quaternion(doubleVector.x, doubleVector.y, doubleVector.z, 0.0)
-        val rotated = (this * vectorQuat) * conjugate()
-        return Vector3(rotated.x, rotated.y, rotated.z)
-    }
-
-    operator fun times(vector: Vector3<T>): Vector3<Double> {
-        return rotate(vector)
-    }
-
-    fun nlerp(other: Quaternion<*>, t: Double): QuaternionD {
-        val alpha = t.coerceIn(0.0, 1.0)
-        val inverse = 1.0 - alpha
-        return Quaternion(
-            x.toDouble() * inverse + other.x.toDouble() * alpha,
-            y.toDouble() * inverse + other.y.toDouble() * alpha,
-            z.toDouble() * inverse + other.z.toDouble() * alpha,
-            w.toDouble() * inverse + other.w.toDouble() * alpha
-        ).normalized()
-    }
-
-    fun slerp(other: Quaternion<*>, t: Double): QuaternionD {
-        val alpha = t.coerceIn(0.0, 1.0)
-        var q1 = normalized()
-        var q2 = Quaternion(
-            other.x.toDouble(),
-            other.y.toDouble(),
-            other.z.toDouble(),
-            other.w.toDouble()
-        ).normalized()
-
-        var dot = q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w
-        if (dot < 0.0) {
-            dot = -dot
-            q2 = Quaternion(-q2.x, -q2.y, -q2.z, -q2.w)
-        }
-
-        dot = min(1.0, max(-1.0, dot))
-
-        if (dot > 0.9995) {
-            return Quaternion(
-                q1.x + alpha * (q2.x - q1.x),
-                q1.y + alpha * (q2.y - q1.y),
-                q1.z + alpha * (q2.z - q1.z),
-                q1.w + alpha * (q2.w - q1.w)
-            ).normalized()
+        @JvmStatic
+        fun fromEulerAngles(pitch: Double, yaw: Double, roll: Double): Quaternion<Double> {
+            return Quaterniond.fromEulerAngles(pitch, yaw, roll).toGeneric()
         }
-
-        val theta0 = acos(dot)
-        val sinTheta0 = sin(theta0)
-        val theta = theta0 * alpha
-        val sinTheta = sin(theta)
-        val s0 = cos(theta) - dot * sinTheta / sinTheta0
-        val s1 = sinTheta / sinTheta0
-
-        return Quaternion(
-            q1.x * s0 + q2.x * s1,
-            q1.y * s0 + q2.y * s1,
-            q1.z * s0 + q2.z * s1,
-            q1.w * s0 + q2.w * s1
-        ).normalized()
     }
 
-    fun toEulerAngles(): Vector3D {
-        val q = normalized()
-        val xx = q.x * q.x
-        val yy = q.y * q.y
-        val zz = q.z * q.z
-
-        val sinPitch = 2.0 * (q.w * q.x - q.y * q.z)
-        val pitch = asin(max(-1.0, min(1.0, sinPitch)))
-
-        val sinYaw = 2.0 * (q.w * q.y + q.z * q.x)
-        val cosYaw = 1.0 - 2.0 * (xx + zz)
-        val yaw = atan2(sinYaw, cosYaw)
+    fun asQuaterniond() = Quaterniond(x.toDouble(), y.toDouble(), z.toDouble(), w.toDouble())
+    fun asQuaternionf() = Quaternionf(x.toFloat(), y.toFloat(), z.toFloat(), w.toFloat())
 
-        val sinRoll = 2.0 * (q.w * q.z + q.x * q.y)
-        val cosRoll = 1.0 - 2.0 * (yy + xx)
-        val roll = atan2(sinRoll, cosRoll)
+    fun normalize(): Quaternion<Double> = asQuaterniond().normalize().toGeneric()
+    fun inverse(): Quaternion<Double> = asQuaterniond().inverse().toGeneric()
 
-        return Vector3D(pitch, yaw, roll)
-    }
-
-    fun toAxisAngle(): Pair<Vector3D, Double> {
-        val q = normalized()
-        val angle = 2.0 * acos(q.w)
-        val sinHalfAngle = sqrt(1.0 - q.w * q.w)
-        if (sinHalfAngle < 1e-6) {
-            return Vector3D(1.0, 0.0, 0.0) to angle
-        }
-        return Vector3D(q.x / sinHalfAngle, q.y / sinHalfAngle, q.z / sinHalfAngle) to angle
-    }
+    override fun toString() = "Quaternion($x, $y, $z, $w)"
 
-    fun toVector4(): Vector4<Double> {
-        return Vector4(x.toDouble(), y.toDouble(), z.toDouble(), w.toDouble())
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Quaternion<*>) return false
+        return x == other.x && y == other.y && z == other.z && w == other.w
     }
 
-    override fun toString(): String {
-        return "Quaternion(x=$x, y=$y, z=$z, w=$w)"
+    override fun hashCode(): Int {
+        var result = x.hashCode()
+        result = 31 * result + y.hashCode()
+        result = 31 * result + z.hashCode()
+        result = 31 * result + w.hashCode()
+        return result
     }
 }
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/math/Quaterniond.kt b/src/commonMain/kotlin/com/dropbear/math/Quaterniond.kt
new file mode 100644
index 0000000..3fcab97
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/math/Quaterniond.kt
@@ -0,0 +1,183 @@
+package com.dropbear.math
+
+import kotlin.jvm.JvmField
+import kotlin.jvm.JvmStatic
+import kotlin.math.*
+
+class Quaterniond(
+    @JvmField var x: Double,
+    @JvmField var y: Double,
+    @JvmField var z: Double,
+    @JvmField var w: Double
+) {
+    companion object {
+        @JvmStatic fun identity() = Quaterniond(0.0, 0.0, 0.0, 1.0)
+
+        @JvmStatic
+        fun fromEulerAngles(pitch: Double, yaw: Double, roll: Double): Quaterniond {
+            val halfPitch = pitch * 0.5
+            val halfYaw = yaw * 0.5
+            val halfRoll = roll * 0.5
+            val sp = sin(halfPitch)
+            val cp = cos(halfPitch)
+            val sy = sin(halfYaw)
+            val cy = cos(halfYaw)
+            val sr = sin(halfRoll)
+            val cr = cos(halfRoll)
+
+            return Quaterniond(
+                x = cy * sr * cp - sy * cr * sp,
+                y = sy * cr * cp + cy * sr * sp,
+                z = sy * sr * cp - cy * cr * sp,
+                w = cy * cr * cp + sy * sr * sp
+            )
+        }
+
+        @JvmStatic
+        fun fromAxisAngle(axis: Vector3d, angleRadians: Double): Quaterniond {
+            val normalizedAxis = axis.normalize()
+            val halfAngle = angleRadians * 0.5
+            val sinHalf = sin(halfAngle)
+            return Quaterniond(
+                normalizedAxis.x * sinHalf,
+                normalizedAxis.y * sinHalf,
+                normalizedAxis.z * sinHalf,
+                cos(halfAngle)
+            )
+        }
+
+        @JvmStatic fun rotateX(angle: Double) = fromAxisAngle(Vector3d(1.0, 0.0, 0.0), angle)
+        @JvmStatic fun rotateY(angle: Double) = fromAxisAngle(Vector3d(0.0, 1.0, 0.0), angle)
+        @JvmStatic fun rotateZ(angle: Double) = fromAxisAngle(Vector3d(0.0, 0.0, 1.0), angle)
+
+        @JvmStatic
+        fun fromToRotation(from: Vector3d, to: Vector3d): Quaterniond {
+            val start = from.normalize()
+            val end = to.normalize()
+            val dot = start.dot(end)
+
+            if (dot >= 1.0 - 1e-6) return identity()
+            if (dot <= -1.0 + 1e-6) {
+                val orthogonal = if (abs(start.x) < 0.9) Vector3d(1.0, 0.0, 0.0) else Vector3d(0.0, 1.0, 0.0)
+                val axis = start.cross(orthogonal).normalize()
+                return fromAxisAngle(axis, PI)
+            }
+            val axis = start.cross(end)
+            val angle = acos(dot.coerceIn(-1.0, 1.0))
+            return fromAxisAngle(axis, angle)
+        }
+
+        private fun copySign(magnitude: Double, sign: Double): Double {
+            val absMag = abs(magnitude)
+            return if (sign < 0.0) -absMag else absMag
+        }
+    }
+
+    operator fun plus(other: Quaterniond) = Quaterniond(x + other.x, y + other.y, z + other.z, w + other.w)
+    operator fun minus(other: Quaterniond) = Quaterniond(x - other.x, y - other.y, z - other.z, w - other.w)
+    operator fun unaryMinus() = Quaterniond(-x, -y, -z, -w)
+
+    operator fun times(scalar: Double) = Quaterniond(x * scalar, y * scalar, z * scalar, w * scalar)
+    operator fun div(scalar: Double) = Quaterniond(x / scalar, y / scalar, z / scalar, w / scalar)
+
+    /**
+     * Quaternion Multiplication (Composition).
+     * Represents applying rotation `other`, then rotation `this`.
+     */
+    operator fun times(other: Quaterniond): Quaterniond {
+        return Quaterniond(
+            w * other.x + x * other.w + y * other.z - z * other.y,
+            w * other.y - x * other.z + y * other.w + z * other.x,
+            w * other.z + x * other.y - y * other.x + z * other.w,
+            w * other.w - x * other.x - y * other.y - z * other.z
+        )
+    }
+
+    /**
+     * Rotates a vector by this quaternion.
+     */
+    operator fun times(vector: Vector3d): Vector3d {
+        val qVec = Vector3d(x, y, z)
+        val uv = qVec.cross(vector)
+        val uuv = qVec.cross(uv)
+        return vector + ((uv * w) + uuv) * 2.0
+    }
+
+    fun dot(other: Quaterniond) = x * other.x + y * other.y + z * other.z + w * other.w
+    fun lengthSquared() = x * x + y * y + z * z + w * w
+    fun length() = sqrt(lengthSquared())
+
+    fun normalize(): Quaterniond {
+        val len = length()
+        return if (len == 0.0) identity() else this / len
+    }
+
+    fun inverse(): Quaterniond {
+        val lenSq = lengthSquared()
+        if (lenSq == 0.0) return identity()
+        val inv = 1.0 / lenSq
+        return Quaterniond(-x * inv, -y * inv, -z * inv, w * inv)
+    }
+
+    fun conjugate() = Quaterniond(-x, -y, -z, w)
+
+    fun slerp(other: Quaterniond, t: Double): Quaterniond {
+        val alpha = t.coerceIn(0.0, 1.0)
+        var q1 = this.normalize()
+        var q2 = other.normalize()
+
+        var dot = q1.dot(q2)
+
+        if (dot < 0.0) {
+            dot = -dot
+            q2 = -q2
+        }
+
+        if (dot > 0.9995) {
+            return (q1 + (q2 - q1) * alpha).normalize()
+        }
+
+        val theta0 = acos(dot)
+        val theta = theta0 * alpha
+        val sinTheta = sin(theta)
+        val sinTheta0 = sin(theta0)
+
+        val s0 = cos(theta) - dot * sinTheta / sinTheta0
+        val s1 = sinTheta / sinTheta0
+
+        return (q1 * s0) + (q2 * s1)
+    }
+
+    fun toEulerAngles(): Vector3d {
+        val sinr_cosp = 2.0 * (w * z + x * y)
+        val cosr_cosp = 1.0 - 2.0 * (y * y + z * z)
+        val roll = atan2(sinr_cosp, cosr_cosp)
+
+        val sinp = 2.0 * (w * x - y * z)
+        val pitch: Double
+        if (abs(sinp) >= 1.0) {
+            pitch = copySign(PI / 2, sinp)
+        } else {
+            pitch = asin(sinp)
+        }
+
+        val siny_cosp = 2.0 * (w * y + z * x)
+        val cosy_cosp = 1.0 - 2.0 * (x * x + y * y)
+        val yaw = atan2(siny_cosp, cosy_cosp)
+
+        return Vector3d(pitch, yaw, roll)
+    }
+
+    fun toFloat() = Quaternionf(x.toFloat(), y.toFloat(), z.toFloat(), w.toFloat())
+    fun toGeneric() = Quaternion(x, y, z, w)
+
+    override fun toString() = "Quaterniond($x, $y, $z, $w)"
+    override fun equals(other: Any?) = other is Quaterniond && x == other.x && y == other.y && z == other.z && w == other.w
+    override fun hashCode(): Int {
+        var result = x.hashCode()
+        result = 31 * result + y.hashCode()
+        result = 31 * result + z.hashCode()
+        result = 31 * result + w.hashCode()
+        return result
+    }
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/math/Quaternionf.kt b/src/commonMain/kotlin/com/dropbear/math/Quaternionf.kt
new file mode 100644
index 0000000..0999ca1
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/math/Quaternionf.kt
@@ -0,0 +1,122 @@
+package com.dropbear.math
+
+import kotlin.jvm.JvmField
+import kotlin.jvm.JvmStatic
+import kotlin.math.*
+
+class Quaternionf(
+    @JvmField var x: Float,
+    @JvmField var y: Float,
+    @JvmField var z: Float,
+    @JvmField var w: Float
+) {
+    companion object {
+        @JvmStatic fun identity() = Quaternionf(0f, 0f, 0f, 1f)
+
+        @JvmStatic
+        fun fromEulerAngles(pitch: Float, yaw: Float, roll: Float): Quaternionf {
+            val halfPitch = pitch * 0.5f
+            val halfYaw = yaw * 0.5f
+            val halfRoll = roll * 0.5f
+            val sp = sin(halfPitch)
+            val cp = cos(halfPitch)
+            val sy = sin(halfYaw)
+            val cy = cos(halfYaw)
+            val sr = sin(halfRoll)
+            val cr = cos(halfRoll)
+
+            return Quaternionf(
+                x = cy * sr * cp - sy * cr * sp,
+                y = sy * cr * cp + cy * sr * sp,
+                z = sy * sr * cp - cy * cr * sp,
+                w = cy * cr * cp + sy * sr * sp
+            )
+        }
+
+        @JvmStatic
+        fun fromAxisAngle(axis: Vector3f, angleRadians: Float): Quaternionf {
+            val normalizedAxis = axis.normalize()
+            val halfAngle = angleRadians * 0.5f
+            val sinHalf = sin(halfAngle)
+            return Quaternionf(
+                normalizedAxis.x * sinHalf,
+                normalizedAxis.y * sinHalf,
+                normalizedAxis.z * sinHalf,
+                cos(halfAngle)
+            )
+        }
+
+        @JvmStatic fun rotateX(angle: Float) = fromAxisAngle(Vector3f(1f, 0f, 0f), angle)
+        @JvmStatic fun rotateY(angle: Float) = fromAxisAngle(Vector3f(0f, 1f, 0f), angle)
+        @JvmStatic fun rotateZ(angle: Float) = fromAxisAngle(Vector3f(0f, 0f, 1f), angle)
+
+        private fun copySign(magnitude: Float, sign: Float): Float {
+            val absMag = abs(magnitude)
+            return if (sign < 0f) -absMag else absMag
+        }
+    }
+
+    operator fun plus(other: Quaternionf) = Quaternionf(x + other.x, y + other.y, z + other.z, w + other.w)
+    operator fun minus(other: Quaternionf) = Quaternionf(x - other.x, y - other.y, z - other.z, w - other.w)
+    operator fun unaryMinus() = Quaternionf(-x, -y, -z, -w)
+
+    operator fun times(scalar: Float) = Quaternionf(x * scalar, y * scalar, z * scalar, w * scalar)
+    operator fun div(scalar: Float) = Quaternionf(x / scalar, y / scalar, z / scalar, w / scalar)
+
+    operator fun times(other: Quaternionf): Quaternionf {
+        return Quaternionf(
+            w * other.x + x * other.w + y * other.z - z * other.y,
+            w * other.y - x * other.z + y * other.w + z * other.x,
+            w * other.z + x * other.y - y * other.x + z * other.w,
+            w * other.w - x * other.x - y * other.y - z * other.z
+        )
+    }
+
+    operator fun times(vector: Vector3f): Vector3f {
+        val qVec = Vector3f(x, y, z)
+        val uv = qVec.cross(vector)
+        val uuv = qVec.cross(uv)
+        return vector + ((uv * w) + uuv) * 2f
+    }
+
+    fun dot(other: Quaternionf) = x * other.x + y * other.y + z * other.z + w * other.w
+    fun lengthSquared() = x * x + y * y + z * z + w * w
+    fun length() = sqrt(lengthSquared())
+
+    fun normalize(): Quaternionf {
+        val len = length()
+        return if (len == 0f) identity() else this / len
+    }
+
+    fun inverse(): Quaternionf {
+        val lenSq = lengthSquared()
+        if (lenSq == 0f) return identity()
+        val inv = 1.0f / lenSq
+        return Quaternionf(-x * inv, -y * inv, -z * inv, w * inv)
+    }
+
+    fun conjugate() = Quaternionf(-x, -y, -z, w)
+
+    fun toDouble() = Quaterniond(x.toDouble(), y.toDouble(), z.toDouble(), w.toDouble())
+
+    fun toEulerAngles(): Vector3f {
+        val sinr_cosp = 2f * (w * z + x * y)
+        val cosr_cosp = 1f - 2f * (y * y + z * z)
+        val roll = atan2(sinr_cosp, cosr_cosp)
+
+        val sinp = 2f * (w * x - y * z)
+        val pitch = if (abs(sinp) >= 1f) {
+            copySign(PI.toFloat() / 2f, sinp)
+        } else {
+            asin(sinp)
+        }
+
+        val siny_cosp = 2f * (w * y + z * x)
+        val cosy_cosp = 1f - 2f * (x * x + y * y)
+        val yaw = atan2(siny_cosp, cosy_cosp)
+
+        return Vector3f(pitch, yaw, roll)
+    }
+
+    override fun toString() = "Quaternionf($x, $y, $z, $w)"
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/math/Transform.kt b/src/commonMain/kotlin/com/dropbear/math/Transform.kt
index e055df1..5cb0d7c 100644
--- a/src/commonMain/kotlin/com/dropbear/math/Transform.kt
+++ b/src/commonMain/kotlin/com/dropbear/math/Transform.kt
@@ -5,10 +5,20 @@ package com.dropbear.math
  * attached to an entity. 
  */
 class Transform(
-    var position: Vector3D,
-    var rotation: QuaternionD,
-    var scale: Vector3D
+    var position: Vector3d,
+    var rotation: Quaterniond,
+    var scale: Vector3d
 ) {
+    companion object {
+        fun identity(): Transform {
+            return Transform(
+                Vector3d.zero(),
+                Quaterniond.identity(),
+                Vector3d.one(),
+            )
+        }
+    }
+
     /**
      * Specific constructor for the individual raw primitive values.
      *
@@ -18,9 +28,9 @@ class Transform(
                 rx: Double, ry: Double, rz: Double, rw: Double,
                 sx: Double, sy: Double, sz: Double)
             : this(
-        Vector3D(px, py, pz),
-        QuaternionD(rx, ry, rz, rw),
-        Vector3D(sx, sy, sz)
+        Vector3d(px, py, pz),
+        Quaterniond(rx, ry, rz, rw),
+        Vector3d(sx, sy, sz)
             )
 
     override fun toString(): String {
diff --git a/src/commonMain/kotlin/com/dropbear/math/Vector.kt b/src/commonMain/kotlin/com/dropbear/math/Vector.kt
deleted file mode 100644
index 9674fc3..0000000
--- a/src/commonMain/kotlin/com/dropbear/math/Vector.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.dropbear.math
-
-/**
- * Abstract class all Vectors inherit.
- */
-abstract class Vector<T: Number, SELF: Vector<T, SELF>> {
-    abstract fun normalize(): SELF
-
-    abstract operator fun plus(other: SELF): SELF
-    abstract operator fun plus(scalar: T): SELF
-
-    abstract operator fun minus(other: SELF): SELF
-    abstract operator fun minus(scalar: T): SELF
-
-    abstract operator fun times(other: SELF): SELF
-    abstract operator fun times(scalar: T): SELF
-
-    abstract operator fun div(other: SELF): SELF
-    abstract operator fun div(scalar: T): SELF
-
-    abstract fun length(): Double
-
-    abstract fun copy(): SELF
-}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/math/Vector2.kt b/src/commonMain/kotlin/com/dropbear/math/Vector2.kt
index 1fd31eb..b433269 100644
--- a/src/commonMain/kotlin/com/dropbear/math/Vector2.kt
+++ b/src/commonMain/kotlin/com/dropbear/math/Vector2.kt
@@ -1,180 +1,32 @@
 package com.dropbear.math
 
 import kotlin.jvm.JvmField
-import kotlin.math.sqrt
+import kotlin.jvm.JvmStatic
 
 /**
- * A class for holding a vector of `2` values of the same type.
+ * A Generic Vector2.
+ * WARNING: Uses boxing. Prefer [Vector2d]/[Vector2f]/[Vector2i] for performance.
  */
-class Vector2<T: Number>(
+class Vector2<T : Number>(
     @JvmField var x: T,
-    @JvmField var y: T,
-) : Vector<T, Vector2<T>>() {
+    @JvmField var y: T
+) {
     companion object {
-        /**
-         * Creates a new [com.dropbear.math.Vector2] of type `T` with one value.
-         */
-        fun <T: Number> uniform(value: T): Vector2<T> {
-            return Vector2(value, value)
-        }
-
-        /**
-         * Creates a [com.dropbear.math.Vector2] of type [Double] filled with only zeroes
-         */
-        fun zero(): Vector2<Double> {
-            return Vector2(0.0, 0.0)
-        }
-    }
-
-    fun asDoubleVector(): Vector2<Double> {
-        return Vector2(this.x.toDouble(), this.y.toDouble())
-    }
-
-    override fun normalize(): Vector2<T> {
-        val length = length()
-        if (length > 0.0) {
-            val invLength = 1.0 / length
-            @Suppress("UNCHECKED_CAST")
-            x = (x.toDouble() * invLength) as T
-            @Suppress("UNCHECKED_CAST")
-            y = (y.toDouble() * invLength) as T
-        }
-        return this
-    }
-
-    override operator fun plus(other: Vector2<T>): Vector2<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector2(
-            x.toDouble() + other.x.toDouble(),
-            y.toDouble() + other.y.toDouble()
-        ) as Vector2<T>
-    }
-
-    override operator fun plus(scalar: T): Vector2<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector2(
-            x.toDouble() + scalar.toDouble(),
-            y.toDouble() + scalar.toDouble()
-        ) as Vector2<T>
-    }
-
-    override operator fun minus(other: Vector2<T>): Vector2<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector2(
-            x.toDouble() - other.x.toDouble(),
-            y.toDouble() - other.y.toDouble()
-        ) as Vector2<T>
-    }
-
-    override operator fun minus(scalar: T): Vector2<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector2(
-            x.toDouble() - scalar.toDouble(),
-            y.toDouble() - scalar.toDouble()
-        ) as Vector2<T>
-    }
-
-    override operator fun times(other: Vector2<T>): Vector2<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector2(
-            x.toDouble() * other.x.toDouble(),
-            y.toDouble() * other.y.toDouble()
-        ) as Vector2<T>
-    }
-
-    override operator fun times(scalar: T): Vector2<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector2(
-            x.toDouble() * scalar.toDouble(),
-            y.toDouble() * scalar.toDouble()
-        ) as Vector2<T>
-    }
-
-    override operator fun div(other: Vector2<T>): Vector2<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector2(
-            x.toDouble() / other.x.toDouble(),
-            y.toDouble() / other.y.toDouble()
-        ) as Vector2<T>
-    }
-
-    override operator fun div(scalar: T): Vector2<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector2(
-            x.toDouble() / scalar.toDouble(),
-            y.toDouble() / scalar.toDouble(),
-        ) as Vector2<T>
-    }
-
-    fun lengthSquared(): Double {
-        val dx = x.toDouble()
-        val dy = y.toDouble()
-        return dx * dx + dy * dy
-    }
-
-    fun dot(other: Vector2<T>): Double {
-        return x.toDouble() * other.x.toDouble() + y.toDouble() * other.y.toDouble()
-    }
-
-    fun distanceTo(other: Vector2<T>): Double {
-        val dx = x.toDouble() - other.x.toDouble()
-        val dy = y.toDouble() - other.y.toDouble()
-        return sqrt(dx * dx + dy * dy)
-    }
-
-    fun normalizedCopy(): Vector2<Double> {
-        val length = length()
-        if (length == 0.0) {
-            return zero()
-        }
-        val invLength = 1.0 / length
-        return Vector2(
-            x.toDouble() * invLength,
-            y.toDouble() * invLength
-        )
-    }
-
-    fun lerp(target: Vector2<T>, alpha: Double): Vector2<Double> {
-        val inverse = 1.0 - alpha
-        return Vector2(
-            x.toDouble() * inverse + target.x.toDouble() * alpha,
-            y.toDouble() * inverse + target.y.toDouble() * alpha
-        )
-    }
-
-    operator fun unaryMinus(): Vector2<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector2(-x.toDouble(), -y.toDouble()) as Vector2<T>
-    }
-
-    fun toVector3(z: T): Vector3<T> {
-        return Vector3(x, y, z)
+        @JvmStatic
+        fun fromDoubles(x: Double, y: Double): Vector2<Double> = Vector2(x, y)
     }
 
-    fun toVector4(z: T, w: T): Vector4<T> {
-        return Vector4(x, y, z, w)
-    }
-
-    operator fun component1(): T = x
-    operator fun component2(): T = y
+    fun asVector2d(): Vector2d = Vector2d(x.toDouble(), y.toDouble())
+    fun asVector2f(): Vector2f = Vector2f(x.toFloat(), y.toFloat())
+    fun asVector2i(): Vector2i = Vector2i(x.toInt(), y.toInt())
 
-    /**
-     * Returns the magnitude/length of the vector
-     *
-     * ## Math
-     * `sqrt(x^2 + y^2)`
-     */
-    override fun length(): Double {
-        return sqrt(x.toDouble() * x.toDouble() + y.toDouble() * y.toDouble())
-    }
-
-    override fun copy(): Vector2<T> {
-        return Vector2(x, y)
-    }
+    override fun toString() = "Vector2($x, $y)"
 
-    override fun toString(): String {
-        return "Vector2(x=$x, y=$y)"
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Vector2<*>) return false
+        return x == other.x && y == other.y
     }
-}
 
-typealias Vector2D = Vector2<Double>
\ No newline at end of file
+    override fun hashCode() = 31 * x.hashCode() + y.hashCode()
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/math/Vector2d.kt b/src/commonMain/kotlin/com/dropbear/math/Vector2d.kt
new file mode 100644
index 0000000..d1d9e10
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/math/Vector2d.kt
@@ -0,0 +1,62 @@
+package com.dropbear.math
+
+import kotlin.math.sqrt
+import kotlin.jvm.JvmField
+import kotlin.jvm.JvmStatic
+
+/**
+ * A class for holding a vector of `2` double values.
+ */
+class Vector2d(
+    @JvmField var x: Double,
+    @JvmField var y: Double
+) {
+    companion object {
+        @JvmStatic fun zero() = Vector2d(0.0, 0.0)
+        @JvmStatic fun one() = Vector2d(1.0, 1.0)
+    }
+
+    operator fun plus(other: Vector2d) = Vector2d(x + other.x, y + other.y)
+    operator fun plus(scalar: Double) = Vector2d(x + scalar, y + scalar)
+
+    operator fun minus(other: Vector2d) = Vector2d(x - other.x, y - other.y)
+    operator fun minus(scalar: Double) = Vector2d(x - scalar, y - scalar)
+
+    operator fun times(other: Vector2d) = Vector2d(x * other.x, y * other.y)
+    operator fun times(scalar: Double) = Vector2d(x * scalar, y * scalar)
+
+    operator fun div(other: Vector2d) = Vector2d(x / other.x, y / other.y)
+    operator fun div(scalar: Double) = Vector2d(x / scalar, y / scalar)
+
+    operator fun unaryMinus() = Vector2d(-x, -y)
+
+    fun length() = sqrt(x * x + y * y)
+    fun lengthSquared() = x * x + y * y
+
+    fun dot(other: Vector2d) = x * other.x + y * other.y
+
+    fun distanceTo(other: Vector2d): Double {
+        val dx = x - other.x
+        val dy = y - other.y
+        return sqrt(dx * dx + dy * dy)
+    }
+
+    fun normalize(): Vector2d {
+        val l = length()
+        return if (l != 0.0) this / l else zero()
+    }
+
+    fun lerp(target: Vector2d, alpha: Double): Vector2d {
+        val inv = 1.0 - alpha
+        return Vector2d(x * inv + target.x * alpha, y * inv + target.y * alpha)
+    }
+
+    fun toFloat() = Vector2f(x.toFloat(), y.toFloat())
+    fun toInt() = Vector2i(x.toInt(), y.toInt())
+
+    fun toGeneric() = Vector2(x, y)
+
+    override fun toString() = "Vector2d($x, $y)"
+    override fun equals(other: Any?) = other is Vector2d && x == other.x && y == other.y
+    override fun hashCode() = 31 * x.hashCode() + y.hashCode()
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/math/Vector2f.kt b/src/commonMain/kotlin/com/dropbear/math/Vector2f.kt
new file mode 100644
index 0000000..a203865
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/math/Vector2f.kt
@@ -0,0 +1,46 @@
+package com.dropbear.math
+
+import kotlin.math.sqrt
+import kotlin.jvm.JvmField
+import kotlin.jvm.JvmStatic
+
+/**
+ * A class for holding a vector of `2` float values.
+ */
+class Vector2f(
+    @JvmField var x: Float,
+    @JvmField var y: Float
+) {
+    companion object {
+        @JvmStatic
+        fun zero() = Vector2f(0f, 0f)
+    }
+
+    operator fun plus(other: Vector2f) = Vector2f(x + other.x, y + other.y)
+    operator fun plus(scalar: Float) = Vector2f(x + scalar, y + scalar)
+
+    operator fun minus(other: Vector2f) = Vector2f(x - other.x, y - other.y)
+    operator fun minus(scalar: Float) = Vector2f(x - scalar, y - scalar)
+
+    operator fun times(other: Vector2f) = Vector2f(x * other.x, y * other.y)
+    operator fun times(scalar: Float) = Vector2f(x * scalar, y * scalar)
+
+    operator fun div(other: Vector2f) = Vector2f(x / other.x, y / other.y)
+    operator fun div(scalar: Float) = Vector2f(x / scalar, y / scalar)
+
+    operator fun unaryMinus() = Vector2f(-x, -y)
+
+    fun length(): Float = sqrt(x * x + y * y)
+    fun lengthSquared(): Float = x * x + y * y
+
+    fun normalize(): Vector2f {
+        val l = length()
+        return if (l != 0f) Vector2f(x / l, y / l) else zero()
+    }
+
+    // --- Conversions ---
+    fun toDouble() = Vector2d(x.toDouble(), y.toDouble())
+    fun toInt() = Vector2i(x.toInt(), y.toInt())
+
+    override fun toString() = "Vector2f($x, $y)"
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/math/Vector2i.kt b/src/commonMain/kotlin/com/dropbear/math/Vector2i.kt
new file mode 100644
index 0000000..0b8e9db
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/math/Vector2i.kt
@@ -0,0 +1,32 @@
+package com.dropbear.math
+
+import kotlin.math.sqrt
+import kotlin.jvm.JvmField
+import kotlin.jvm.JvmStatic
+
+/**
+ * A class for holding a vector of `2` integer values.
+ */
+class Vector2i(
+    @JvmField var x: Int,
+    @JvmField var y: Int
+) {
+    companion object {
+        @JvmStatic
+        fun zero() = Vector2i(0, 0)
+    }
+
+    operator fun plus(other: Vector2i) = Vector2i(x + other.x, y + other.y)
+    operator fun minus(other: Vector2i) = Vector2i(x - other.x, y - other.y)
+
+    operator fun times(scalar: Int) = Vector2i(x * scalar, y * scalar)
+
+    operator fun div(scalar: Int) = Vector2i(x / scalar, y / scalar)
+
+    fun length(): Double = sqrt((x * x + y * y).toDouble())
+
+    fun toFloat() = Vector2f(x.toFloat(), y.toFloat())
+    fun toDouble() = Vector2d(x.toDouble(), y.toDouble())
+
+    override fun toString() = "Vector2i($x, $y)"
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/math/Vector3.kt b/src/commonMain/kotlin/com/dropbear/math/Vector3.kt
index 5c50af3..706e75d 100644
--- a/src/commonMain/kotlin/com/dropbear/math/Vector3.kt
+++ b/src/commonMain/kotlin/com/dropbear/math/Vector3.kt
@@ -1,227 +1,41 @@
 package com.dropbear.math
 
 import kotlin.jvm.JvmField
-import kotlin.math.sqrt
+import kotlin.jvm.JvmStatic
 
 /**
- * A class for holding a vector of `3` values of the same type.
+ * A Generic Vector3.
+ * WARNING: Uses boxing. Prefer [Vector3d]/[Vector3f]/[Vector3i] for performance.
  */
-class Vector3<T: Number>(
+class Vector3<T : Number>(
     @JvmField var x: T,
     @JvmField var y: T,
-    @JvmField var z: T,
-) : Vector<T, Vector3<T>>() {
+    @JvmField var z: T
+) {
     companion object {
-        /**
-         * Creates a new [com.dropbear.math.Vector3] of type `T` with one value.
-         */
-        fun <T: Number> uniform(value: T): Vector3<T> {
-            return Vector3(value, value, value)
-        }
+        @JvmStatic
+        fun fromDoubles(x: Double, y: Double, z: Double): Vector3<Double> = Vector3(x, y, z)
 
-        /**
-         * Creates a [com.dropbear.math.Vector3] of type [Double] filled with only zeroes
-         */
-        fun zero(): Vector3<Double> {
-            return Vector3(0.0, 0.0, 0.0)
-        }
-
-        fun x(): Vector3D {
-            return Vector3D(1.0, 0.0, 0.0)
-        }
-
-        fun y(): Vector3D {
-            return Vector3D(0.0, 1.0, 0.0)
-        }
-
-        fun z(): Vector3D {
-            return Vector3D(0.0, 0.0, 1.0)
-        }
-    }
-
-    /**
-     * Returns the [Vector3] to a [Vector3D] (Vector3 of type `Double`)
-     */
-    fun asDoubleVector(): Vector3<Double> {
-        return Vector3(this.x.toDouble(), this.y.toDouble(), this.z.toDouble())
-    }
-
-    override fun normalize(): Vector3<T> {
-        val length = length()
-        if (length > 0.0) {
-            val invLength = 1.0 / length
-            @Suppress("UNCHECKED_CAST")
-            x = (x.toDouble() * invLength) as T
-            @Suppress("UNCHECKED_CAST")
-            y = (y.toDouble() * invLength) as T
-            @Suppress("UNCHECKED_CAST")
-            z = (z.toDouble() * invLength) as T
-        }
-        return this
-    }
-
-    override operator fun plus(other: Vector3<T>): Vector3<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector3(
-            x.toDouble() + other.x.toDouble(),
-            y.toDouble() + other.y.toDouble(),
-            z.toDouble() + other.z.toDouble()
-        ) as Vector3<T>
-    }
-
-    override operator fun plus(scalar: T): Vector3<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector3(
-            x.toDouble() + scalar.toDouble(),
-            y.toDouble() + scalar.toDouble(),
-            z.toDouble() + scalar.toDouble()
-        ) as Vector3<T>
+        @JvmStatic
+        fun zero(): Vector3<Double> = Vector3(0.0, 0.0, 0.0)
     }
 
-    override operator fun minus(other: Vector3<T>): Vector3<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector3(
-            x.toDouble() - other.x.toDouble(),
-            y.toDouble() - other.y.toDouble(),
-            z.toDouble() - other.z.toDouble()
-        ) as Vector3<T>
-    }
-
-    override operator fun minus(scalar: T): Vector3<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector3(
-            x.toDouble() - scalar.toDouble(),
-            y.toDouble() - scalar.toDouble(),
-            z.toDouble() - scalar.toDouble()
-        ) as Vector3<T>
-    }
+    fun asVector3d(): Vector3d = Vector3d(x.toDouble(), y.toDouble(), z.toDouble())
+    fun asVector3f(): Vector3f = Vector3f(x.toFloat(), y.toFloat(), z.toFloat())
+    fun asVector3i(): Vector3i = Vector3i(x.toInt(), y.toInt(), z.toInt())
 
-    override operator fun times(other: Vector3<T>): Vector3<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector3(
-            x.toDouble() * other.x.toDouble(),
-            y.toDouble() * other.y.toDouble(),
-            z.toDouble() * other.z.toDouble()
-        ) as Vector3<T>
-    }
-
-    override operator fun times(scalar: T): Vector3<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector3(
-            x.toDouble() * scalar.toDouble(),
-            y.toDouble() * scalar.toDouble(),
-            z.toDouble() * scalar.toDouble()
-        ) as Vector3<T>
-    }
+    override fun toString() = "Vector3($x, $y, $z)"
 
-    override operator fun div(other: Vector3<T>): Vector3<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector3(
-            x.toDouble() / other.x.toDouble(),
-            y.toDouble() / other.y.toDouble(),
-            z.toDouble() / other.z.toDouble()
-        ) as Vector3<T>
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Vector3<*>) return false
+        return x == other.x && y == other.y && z == other.z
     }
 
-    override operator fun div(scalar: T): Vector3<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector3(
-            x.toDouble() / scalar.toDouble(),
-            y.toDouble() / scalar.toDouble(),
-            z.toDouble() / scalar.toDouble()
-        ) as Vector3<T>
+    override fun hashCode(): Int {
+        var result = x.hashCode()
+        result = 31 * result + y.hashCode()
+        result = 31 * result + z.hashCode()
+        return result
     }
-
-    fun lengthSquared(): Double {
-        val dx = x.toDouble()
-        val dy = y.toDouble()
-        val dz = z.toDouble()
-        return dx * dx + dy * dy + dz * dz
-    }
-
-    fun dot(other: Vector3<T>): Double {
-        return x.toDouble() * other.x.toDouble() +
-                y.toDouble() * other.y.toDouble() +
-                z.toDouble() * other.z.toDouble()
-    }
-
-    fun cross(other: Vector3<T>): Vector3<Double> {
-        val ax = x.toDouble()
-        val ay = y.toDouble()
-        val az = z.toDouble()
-        val bx = other.x.toDouble()
-        val by = other.y.toDouble()
-        val bz = other.z.toDouble()
-        return Vector3(
-            ay * bz - az * by,
-            az * bx - ax * bz,
-            ax * by - ay * bx
-        )
-    }
-
-    fun distanceTo(other: Vector3<T>): Double {
-        val dx = x.toDouble() - other.x.toDouble()
-        val dy = y.toDouble() - other.y.toDouble()
-        val dz = z.toDouble() - other.z.toDouble()
-        return sqrt(dx * dx + dy * dy + dz * dz)
-    }
-
-    fun normalizedCopy(): Vector3<Double> {
-        val length = length()
-        if (length == 0.0) {
-            return zero()
-        }
-        val invLength = 1.0 / length
-        return Vector3(
-            x.toDouble() * invLength,
-            y.toDouble() * invLength,
-            z.toDouble() * invLength
-        )
-    }
-
-    fun lerp(target: Vector3<T>, alpha: Double): Vector3<Double> {
-        val inverse = 1.0 - alpha
-        return Vector3(
-            x.toDouble() * inverse + target.x.toDouble() * alpha,
-            y.toDouble() * inverse + target.y.toDouble() * alpha,
-            z.toDouble() * inverse + target.z.toDouble() * alpha
-        )
-    }
-
-    operator fun unaryMinus(): Vector3<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector3(-x.toDouble(), -y.toDouble(), -z.toDouble()) as Vector3<T>
-    }
-
-    fun toVector2(): Vector2<T> {
-        return Vector2(x, y)
-    }
-
-    fun toVector4(w: T): Vector4<T> {
-        return Vector4(x, y, z, w)
-    }
-
-    operator fun component1(): T = x
-    operator fun component2(): T = y
-    operator fun component3(): T = z
-
-    /**
-     * Returns the magnitude/length of the vector
-     *
-     * ## Math
-     * `sqrt(x^2 + y^2 + z^2)`
-     */
-    override fun length(): Double {
-        return sqrt(x.toDouble() * x.toDouble() + y.toDouble() * y.toDouble() + z.toDouble() * z.toDouble())
-    }
-
-    override fun copy(): Vector3<T> {
-        return Vector3(x, y, z)
-    }
-
-    override fun toString(): String {
-        return "Vector3(x=$x, y=$y, z=$z)"
-    }
-}
-
-typealias Vector3D = Vector3<Double>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/math/Vector3d.kt b/src/commonMain/kotlin/com/dropbear/math/Vector3d.kt
new file mode 100644
index 0000000..7f300c1
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/math/Vector3d.kt
@@ -0,0 +1,87 @@
+package com.dropbear.math
+
+import kotlin.math.sqrt
+import kotlin.jvm.JvmField
+import kotlin.jvm.JvmStatic
+
+class Vector3d(
+    @JvmField var x: Double,
+    @JvmField var y: Double,
+    @JvmField var z: Double
+) {
+    companion object {
+        @JvmStatic fun zero() = Vector3d(0.0, 0.0, 0.0)
+        @JvmStatic fun one() = Vector3d(1.0, 1.0, 1.0)
+
+        @JvmStatic fun right() = Vector3d(1.0, 0.0, 0.0) // X
+        @JvmStatic fun up()    = Vector3d(0.0, 1.0, 0.0) // Y
+        @JvmStatic fun forward() = Vector3d(0.0, 0.0, 1.0) // Z
+    }
+
+    operator fun plus(other: Vector3d) = Vector3d(x + other.x, y + other.y, z + other.z)
+    operator fun plus(scalar: Double) = Vector3d(x + scalar, y + scalar, z + scalar)
+
+    operator fun minus(other: Vector3d) = Vector3d(x - other.x, y - other.y, z - other.z)
+    operator fun minus(scalar: Double) = Vector3d(x - scalar, y - scalar, z - scalar)
+
+    operator fun times(other: Vector3d) = Vector3d(x * other.x, y * other.y, z * other.z)
+    operator fun times(scalar: Double) = Vector3d(x * scalar, y * scalar, z * scalar)
+
+    operator fun div(other: Vector3d) = Vector3d(x / other.x, y / other.y, z / other.z)
+    operator fun div(scalar: Double) = Vector3d(x / scalar, y / scalar, z / scalar)
+
+    operator fun unaryMinus() = Vector3d(-x, -y, -z)
+
+    fun length() = sqrt(x * x + y * y + z * z)
+    fun lengthSquared() = x * x + y * y + z * z
+
+    fun dot(other: Vector3d) = x * other.x + y * other.y + z * other.z
+
+    /**
+     * Calculates the Cross Product.
+     * Result is perpendicular to both this vector and the other.
+     */
+    fun cross(other: Vector3d): Vector3d {
+        return Vector3d(
+            y * other.z - z * other.y,
+            z * other.x - x * other.z,
+            x * other.y - y * other.x
+        )
+    }
+
+    fun distanceTo(other: Vector3d): Double {
+        val dx = x - other.x
+        val dy = y - other.y
+        val dz = z - other.z
+        return sqrt(dx * dx + dy * dy + dz * dz)
+    }
+
+    fun normalize(): Vector3d {
+        val l = length()
+        return if (l != 0.0) this / l else zero()
+    }
+
+    fun lerp(target: Vector3d, alpha: Double): Vector3d {
+        val inv = 1.0 - alpha
+        return Vector3d(
+            x * inv + target.x * alpha,
+            y * inv + target.y * alpha,
+            z * inv + target.z * alpha
+        )
+    }
+
+    fun toFloat() = Vector3f(x.toFloat(), y.toFloat(), z.toFloat())
+    fun toInt() = Vector3i(x.toInt(), y.toInt(), z.toInt())
+    fun toVector2d() = Vector2d(x, y)
+
+    fun toGeneric() = Vector3(x, y, z)
+
+    override fun toString() = "Vector3d($x, $y, $z)"
+    override fun equals(other: Any?) = other is Vector3d && x == other.x && y == other.y && z == other.z
+    override fun hashCode(): Int {
+        var result = x.hashCode()
+        result = 31 * result + y.hashCode()
+        result = 31 * result + z.hashCode()
+        return result
+    }
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/math/Vector3f.kt b/src/commonMain/kotlin/com/dropbear/math/Vector3f.kt
new file mode 100644
index 0000000..ed5ad2b
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/math/Vector3f.kt
@@ -0,0 +1,54 @@
+package com.dropbear.math
+
+import kotlin.math.sqrt
+import kotlin.jvm.JvmField
+import kotlin.jvm.JvmStatic
+
+class Vector3f(
+    @JvmField var x: Float,
+    @JvmField var y: Float,
+    @JvmField var z: Float
+) {
+    companion object {
+        @JvmStatic
+        fun zero() = Vector3f(0f, 0f, 0f)
+    }
+
+    operator fun plus(other: Vector3f) = Vector3f(x + other.x, y + other.y, z + other.z)
+    operator fun plus(scalar: Float) = Vector3f(x + scalar, y + scalar, z + scalar)
+
+    operator fun minus(other: Vector3f) = Vector3f(x - other.x, y - other.y, z - other.z)
+    operator fun minus(scalar: Float) = Vector3f(x - scalar, y - scalar, z - scalar)
+
+    operator fun times(other: Vector3f) = Vector3f(x * other.x, y * other.y, z * other.z)
+    operator fun times(scalar: Float) = Vector3f(x * scalar, y * scalar, z * scalar)
+
+    operator fun div(other: Vector3f) = Vector3f(x / other.x, y / other.y, z / other.z)
+    operator fun div(scalar: Float) = Vector3f(x / scalar, y / scalar, z / scalar)
+
+    operator fun unaryMinus() = Vector3f(-x, -y, -z)
+
+    fun length(): Float = sqrt(x * x + y * y + z * z)
+    fun lengthSquared(): Float = x * x + y * y + z * z
+
+    fun dot(other: Vector3f) = x * other.x + y * other.y + z * other.z
+
+    fun cross(other: Vector3f): Vector3f {
+        return Vector3f(
+            y * other.z - z * other.y,
+            z * other.x - x * other.z,
+            x * other.y - y * other.x
+        )
+    }
+
+    fun normalize(): Vector3f {
+        val l = length()
+        return if (l != 0f) Vector3f(x / l, y / l, z / l) else zero()
+    }
+
+    fun toDouble() = Vector3d(x.toDouble(), y.toDouble(), z.toDouble())
+    fun toInt() = Vector3i(x.toInt(), y.toInt(), z.toInt())
+    fun toVector2f() = Vector2f(x, y)
+
+    override fun toString() = "Vector3f($x, $y, $z)"
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/math/Vector3i.kt b/src/commonMain/kotlin/com/dropbear/math/Vector3i.kt
new file mode 100644
index 0000000..2e4a7a0
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/math/Vector3i.kt
@@ -0,0 +1,28 @@
+package com.dropbear.math
+
+import kotlin.math.sqrt
+import kotlin.jvm.JvmField
+import kotlin.jvm.JvmStatic
+
+class Vector3i(
+    @JvmField var x: Int,
+    @JvmField var y: Int,
+    @JvmField var z: Int
+) {
+    companion object {
+        @JvmStatic
+        fun zero() = Vector3i(0, 0, 0)
+    }
+
+    operator fun plus(other: Vector3i) = Vector3i(x + other.x, y + other.y, z + other.z)
+    operator fun minus(other: Vector3i) = Vector3i(x - other.x, y - other.y, z - other.z)
+    operator fun times(scalar: Int) = Vector3i(x * scalar, y * scalar, z * scalar)
+    operator fun div(scalar: Int) = Vector3i(x / scalar, y / scalar, z / scalar)
+
+    fun length(): Double = sqrt((x * x + y * y + z * z).toDouble())
+
+    fun toFloat() = Vector3f(x.toFloat(), y.toFloat(), z.toFloat())
+    fun toDouble() = Vector3d(x.toDouble(), y.toDouble(), z.toDouble())
+
+    override fun toString() = "Vector3i($x, $y, $z)"
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/math/Vector4.kt b/src/commonMain/kotlin/com/dropbear/math/Vector4.kt
index 408a483..c5ac7d1 100644
--- a/src/commonMain/kotlin/com/dropbear/math/Vector4.kt
+++ b/src/commonMain/kotlin/com/dropbear/math/Vector4.kt
@@ -1,228 +1,48 @@
 package com.dropbear.math
 
 import kotlin.jvm.JvmField
-import kotlin.math.sqrt
+import kotlin.jvm.JvmStatic
 
 /**
- * A class for holding a vector of `4` values of the same type.
+ * A Generic Vector4.
+ * WARNING: Uses boxing. Prefer [Vector4d]/[Vector4f]/[Vector4i] for performance.
  */
-class Vector4<T: Number>(
+class Vector4<T : Number>(
     @JvmField var x: T,
     @JvmField var y: T,
     @JvmField var z: T,
-    @JvmField var w: T,
-) : Vector<T, Vector4<T>>() {
+    @JvmField var w: T
+) {
     companion object {
-        /**
-         * Creates a new [com.dropbear.math.Vector4] of type `T` with one value.
-         */
-        fun <T: Number> uniform(value: T): Vector4<T> {
-            return Vector4(value, value, value, value)
-        }
+        @JvmStatic
+        fun fromDoubles(x: Double, y: Double, z: Double, w: Double): Vector4<Double> = Vector4(x, y, z, w)
 
-        /**
-         * Creates a [com.dropbear.math.Vector4] of type [Double] filled with only zeroes
-         */
-        fun zero(): Vector4<Double> {
-            return Vector4(0.0, 0.0, 0.0, 0.0)
-        }
-
-        fun x(): Vector4D {
-            return Vector4D(1.0, 0.0, 0.0, 0.0)
-        }
-
-        fun y(): Vector4D {
-            return Vector4D(0.0, 1.0, 0.0, 0.0)
-        }
-
-        fun z(): Vector4D {
-            return Vector4D(0.0, 0.0, 1.0, 0.0)
-        }
-
-        fun w(): Vector4D {
-            return Vector4D(0.0, 0.0, 0.0, 1.0)
-        }
-    }
-
-    fun asDoubleVector(): Vector4<Double> {
-        return Vector4(x.toDouble(), y.toDouble(), z.toDouble(), w.toDouble())
-    }
-
-    override fun normalize(): Vector4<T> {
-        val length = length()
-        if (length > 0.0) {
-            val invLength = 1.0 / length
-            @Suppress("UNCHECKED_CAST")
-            x = (x.toDouble() * invLength) as T
-            @Suppress("UNCHECKED_CAST")
-            y = (y.toDouble() * invLength) as T
-            @Suppress("UNCHECKED_CAST")
-            z = (z.toDouble() * invLength) as T
-            @Suppress("UNCHECKED_CAST")
-            w = (w.toDouble() * invLength) as T
-        }
-        return this
-    }
-
-    override operator fun plus(other: Vector4<T>): Vector4<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector4(
-            x.toDouble() + other.x.toDouble(),
-            y.toDouble() + other.y.toDouble(),
-            z.toDouble() + other.z.toDouble(),
-            w.toDouble() + other.w.toDouble()
-        ) as Vector4<T>
+        @JvmStatic
+        fun zero(): Vector4<Double> = Vector4(0.0, 0.0, 0.0, 0.0)
     }
 
-    override operator fun plus(scalar: T): Vector4<T> {
-        val value = scalar.toDouble()
-        @Suppress("UNCHECKED_CAST")
-        return Vector4(
-            x.toDouble() + value,
-            y.toDouble() + value,
-            z.toDouble() + value,
-            w.toDouble() + value
-        ) as Vector4<T>
-    }
-
-    override operator fun minus(other: Vector4<T>): Vector4<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector4(
-            x.toDouble() - other.x.toDouble(),
-            y.toDouble() - other.y.toDouble(),
-            z.toDouble() - other.z.toDouble(),
-            w.toDouble() - other.w.toDouble()
-        ) as Vector4<T>
-    }
-
-    override operator fun minus(scalar: T): Vector4<T> {
-        val value = scalar.toDouble()
-        @Suppress("UNCHECKED_CAST")
-        return Vector4(
-            x.toDouble() - value,
-            y.toDouble() - value,
-            z.toDouble() - value,
-            w.toDouble() - value
-        ) as Vector4<T>
-    }
+    fun asVector4d(): Vector4d = Vector4d(x.toDouble(), y.toDouble(), z.toDouble(), w.toDouble())
+    fun asVector4f(): Vector4f = Vector4f(x.toFloat(), y.toFloat(), z.toFloat(), w.toFloat())
+    fun asVector4i(): Vector4i = Vector4i(x.toInt(), y.toInt(), z.toInt(), w.toInt())
 
-    override operator fun times(other: Vector4<T>): Vector4<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector4(
-            x.toDouble() * other.x.toDouble(),
-            y.toDouble() * other.y.toDouble(),
-            z.toDouble() * other.z.toDouble(),
-            w.toDouble() * other.w.toDouble()
-        ) as Vector4<T>
-    }
+    operator fun component1() = x
+    operator fun component2() = y
+    operator fun component3() = z
+    operator fun component4() = w
 
-    override operator fun times(scalar: T): Vector4<T> {
-        val value = scalar.toDouble()
-        @Suppress("UNCHECKED_CAST")
-        return Vector4(
-            x.toDouble() * value,
-            y.toDouble() * value,
-            z.toDouble() * value,
-            w.toDouble() * value
-        ) as Vector4<T>
-    }
+    override fun toString() = "Vector4($x, $y, $z, $w)"
 
-    override operator fun div(other: Vector4<T>): Vector4<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector4(
-            x.toDouble() / other.x.toDouble(),
-            y.toDouble() / other.y.toDouble(),
-            z.toDouble() / other.z.toDouble(),
-            w.toDouble() / other.w.toDouble()
-        ) as Vector4<T>
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Vector4<*>) return false
+        return x == other.x && y == other.y && z == other.z && w == other.w
     }
 
-    override operator fun div(scalar: T): Vector4<T> {
-        val value = scalar.toDouble()
-        @Suppress("UNCHECKED_CAST")
-        return Vector4(
-            x.toDouble() / value,
-            y.toDouble() / value,
-            z.toDouble() / value,
-            w.toDouble() / value
-        ) as Vector4<T>
+    override fun hashCode(): Int {
+        var result = x.hashCode()
+        result = 31 * result + y.hashCode()
+        result = 31 * result + z.hashCode()
+        result = 31 * result + w.hashCode()
+        return result
     }
-
-    fun lengthSquared(): Double {
-        val dx = x.toDouble()
-        val dy = y.toDouble()
-        val dz = z.toDouble()
-        val dw = w.toDouble()
-        return dx * dx + dy * dy + dz * dz + dw * dw
-    }
-
-    fun dot(other: Vector4<T>): Double {
-        return x.toDouble() * other.x.toDouble() +
-                y.toDouble() * other.y.toDouble() +
-                z.toDouble() * other.z.toDouble() +
-                w.toDouble() * other.w.toDouble()
-    }
-
-    fun distanceTo(other: Vector4<T>): Double {
-        val dx = x.toDouble() - other.x.toDouble()
-        val dy = y.toDouble() - other.y.toDouble()
-        val dz = z.toDouble() - other.z.toDouble()
-        val dw = w.toDouble() - other.w.toDouble()
-        return sqrt(dx * dx + dy * dy + dz * dz + dw * dw)
-    }
-
-    fun normalizedCopy(): Vector4<Double> {
-        val length = length()
-        if (length == 0.0) {
-            return zero()
-        }
-        val invLength = 1.0 / length
-        return Vector4(
-            x.toDouble() * invLength,
-            y.toDouble() * invLength,
-            z.toDouble() * invLength,
-            w.toDouble() * invLength
-        )
-    }
-
-    fun lerp(target: Vector4<T>, alpha: Double): Vector4<Double> {
-        val inverse = 1.0 - alpha
-        return Vector4(
-            x.toDouble() * inverse + target.x.toDouble() * alpha,
-            y.toDouble() * inverse + target.y.toDouble() * alpha,
-            z.toDouble() * inverse + target.z.toDouble() * alpha,
-            w.toDouble() * inverse + target.w.toDouble() * alpha
-        )
-    }
-
-    operator fun unaryMinus(): Vector4<T> {
-        @Suppress("UNCHECKED_CAST")
-        return Vector4(-x.toDouble(), -y.toDouble(), -z.toDouble(), -w.toDouble()) as Vector4<T>
-    }
-
-    fun toVector3(): Vector3<T> {
-        return Vector3(x, y, z)
-    }
-
-    operator fun component1(): T = x
-    operator fun component2(): T = y
-    operator fun component3(): T = z
-    operator fun component4(): T = w
-
-    /**
-     * Returns the magnitude/length of the vector
-     */
-    override fun length(): Double {
-        return sqrt(lengthSquared())
-    }
-
-    override fun copy(): Vector4<T> {
-        return Vector4(x, y, z, w)
-    }
-
-    override fun toString(): String {
-        return "Vector4(x=$x, y=$y, z=$z, w=$w)"
-    }
-}
-
-typealias Vector4D = Vector4<Double>
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/math/Vector4d.kt b/src/commonMain/kotlin/com/dropbear/math/Vector4d.kt
new file mode 100644
index 0000000..50b695b
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/math/Vector4d.kt
@@ -0,0 +1,81 @@
+package com.dropbear.math
+
+import kotlin.math.sqrt
+import kotlin.jvm.JvmField
+import kotlin.jvm.JvmStatic
+
+class Vector4d(
+    @JvmField var x: Double,
+    @JvmField var y: Double,
+    @JvmField var z: Double,
+    @JvmField var w: Double
+) {
+    companion object {
+        @JvmStatic fun zero() = Vector4d(0.0, 0.0, 0.0, 0.0)
+        @JvmStatic fun one() = Vector4d(1.0, 1.0, 1.0, 1.0)
+    }
+
+    operator fun plus(other: Vector4d) = Vector4d(x + other.x, y + other.y, z + other.z, w + other.w)
+    operator fun plus(scalar: Double) = Vector4d(x + scalar, y + scalar, z + scalar, w + scalar)
+
+    operator fun minus(other: Vector4d) = Vector4d(x - other.x, y - other.y, z - other.z, w - other.w)
+    operator fun minus(scalar: Double) = Vector4d(x - scalar, y - scalar, z - scalar, w - scalar)
+
+    operator fun times(other: Vector4d) = Vector4d(x * other.x, y * other.y, z * other.z, w * other.w)
+    operator fun times(scalar: Double) = Vector4d(x * scalar, y * scalar, z * scalar, w * scalar)
+
+    operator fun div(other: Vector4d) = Vector4d(x / other.x, y / other.y, z / other.z, w / other.w)
+    operator fun div(scalar: Double) = Vector4d(x / scalar, y / scalar, z / scalar, w / scalar)
+
+    operator fun unaryMinus() = Vector4d(-x, -y, -z, -w)
+
+    fun length() = sqrt(x * x + y * y + z * z + w * w)
+    fun lengthSquared() = x * x + y * y + z * z + w * w
+
+    fun dot(other: Vector4d) = x * other.x + y * other.y + z * other.z + w * other.w
+
+    fun distanceTo(other: Vector4d): Double {
+        val dx = x - other.x
+        val dy = y - other.y
+        val dz = z - other.z
+        val dw = w - other.w
+        return sqrt(dx * dx + dy * dy + dz * dz + dw * dw)
+    }
+
+    fun normalize(): Vector4d {
+        val l = length()
+        return if (l != 0.0) this / l else zero()
+    }
+
+    fun lerp(target: Vector4d, alpha: Double): Vector4d {
+        val inv = 1.0 - alpha
+        return Vector4d(
+            x * inv + target.x * alpha,
+            y * inv + target.y * alpha,
+            z * inv + target.z * alpha,
+            w * inv + target.w * alpha
+        )
+    }
+
+    fun toFloat() = Vector4f(x.toFloat(), y.toFloat(), z.toFloat(), w.toFloat())
+    fun toInt() = Vector4i(x.toInt(), y.toInt(), z.toInt(), w.toInt())
+    fun toVector3d() = Vector3d(x, y, z)
+    fun toVector2d() = Vector2d(x, y)
+
+    operator fun component1() = x
+    operator fun component2() = y
+    operator fun component3() = z
+    operator fun component4() = w
+
+    fun toGeneric() = Vector4(x, y, z, w)
+
+    override fun toString() = "Vector4d($x, $y, $z, $w)"
+    override fun equals(other: Any?) = other is Vector4d && x == other.x && y == other.y && z == other.z && w == other.w
+    override fun hashCode(): Int {
+        var result = x.hashCode()
+        result = 31 * result + y.hashCode()
+        result = 31 * result + z.hashCode()
+        result = 31 * result + w.hashCode()
+        return result
+    }
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/math/Vector4f.kt b/src/commonMain/kotlin/com/dropbear/math/Vector4f.kt
new file mode 100644
index 0000000..02d4978
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/math/Vector4f.kt
@@ -0,0 +1,52 @@
+package com.dropbear.math
+
+import kotlin.math.sqrt
+import kotlin.jvm.JvmField
+import kotlin.jvm.JvmStatic
+
+class Vector4f(
+    @JvmField var x: Float,
+    @JvmField var y: Float,
+    @JvmField var z: Float,
+    @JvmField var w: Float
+) {
+    companion object {
+        @JvmStatic
+        fun zero() = Vector4f(0f, 0f, 0f, 0f)
+    }
+
+    operator fun plus(other: Vector4f) = Vector4f(x + other.x, y + other.y, z + other.z, w + other.w)
+    operator fun plus(scalar: Float) = Vector4f(x + scalar, y + scalar, z + scalar, w + scalar)
+
+    operator fun minus(other: Vector4f) = Vector4f(x - other.x, y - other.y, z - other.z, w - other.w)
+    operator fun minus(scalar: Float) = Vector4f(x - scalar, y - scalar, z - scalar, w - scalar)
+
+    operator fun times(other: Vector4f) = Vector4f(x * other.x, y * other.y, z * other.z, w * other.w)
+    operator fun times(scalar: Float) = Vector4f(x * scalar, y * scalar, z * scalar, w * scalar)
+
+    operator fun div(other: Vector4f) = Vector4f(x / other.x, y / other.y, z / other.z, w / other.w)
+    operator fun div(scalar: Float) = Vector4f(x / scalar, y / scalar, z / scalar, w / scalar)
+
+    operator fun unaryMinus() = Vector4f(-x, -y, -z, -w)
+
+    fun length(): Float = sqrt(x * x + y * y + z * z + w * w)
+    fun lengthSquared(): Float = x * x + y * y + z * z + w * w
+
+    fun dot(other: Vector4f) = x * other.x + y * other.y + z * other.z + w * other.w
+
+    fun normalize(): Vector4f {
+        val l = length()
+        return if (l != 0f) Vector4f(x / l, y / l, z / l, w / l) else zero()
+    }
+
+    fun toDouble() = Vector4d(x.toDouble(), y.toDouble(), z.toDouble(), w.toDouble())
+    fun toInt() = Vector4i(x.toInt(), y.toInt(), z.toInt(), w.toInt())
+    fun toVector3f() = Vector3f(x, y, z)
+
+    operator fun component1() = x
+    operator fun component2() = y
+    operator fun component3() = z
+    operator fun component4() = w
+
+    override fun toString() = "Vector4f($x, $y, $z, $w)"
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/math/Vector4i.kt b/src/commonMain/kotlin/com/dropbear/math/Vector4i.kt
new file mode 100644
index 0000000..b3af7e5
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/math/Vector4i.kt
@@ -0,0 +1,35 @@
+package com.dropbear.math
+
+import kotlin.math.sqrt
+import kotlin.jvm.JvmField
+import kotlin.jvm.JvmStatic
+
+class Vector4i(
+    @JvmField var x: Int,
+    @JvmField var y: Int,
+    @JvmField var z: Int,
+    @JvmField var w: Int
+) {
+    companion object {
+        @JvmStatic
+        fun zero() = Vector4i(0, 0, 0, 0)
+    }
+
+    operator fun plus(other: Vector4i) = Vector4i(x + other.x, y + other.y, z + other.z, w + other.w)
+    operator fun minus(other: Vector4i) = Vector4i(x - other.x, y - other.y, z - other.z, w - other.w)
+
+    operator fun times(scalar: Int) = Vector4i(x * scalar, y * scalar, z * scalar, w * scalar)
+    operator fun div(scalar: Int) = Vector4i(x / scalar, y / scalar, z / scalar, w / scalar)
+
+    fun length(): Double = sqrt((x * x + y * y + z * z + w * w).toDouble())
+
+    fun toFloat() = Vector4f(x.toFloat(), y.toFloat(), z.toFloat(), w.toFloat())
+    fun toDouble() = Vector4d(x.toDouble(), y.toDouble(), z.toDouble(), w.toDouble())
+
+    operator fun component1() = x
+    operator fun component2() = y
+    operator fun component3() = z
+    operator fun component4() = w
+
+    override fun toString() = "Vector4i($x, $y, $z, $w)"
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/physics/Collider.kt b/src/commonMain/kotlin/com/dropbear/physics/Collider.kt
index 41fabac..3b9d9e3 100644
--- a/src/commonMain/kotlin/com/dropbear/physics/Collider.kt
+++ b/src/commonMain/kotlin/com/dropbear/physics/Collider.kt
@@ -1,82 +1,52 @@
 package com.dropbear.physics
 
 import com.dropbear.EntityId
-import com.dropbear.exceptionOnError
-import com.dropbear.ffi.NativeEngine
-import com.dropbear.math.Vector3D
+import com.dropbear.math.Vector3d
 
 class Collider(
     internal val index: Index,
     internal val entity: EntityId,
     internal val id: UInt,
-    var colliderShape: ColliderShape,
-    var density: Double,
-    var friction: Double,
-    var restitution: Double,
-    var isSensor: Boolean,
-    var translation: Vector3D,
-    var rotation: Vector3D,
 ) {
-    internal constructor(
-        index: Index,
-        entity: EntityId,
-        id: UInt,
-        colliderShape: ColliderShape,
-        density: Double,
-        friction: Double,
-        restitution: Double,
-        isSensor: Boolean,
-        translation: Vector3D,
-        rotation: Vector3D,
-        native: NativeEngine,
-    ): this(
-        index,
-        entity,
-        id,
-        colliderShape,
-        density,
-        friction,
-        restitution,
-        isSensor,
-        translation,
-        rotation
-    ) {
-        this.native = native
-    }
+    var colliderShape: ColliderShape
+        get() = getColliderShape(this)
+        set(value) = setColliderShape(this, value)
+    var density: Double
+        get() = getColliderDensity(this)
+        set(value) = setColliderDensity(this, value)
+    var friction: Double
+        get() = getColliderFriction(this)
+        set(value) = setColliderFriction(this, value)
+    var restitution: Double
+        get() = getColliderRestitution(this)
+        set(value) = setColliderRestitution(this, value)
+    var isSensor: Boolean
+        get() = getColliderIsSensor(this)
+        set(value) = setColliderIsSensor(this, value)
+    var translation: Vector3d
+        get() = getColliderTranslation(this)
+        set(value) = setColliderTranslation(this, value)
+    var rotation: Vector3d
+        get() = getColliderRotation(this)
+        set(value) = setColliderRotation(this, value)
+    var mass: Double
+        get() = getColliderMass(this)
+        set(value) = setColliderMass(this, value)
+}
 
-    internal var native: NativeEngine? = null
-
-    /**
-     * Calculates the mass of the [Collider] based on its shape (determined based on colliderShape) and density.
-     *
-     * @return Mass of the collider in kilograms.
-     */
-    fun mass(): Double {
-        return when (colliderShape) {
-            is ColliderShape.Box -> {
-                (colliderShape as ColliderShape.Box).volume() * density
-            }
-            is ColliderShape.Sphere -> {
-                (colliderShape as ColliderShape.Sphere).volume() * density
-            }
-            is ColliderShape.Capsule -> {
-                (colliderShape as ColliderShape.Capsule).volume() * density
-            }
-
-            is ColliderShape.Cone -> (colliderShape as ColliderShape.Cone).volume() * density
-            is ColliderShape.Cylinder -> (colliderShape as ColliderShape.Cylinder).volume() * density
-        }
-    }
-
-    /**
-     * Pushes your changes to the [Collider] back to the physics engine.
-     */
-    fun setCollider() {
-        val native = native ?: if (exceptionOnError) {
-            throw IllegalStateException("Native engine is not initialised")
-        } else {
-            return
-        }
-        native.setCollider(this)
-    }
-}
\ No newline at end of file
+expect fun Collider.getColliderShape(collider: Collider): ColliderShape
+expect fun Collider.setColliderShape(collider: Collider, shape: ColliderShape)
+expect fun Collider.getColliderDensity(collider: Collider): Double
+expect fun Collider.setColliderDensity(collider: Collider, density: Double)
+expect fun Collider.getColliderFriction(collider: Collider): Double
+expect fun Collider.setColliderFriction(collider: Collider, friction: Double)
+expect fun Collider.getColliderRestitution(collider: Collider): Double
+expect fun Collider.setColliderRestitution(collider: Collider, restitution: Double)
+expect fun Collider.getColliderIsSensor(collider: Collider): Boolean
+expect fun Collider.setColliderIsSensor(collider: Collider, isSensor: Boolean)
+expect fun Collider.getColliderTranslation(collider: Collider): Vector3d
+expect fun Collider.setColliderTranslation(collider: Collider, translation: Vector3d)
+expect fun Collider.getColliderRotation(collider: Collider): Vector3d
+expect fun Collider.setColliderRotation(collider: Collider, rotation: Vector3d)
+expect fun Collider.getColliderMass(collider: Collider): Double
+expect fun Collider.setColliderMass(collider: Collider, mass: Double)
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/physics/ColliderGroup.kt b/src/commonMain/kotlin/com/dropbear/physics/ColliderGroup.kt
new file mode 100644
index 0000000..4f0a8ae
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/physics/ColliderGroup.kt
@@ -0,0 +1,23 @@
+package com.dropbear.physics
+
+import com.dropbear.ecs.Component
+import com.dropbear.EntityId
+import com.dropbear.ecs.ComponentType
+
+class ColliderGroup(
+    entity: EntityId,
+) : Component(entity, "ColliderGroup") {
+    fun getColliders(): List<Collider> {
+        return getColliderGroupColliders(this)
+    }
+
+    companion object : ComponentType<ColliderGroup> {
+        override fun get(entityId: EntityId): ColliderGroup? {
+            return if (colliderGroupExistsForEntity(entityId)) ColliderGroup(entityId) else null
+        }
+    }
+}
+
+expect fun ColliderGroup.getColliderGroupColliders(colliderGroup: ColliderGroup): List<Collider>
+
+expect fun colliderGroupExistsForEntity(entityId: EntityId): Boolean
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/physics/ColliderShape.kt b/src/commonMain/kotlin/com/dropbear/physics/ColliderShape.kt
index c72c9c5..c0241ab 100644
--- a/src/commonMain/kotlin/com/dropbear/physics/ColliderShape.kt
+++ b/src/commonMain/kotlin/com/dropbear/physics/ColliderShape.kt
@@ -1,80 +1,30 @@
 package com.dropbear.physics
 
-import com.dropbear.math.Vector3D
-import kotlin.math.PI
-import kotlin.math.pow
+import com.dropbear.math.Vector3d
 
 sealed class ColliderShape {
 
     /**
      * Box shape with half-extents (half-width, half-height, half-depth).
      */
-    data class Box(val halfExtents: Vector3D) : ColliderShape() {
-        /**
-         * Returns the volume of the shape/box.
-         *
-         * In this case, volume is calculated as: [halfExtents].x * [halfExtents].y * [halfExtents].z
-         */
-        fun volume(): Double {
-            return halfExtents.x * halfExtents.y * halfExtents.z
-        }
-    }
+    data class Box(val halfExtents: Vector3d) : ColliderShape()
 
     /**
      * Sphere shape with radius.
      */
-    data class Sphere(val radius: Float) : ColliderShape() {
-        /**
-         * Returns the volume of the shape/sphere.
-         *
-         * In this case, volume is calculated as: (4/3) * π * [radius]^3
-         */
-        fun volume(): Double {
-            return (4.0 / 3.0) * PI * radius.toDouble().pow(3.0)
-        }
-    }
+    data class Sphere(val radius: Float) : ColliderShape()
 
     /**
      * Capsule shape along Y-axis.
      */
-    data class Capsule(val halfHeight: Float, val radius: Float) : ColliderShape() {
-        /**
-         * Returns the volume of the shape/capsule.
-         *
-         * In this case, volume is calculated as: (4/3) * π * [radius]^3 + π * [radius]^2 * (2 * [halfHeight])
-         */
-        fun volume(): Double {
-            val sphereVolume = (4.0 / 3.0) * PI * radius.toDouble().pow(3.0)
-            val cylinderVolume = PI * radius.toDouble().pow(2.0) * (2.0 * halfHeight.toDouble())
-            return sphereVolume + cylinderVolume
-        }
-    }
+    data class Capsule(val halfHeight: Float, val radius: Float) : ColliderShape()
 
     /**
      * Cylinder shape along Y-axis.
      */
-    data class Cylinder(val halfHeight: Float, val radius: Float) : ColliderShape() {
-        /**
-         * Returns the volume of the shape/cylinder.
-         *
-         * In this case, volume is calculated as: π * [radius]^2 * (2 * [halfHeight])
-         */
-        fun volume(): Double {
-            return PI * radius.toDouble().pow(2.0) * (2.0 * halfHeight.toDouble())
-        }
-    }
-
+    data class Cylinder(val halfHeight: Float, val radius: Float) : ColliderShape()
     /**
      * Cone shape along Y-axis.
      */
-    data class Cone(val halfHeight: Float, val radius: Float) : ColliderShape() {
-        /**
-         * Returns the volume of the shape/cone.
-         *
-         * In this case, volume is calculated as: (1/3) * π * [radius]^2 * (2 * [halfHeight])
-         */
-        fun volume(): Double {
-            return (1.0 / 3.0) * PI * radius.toDouble().pow(2.0) * (2.0 * halfHeight.toDouble())
-        }
-    }
+    data class Cone(val halfHeight: Float, val radius: Float) : ColliderShape()
 }
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/physics/Physics.kt b/src/commonMain/kotlin/com/dropbear/physics/Physics.kt
new file mode 100644
index 0000000..781583f
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/physics/Physics.kt
@@ -0,0 +1,10 @@
+package com.dropbear.physics
+
+import com.dropbear.math.Vector3d
+
+var gravity: Vector3d
+    get() = getGravity()
+    set(value) = setGravity(value)
+
+internal expect fun getGravity(): Vector3d
+internal expect fun setGravity(gravity: Vector3d)
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/physics/RigidBody.kt b/src/commonMain/kotlin/com/dropbear/physics/RigidBody.kt
index 0260773..6ee7fdd 100644
--- a/src/commonMain/kotlin/com/dropbear/physics/RigidBody.kt
+++ b/src/commonMain/kotlin/com/dropbear/physics/RigidBody.kt
@@ -1,63 +1,53 @@
 package com.dropbear.physics
 
+import com.dropbear.ecs.Component
 import com.dropbear.EntityId
-import com.dropbear.exceptionOnError
-import com.dropbear.ffi.NativeEngine
-import com.dropbear.math.Vector3D
+import com.dropbear.ecs.ComponentType
+import com.dropbear.math.Vector3d
 
 class RigidBody(
     internal val index: Index,
     internal val entity: EntityId,
-    val rigidBodyMode: RigidBodyMode,
-    var gravityScale: Double,
-    var canSleep: Boolean,
-    var ccdEnabled: Boolean,
-    var linearVelocity: Vector3D,
-    var angularVelocity: Vector3D,
-    var linearDamping: Double,
-    var angularDamping: Double,
-    var lockTranslation: AxisLock,
-    var lockRotation: AxisLock,
-) {
-    internal constructor(
-        index: Index,
-        entity: EntityId,
-        rigidBodyMode: RigidBodyMode,
-        gravityScale: Double,
-        canSleep: Boolean,
-        ccdEnabled: Boolean,
-        linearVelocity: Vector3D,
-        angularVelocity: Vector3D,
-        linearDamping: Double,
-        angularDamping: Double,
-        lockTranslation: AxisLock,
-        lockRotation: AxisLock,
-        native: NativeEngine
-    ): this(
-        index,
-        entity,
-        rigidBodyMode,
-        gravityScale,
-        canSleep,
-        ccdEnabled,
-        linearVelocity,
-        angularVelocity,
-        linearDamping,
-        angularDamping,
-        lockTranslation,
-        lockRotation
-    ) {
-        this.native = native
-    }
-
-    internal var native: NativeEngine? = null
+) : Component(entity, "RigidBody") {
+    var rigidBodyMode: RigidBodyMode
+        get() = getRigidbodyMode(this)
+        set(value) = setRigidbodyMode(this, value)
+    var gravityScale: Double
+        get() = getRigidbodyGravityScale(this)
+        set(value) = setRigidbodyGravityScale(this, value)
+    var canSleep: Boolean
+        get() = getRigidBodySleep(this)
+        set(value) = setRigidBodySleep(this, value)
+    var ccdEnabled: Boolean
+        get() = getRigidbodyCcdEnabled(this)
+        set(value) = setRigidbodyCcdEnabled(this, value)
+    var linearVelocity: Vector3d
+        get() = getRigidbodyLinearVelocity(this)
+        set(value) = setRigidbodyLinearVelocity(this, value)
+    var angularVelocity: Vector3d
+        get() = getRigidbodyAngularVelocity(this)
+        set(value) = setRigidbodyAngularVelocity(this, value)
+    var linearDamping: Double
+        get() = getRigidbodyLinearDamping(this)
+        set(value) = setRigidbodyLinearDamping(this, value)
+    var angularDamping: Double
+        get() = getRigidbodyAngularDamping(this)
+        set(value) = setRigidbodyAngularDamping(this, value)
+    var lockTranslation: AxisLock
+        get() = getRigidbodyLockTranslation(this)
+        set(value) = setRigidbodyLockTranslation(this, value)
+    var lockRotation: AxisLock
+        get() = getRigidbodyLockRotation(this)
+        set(value) = setRigidbodyLockRotation(this, value)
+    val childColliders: List<Collider>
+        get() = getRigidbodyChildren(this)
 
     /**
      * Applies an instant force.
      *
      * Typically used for jumping or explosions.
      */
-    fun applyImpulse(impulse: Vector3D) {
+    fun applyImpulse(impulse: Vector3d) {
         applyImpulse(impulse.x, impulse.y, impulse.z)
     }
 
@@ -67,19 +57,15 @@ class RigidBody(
      * Typically used for jumping or explosions.
      */
     fun applyImpulse(x: Double, y: Double, z: Double) {
-        val native = native ?: if (exceptionOnError) {
-            throw IllegalStateException("Native engine is not initialised")
-        } else {
-            return
-        }
-        return native.applyImpulse(index, x, y, z)
+        return applyImpulse(index, x, y, z)
     }
+
     /**
      * Applies an instant torque/rotational impulse.
      *
      * Typically used for spinning objects.
      */
-    fun applyTorqueImpulse(impulse: Vector3D) {
+    fun applyTorqueImpulse(impulse: Vector3d) {
         applyTorqueImpulse(impulse.x, impulse.y, impulse.z)
     }
 
@@ -89,39 +75,39 @@ class RigidBody(
      * Typically used for spinning objects.
      */
     fun applyTorqueImpulse(x: Double, y: Double, z: Double) {
-        val native = native ?: if (exceptionOnError) {
-            throw IllegalStateException("Native engine is not initialised")
-        } else {
-            return
-        }
-        return native.applyTorqueImpulse(index, x, y, z)
+        return applyTorqueImpulse(index, x, y, z)
     }
 
-    /**
-     * Pushes your changes to the [RigidBody] back to the physics engine.
-     */
-    fun setRigidbody() {
-        val native = native ?: if (exceptionOnError) {
-            throw IllegalStateException("Native engine is not initialised")
-        } else {
-            return
+    companion object : ComponentType<RigidBody> {
+        override fun get(entityId: EntityId): RigidBody? {
+            val index = rigidBodyExistsForEntity(entityId)
+            return if (index != null) RigidBody(index, entityId) else null
         }
-
-        native.setRigidbody(this)
     }
+}
 
-    /**
-     * Fetches all child [Collider] under this [RigidBody].
-     *
-     * Returns `null` if there is no component such as `ColliderGroup` attached to the
-     * entity, or an [emptyList] if there are no child colliders.
-     */
-    fun getChildColliders(): List<Collider>? {
-        val native = native ?: if (exceptionOnError) {
-            throw IllegalStateException("Native engine is not initialised")
-        } else {
-            return null
-        }
-        return native.getChildColliders(index)
-    }
-}
\ No newline at end of file
+expect fun RigidBody.getRigidbodyMode(rigidBody: RigidBody): RigidBodyMode
+expect fun RigidBody.setRigidbodyMode(rigidBody: RigidBody, mode: RigidBodyMode)
+expect fun RigidBody.getRigidbodyGravityScale(rigidBody: RigidBody): Double
+expect fun RigidBody.setRigidbodyGravityScale(rigidBody: RigidBody, gravityScale: Double)
+expect fun RigidBody.getRigidBodySleep(rigidBody: RigidBody): Boolean
+expect fun RigidBody.setRigidBodySleep(rigidBody: RigidBody, canSleep: Boolean)
+expect fun RigidBody.getRigidbodyCcdEnabled(rigidBody: RigidBody): Boolean
+expect fun RigidBody.setRigidbodyCcdEnabled(rigidBody: RigidBody, ccdEnabled: Boolean)
+expect fun RigidBody.getRigidbodyLinearVelocity(rigidBody: RigidBody): Vector3d
+expect fun RigidBody.setRigidbodyLinearVelocity(rigidBody: RigidBody, linearVelocity: Vector3d)
+expect fun RigidBody.getRigidbodyAngularVelocity(rigidBody: RigidBody): Vector3d
+expect fun RigidBody.setRigidbodyAngularVelocity(rigidBody: RigidBody, angularVelocity: Vector3d)
+expect fun RigidBody.getRigidbodyLinearDamping(rigidBody: RigidBody): Double
+expect fun RigidBody.setRigidbodyLinearDamping(rigidBody: RigidBody, linearDamping: Double)
+expect fun RigidBody.getRigidbodyAngularDamping(rigidBody: RigidBody): Double
+expect fun RigidBody.setRigidbodyAngularDamping(rigidBody: RigidBody, angularDamping: Double)
+expect fun RigidBody.getRigidbodyLockTranslation(rigidBody: RigidBody): AxisLock
+expect fun RigidBody.setRigidbodyLockTranslation(rigidBody: RigidBody, lockTranslation: AxisLock)
+expect fun RigidBody.getRigidbodyLockRotation(rigidBody: RigidBody): AxisLock
+expect fun RigidBody.setRigidbodyLockRotation(rigidBody: RigidBody, lockRotation: AxisLock)
+expect fun RigidBody.getRigidbodyChildren(rigidBody: RigidBody): List<Collider>
+expect fun RigidBody.applyImpulse(index: Index, x: Double, y: Double, z: Double)
+expect fun RigidBody.applyTorqueImpulse(index: Index, x: Double, y: Double, z: Double)
+
+expect fun rigidBodyExistsForEntity(entityId: EntityId): Index?
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/scene/SceneLoadHandle.kt b/src/commonMain/kotlin/com/dropbear/scene/SceneLoadHandle.kt
index fa9a446..5c116fa 100644
--- a/src/commonMain/kotlin/com/dropbear/scene/SceneLoadHandle.kt
+++ b/src/commonMain/kotlin/com/dropbear/scene/SceneLoadHandle.kt
@@ -8,7 +8,9 @@ import com.dropbear.ffi.NativeEngine
 /**
  * A handle that allows you to check the state of an async scene load.
  */
-class SceneLoadHandle(val id: Long, val sceneName: String, val native: NativeEngine) {
+class SceneLoadHandle(val id: Long) {
+    val sceneName: String
+        get() = getSceneLoadHandleSceneName(id)
     /**
      * Switches the scene to the requested scene.
      *
@@ -19,18 +21,18 @@ class SceneLoadHandle(val id: Long, val sceneName: String, val native: NativeEng
      * is enabled or not.
      */
     fun switchTo() {
-        native.switchToSceneAsync(this)
+        switchToSceneAsync()
     }
 
     /**
      * Returns the progress of scene load.
      */
     fun progress(): Progress {
-        return native.getSceneLoadProgress(this)
+        return getSceneLoadProgress()
     }
 
     fun status(): SceneLoadStatus {
-        return native.getSceneLoadStatus(this)
+        return getSceneLoadStatus()
     }
 
     /**
@@ -61,4 +63,9 @@ class SceneLoadHandle(val id: Long, val sceneName: String, val native: NativeEng
     fun raw(): Long {
         return id
     }
-}
\ No newline at end of file
+}
+
+expect fun SceneLoadHandle.getSceneLoadHandleSceneName(id: Long): String
+expect fun SceneLoadHandle.switchToSceneAsync()
+expect fun SceneLoadHandle.getSceneLoadProgress(): Progress
+expect fun SceneLoadHandle.getSceneLoadStatus(): SceneLoadStatus
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/scene/SceneManager.kt b/src/commonMain/kotlin/com/dropbear/scene/SceneManager.kt
index cf53fcf..be993ce 100644
--- a/src/commonMain/kotlin/com/dropbear/scene/SceneManager.kt
+++ b/src/commonMain/kotlin/com/dropbear/scene/SceneManager.kt
@@ -1,8 +1,6 @@
 package com.dropbear.scene
 
-import com.dropbear.ffi.NativeEngine
-
-class SceneManager(val native: NativeEngine) {
+class SceneManager() {
     /**
      * Loads the resources of a scene asynchronously.
      *
@@ -11,20 +9,20 @@ class SceneManager(val native: NativeEngine) {
      * and check its status.
      */
     fun loadSceneAsync(sceneName: String): SceneLoadHandle? {
-        return native.loadSceneAsync(sceneName)
+        return loadSceneAsyncNative(sceneName)
     }
 
     /**
      * Loads the resources of a scene asynchronously.
      *
-     * This function is an overload, which contains a `loading_scene` parameter.
+     * This function is an overload, which contains a `loadingScene` parameter.
      * This allows you to specify a scene to display while the resources are being loaded.
      *
      * It must be preloaded (through the scene settings menu in eucalyptus-editor). If not preloaded, it will
-     * block/freeze the main thread/window to load the `loading_scene`
+     * block/freeze the main thread/window to load the `loadingScene`
      */
-    fun loadSceneAsync(sceneName: String, loading_scene: String): SceneLoadHandle? {
-        return native.loadSceneAsync(sceneName, loading_scene)
+    fun loadSceneAsync(sceneName: String, loadingScene: String): SceneLoadHandle? {
+        return loadSceneAsyncNative(sceneName, loadingScene)
     }
 
     /**
@@ -35,13 +33,10 @@ class SceneManager(val native: NativeEngine) {
      * as 3D objects. Use [loadSceneAsync] instead.
      */
     fun switchToSceneImmediate(sceneName: String) {
-        return native.switchToSceneImmediate(sceneName)
+        return switchToSceneImmediateNative(sceneName)
     }
+}
 
-    /**
-     * Fetches the current scene that is currently being rendered.
-     */
-    fun getCurrentScene(): SceneMetadata {
-        TODO("Not implemented yet...")
-    }
-}
\ No newline at end of file
+expect fun SceneManager.loadSceneAsyncNative(sceneName: String): SceneLoadHandle?
+expect fun SceneManager.loadSceneAsyncNative(sceneName: String, loadingScene: String): SceneLoadHandle?
+expect fun SceneManager.switchToSceneImmediateNative(sceneName: String)
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/ui/UIManager.kt b/src/commonMain/kotlin/com/dropbear/ui/UIManager.kt
deleted file mode 100644
index 477288e..0000000
--- a/src/commonMain/kotlin/com/dropbear/ui/UIManager.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.dropbear.ui
-
-import com.dropbear.ffi.NativeEngine
-
-class UIManager(native: NativeEngine) {
-
-}
\ No newline at end of file
diff --git a/src/dropbear.def b/src/dropbear.def
index 8d1d389..1c12c33 100644
--- a/src/dropbear.def
+++ b/src/dropbear.def
@@ -1,4 +1,4 @@
 package: com.dropbear.ffi.generated
 headers: dropbear.h
-headerFilter: *.h components/*.h
+headerFilter: *.h
 libraryPaths: src
\ No newline at end of file
diff --git a/src/jvmMain/java/com/dropbear/DropbearEngineNative.java b/src/jvmMain/java/com/dropbear/DropbearEngineNative.java
new file mode 100644
index 0000000..3068b19
--- /dev/null
+++ b/src/jvmMain/java/com/dropbear/DropbearEngineNative.java
@@ -0,0 +1,11 @@
+package com.dropbear;
+
+public class DropbearEngineNative {
+    static {
+        new EucalyptusCoreLoader().ensureLoaded();
+    }
+
+    public static native Long getEntity(long worldPtr, String label);
+    public static native Long getAsset(long assetHandle, String label);
+    public static native void quit(long commandBufferPtr);
+}
diff --git a/src/jvmMain/java/com/dropbear/EntityRefNative.java b/src/jvmMain/java/com/dropbear/EntityRefNative.java
new file mode 100644
index 0000000..9c79135
--- /dev/null
+++ b/src/jvmMain/java/com/dropbear/EntityRefNative.java
@@ -0,0 +1,12 @@
+package com.dropbear;
+
+public class EntityRefNative {
+    static {
+        new EucalyptusCoreLoader().ensureLoaded();
+    }
+
+    public static native String getEntityLabel(long worldPtr, long entityId);
+    public static native long[] getChildren(long worldPtr, long entityId);
+    public static native Long getChildByLabel(long worldPtr, long entityId, String label);
+    public static native Long getParent(long worldPtr, long entityId);
+}
diff --git a/src/jvmMain/java/com/dropbear/EucalyptusCoreLoader.java b/src/jvmMain/java/com/dropbear/EucalyptusCoreLoader.java
new file mode 100644
index 0000000..5cf3705
--- /dev/null
+++ b/src/jvmMain/java/com/dropbear/EucalyptusCoreLoader.java
@@ -0,0 +1,22 @@
+package com.dropbear;
+
+import com.dropbear.ffi.DynamicLibraryLoader;
+
+import java.lang.System;
+
+public class EucalyptusCoreLoader implements DynamicLibraryLoader {
+    private static boolean loaded = false;
+
+    @Override
+    public void ensureLoaded() {
+        if (!loaded) {
+            System.loadLibrary("eucalyptus_core");
+            loaded = true;
+        }
+    }
+
+    @Override
+    public boolean isLoaded() {
+        return loaded;
+    }
+}
diff --git a/src/jvmMain/java/com/dropbear/NativeEngineLoader.java b/src/jvmMain/java/com/dropbear/NativeEngineLoader.java
deleted file mode 100644
index c267819..0000000
--- a/src/jvmMain/java/com/dropbear/NativeEngineLoader.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.dropbear;
-
-import java.lang.System;
-
-public class NativeEngineLoader {
-    private static boolean loaded = false;
-
-    /// Ensures that the eucalyptus_core library is loaded before accessing
-    /// any of the JNI static functions.
-    public static void ensureLoaded() {
-        if (!loaded) {
-            System.loadLibrary("eucalyptus_core");
-            loaded = true;
-        }
-    }
-}
diff --git a/src/jvmMain/java/com/dropbear/asset/AssetHandleNative.java b/src/jvmMain/java/com/dropbear/asset/AssetHandleNative.java
new file mode 100644
index 0000000..adc49b0
--- /dev/null
+++ b/src/jvmMain/java/com/dropbear/asset/AssetHandleNative.java
@@ -0,0 +1,12 @@
+package com.dropbear.asset;
+
+import com.dropbear.EucalyptusCoreLoader;
+
+public class AssetHandleNative {
+    static {
+        new EucalyptusCoreLoader().ensureLoaded();
+    }
+
+    public static native boolean isModelHandle(long assetRegistryHandle, long handle);
+    public static native boolean isTextureHandle(long assetRegistryHandle, long handle);
+}
diff --git a/src/jvmMain/java/com/dropbear/asset/TextureHandleNative.java b/src/jvmMain/java/com/dropbear/asset/TextureHandleNative.java
new file mode 100644
index 0000000..7fc5a1c
--- /dev/null
+++ b/src/jvmMain/java/com/dropbear/asset/TextureHandleNative.java
@@ -0,0 +1,11 @@
+package com.dropbear.asset;
+
+import com.dropbear.EucalyptusCoreLoader;
+
+public class TextureHandleNative {
+    static {
+        new EucalyptusCoreLoader().ensureLoaded();
+    }
+
+    public static native String getTextureName(long assetRegistryHandle, long handle);
+}
diff --git a/src/jvmMain/java/com/dropbear/components/CameraNative.java b/src/jvmMain/java/com/dropbear/components/CameraNative.java
new file mode 100644
index 0000000..969c2ff
--- /dev/null
+++ b/src/jvmMain/java/com/dropbear/components/CameraNative.java
@@ -0,0 +1,44 @@
+package com.dropbear.components;
+
+import com.dropbear.EucalyptusCoreLoader;
+import com.dropbear.math.Vector3d;
+
+public class CameraNative {
+    static {
+        new EucalyptusCoreLoader().ensureLoaded();
+    }
+
+    public static native boolean cameraExistsForEntity(long worldHandle, long entityId);
+
+    public static native Vector3d getCameraEye(long worldHandle, long entityId);
+    public static native void setCameraEye(long worldHandle, long entityId, Vector3d value);
+
+    public static native Vector3d getCameraTarget(long worldHandle, long entityId);
+    public static native void setCameraTarget(long worldHandle, long entityId, Vector3d value);
+
+    public static native Vector3d getCameraUp(long worldHandle, long entityId);
+    public static native void setCameraUp(long worldHandle, long entityId, Vector3d value);
+
+    public static native double getCameraAspect(long worldHandle, long entityId);
+
+    public static native double getCameraFovY(long worldHandle, long entityId);
+    public static native void setCameraFovY(long worldHandle, long entityId, double value);
+
+    public static native double getCameraZNear(long worldHandle, long entityId);
+    public static native void setCameraZNear(long worldHandle, long entityId, double value);
+
+    public static native double getCameraZFar(long worldHandle, long entityId);
+    public static native void setCameraZFar(long worldHandle, long entityId, double value);
+
+    public static native double getCameraYaw(long worldHandle, long entityId);
+    public static native void setCameraYaw(long worldHandle, long entityId, double value);
+
+    public static native double getCameraPitch(long worldHandle, long entityId);
+    public static native void setCameraPitch(long worldHandle, long entityId, double value);
+
+    public static native double getCameraSpeed(long worldHandle, long entityId);
+    public static native void setCameraSpeed(long worldHandle, long entityId, double value);
+
+    public static native double getCameraSensitivity(long worldHandle, long entityId);
+    public static native void setCameraSensitivity(long worldHandle, long entityId, double value);
+}
\ No newline at end of file
diff --git a/src/jvmMain/java/com/dropbear/components/CustomPropertiesNative.java b/src/jvmMain/java/com/dropbear/components/CustomPropertiesNative.java
new file mode 100644
index 0000000..e1775c0
--- /dev/null
+++ b/src/jvmMain/java/com/dropbear/components/CustomPropertiesNative.java
@@ -0,0 +1,27 @@
+package com.dropbear.components;
+
+import com.dropbear.EucalyptusCoreLoader;
+import com.dropbear.math.Vector3d;
+
+public class CustomPropertiesNative {
+    static {
+        new EucalyptusCoreLoader().ensureLoaded();
+    }
+
+    public static native boolean customPropertiesExistsForEntity(long worldHandle, long entityId);
+
+    public static native String getStringProperty(long worldHandle, long entityId, String label);
+    public static native Integer getIntProperty(long worldHandle, long entityId, String label);
+    public static native Long getLongProperty(long worldHandle, long entityId, String label);
+    public static native Double getDoubleProperty(long worldHandle, long entityId, String label);
+    public static native Float getFloatProperty(long worldHandle, long entityId, String label);
+    public static native Boolean getBoolProperty(long worldHandle, long entityId, String label);
+    public static native Vector3d getVec3Property(long worldHandle, long entityId, String label);
+
+    public static native void setStringProperty(long worldHandle, long entityId, String label, String value);
+    public static native void setIntProperty(long worldHandle, long entityId, String label, int value);
+    public static native void setLongProperty(long worldHandle, long entityId, String label, long value);
+    public static native void setFloatProperty(long worldHandle, long entityId, String label, double value);
+    public static native void setBoolProperty(long worldHandle, long entityId, String label, boolean value);
+    public static native void setVec3Property(long worldHandle, long entityId, String label, Vector3d value);
+}
\ No newline at end of file
diff --git a/src/jvmMain/java/com/dropbear/components/EntityTransformNative.java b/src/jvmMain/java/com/dropbear/components/EntityTransformNative.java
new file mode 100644
index 0000000..37520f7
--- /dev/null
+++ b/src/jvmMain/java/com/dropbear/components/EntityTransformNative.java
@@ -0,0 +1,18 @@
+package com.dropbear.components;
+
+import com.dropbear.EucalyptusCoreLoader;
+import com.dropbear.math.Transform;
+
+public class EntityTransformNative {
+    static {
+        new EucalyptusCoreLoader().ensureLoaded();
+    }
+
+    public static native boolean entityTransformExistsForEntity(long worldPtr, long entityId);
+
+    public static native Transform getLocalTransform(long worldPtr, long entityId);
+    public static native void setLocalTransform(long worldPtr, long entityId, Transform transform);
+    public static native Transform getWorldTransform(long worldPtr, long entityId);
+    public static native void setWorldTransform(long worldPtr, long entityId, Transform transform);
+    public static native Transform propagateTransform(long worldPtr, long entityId);
+}
\ No newline at end of file
diff --git a/src/jvmMain/java/com/dropbear/components/MeshRendererNative.java b/src/jvmMain/java/com/dropbear/components/MeshRendererNative.java
new file mode 100644
index 0000000..4bc8052
--- /dev/null
+++ b/src/jvmMain/java/com/dropbear/components/MeshRendererNative.java
@@ -0,0 +1,17 @@
+package com.dropbear.components;
+
+import com.dropbear.EucalyptusCoreLoader;
+
+public class MeshRendererNative {
+    static {
+        new EucalyptusCoreLoader().ensureLoaded();
+    }
+
+    public static native boolean meshRendererExistsForEntity(long worldHandle, long entityId);
+
+    public static native long getModel(long worldHandle, long entityId);
+    public static native void setModel(long worldHandle, long assetRegistryHandle, long entityId, long modelHandle);
+    public static native long[] getAllTextureIds(long worldHandle, long assetRegistryHandle, long entityId);
+    public static native Long getTexture(long worldHandle, long assetRegistryHandle, long entityId, String materialName);
+    public static native void setTextureOverride(long worldHandle, long assetRegistryHandle, long entityId, String meshName, long textureHandle);
+}
\ No newline at end of file
diff --git a/src/jvmMain/java/com/dropbear/ffi/DropbearEngineNative.java b/src/jvmMain/java/com/dropbear/ffi/DropbearEngineNative.java
deleted file mode 100644
index dedb229..0000000
--- a/src/jvmMain/java/com/dropbear/ffi/DropbearEngineNative.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.dropbear.ffi;
-
-import com.dropbear.NativeEngineLoader;
-
-public class DropbearEngineNative {
-    static {
-        NativeEngineLoader.ensureLoaded();
-    }
-
-    public static native void quit(long graphicsHandle);
-}
diff --git a/src/jvmMain/java/com/dropbear/ffi/DynamicLibraryLoader.java b/src/jvmMain/java/com/dropbear/ffi/DynamicLibraryLoader.java
new file mode 100644
index 0000000..43b5c17
--- /dev/null
+++ b/src/jvmMain/java/com/dropbear/ffi/DynamicLibraryLoader.java
@@ -0,0 +1,12 @@
+package com.dropbear.ffi;
+
+/// Interface for dynamic library loading, allowing for custom components not within the dropbear component
+/// library.
+public interface DynamicLibraryLoader {
+    /// Ensures that the library is loaded before accessing
+    /// any of the JNI static functions.
+    void ensureLoaded();
+
+    /// Forces the library to have a loaded boolean variable.
+    boolean isLoaded();
+}
diff --git a/src/jvmMain/java/com/dropbear/ffi/InputStateNative.java b/src/jvmMain/java/com/dropbear/ffi/InputStateNative.java
deleted file mode 100644
index 0beb819..0000000
--- a/src/jvmMain/java/com/dropbear/ffi/InputStateNative.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.dropbear.ffi;
-
-import com.dropbear.NativeEngineLoader;
-import com.dropbear.input.Gamepad;
-
-public class InputStateNative {
-    static {
-        NativeEngineLoader.ensureLoaded();
-    }
-
-    // input
-    public static native void printInputState(long inputHandle);
-    public static native boolean isKeyPressed(long inputHandle, int ordinal);
-    public static native float[] getMousePosition(long inputHandle);
-    public static native boolean isMouseButtonPressed(long inputHandle, int ordinal);
-    public static native float[] getMouseDelta(long inputHandle);
-    public static native boolean isCursorLocked(long inputHandle);
-    public static native void setCursorLocked(long inputHandle, long graphicsHandle, boolean locked);
-    public static native float[] getLastMousePos(long inputHandle);
-    public static native boolean isCursorHidden(long inputHandle);
-    public static native void setCursorHidden(long inputHandle, long graphicsHandle, boolean hidden);
-
-    // gamepad stuff
-    public static native Gamepad[] getConnectedGamepads(long inputHandle);
-    public static native boolean isGamepadButtonPressed(long inputHandle, long gamepadId, int gamepadButtonOrdinal);
-}
diff --git a/src/jvmMain/java/com/dropbear/ffi/JNINative.java b/src/jvmMain/java/com/dropbear/ffi/JNINative.java
deleted file mode 100644
index 5b8285e..0000000
--- a/src/jvmMain/java/com/dropbear/ffi/JNINative.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.dropbear.ffi;
-
-import com.dropbear.Camera;
-import com.dropbear.EntityTransform;
-import com.dropbear.NativeEngineLoader;
-import com.dropbear.math.Transform;
-import com.dropbear.scene.SceneLoadHandle;
-import com.dropbear.utils.Progress;
-
-/**
- * Describes all the functions that are available in
- * the `eucalyptus_core` dynamic library. 
- */
-public class JNINative {
-    static {
-        NativeEngineLoader.ensureLoaded();
-    }
-    public static native long getEntity(long worldHandle, String label);
-    public static native long getAsset(long assetRegistryHandle, String eucaURI);
-}
diff --git a/src/jvmMain/java/com/dropbear/ffi/PhysicsNative.java b/src/jvmMain/java/com/dropbear/ffi/PhysicsNative.java
deleted file mode 100644
index 069df3d..0000000
--- a/src/jvmMain/java/com/dropbear/ffi/PhysicsNative.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.dropbear.ffi;
-
-import com.dropbear.NativeEngineLoader;
-import com.dropbear.physics.Collider;
-import com.dropbear.physics.RigidBody;
-
-public class PhysicsNative {
-    static {
-        NativeEngineLoader.ensureLoaded();
-    }
-
-    public static native void setPhysicsEnabled(long worldHandle, long physicsEngineHandle, long entityId, boolean enabled);
-    public static native boolean isPhysicsEnabled(long worldHandle, long physicsEngineHandle, long entityId);
-    public static native RigidBody getRigidBody(long worldHandle, long physicsEngineHandle, long entityId);
-    public static native Collider[] getAllColliders(long worldHandle, long physicsEngineHandle, long entityId);
-}
diff --git a/src/jvmMain/java/com/dropbear/ffi/SceneNative.java b/src/jvmMain/java/com/dropbear/ffi/SceneNative.java
deleted file mode 100644
index 3b0e828..0000000
--- a/src/jvmMain/java/com/dropbear/ffi/SceneNative.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.dropbear.ffi;
-
-import com.dropbear.NativeEngineLoader;
-import com.dropbear.scene.SceneLoadHandle;
-import com.dropbear.scene.SceneLoadStatus;
-import com.dropbear.utils.Progress;
-
-public class SceneNative {
-    static {
-        NativeEngineLoader.ensureLoaded();
-    }
-
-    public static native SceneLoadHandle loadSceneAsync(long commandBufferHandle, long sceneLoader, String sceneName, NativeEngine engine);
-    public static native SceneLoadHandle loadSceneAsync(long commandBufferHandle, long sceneLoader, String sceneName, String loadingScene, NativeEngine engine);
-    public static native void switchToSceneAsync(long commandBufferHandle, SceneLoadHandle handle);
-    public static native void switchToSceneImmediate(long commandBufferHandle, String sceneName);
-    public static native Progress getSceneLoadProgress(long sceneLoader, SceneLoadHandle handle);
-    public static native SceneLoadStatus getSceneLoadStatus(long sceneLoader, SceneLoadHandle handle);
-}
\ No newline at end of file
diff --git a/src/jvmMain/java/com/dropbear/ffi/components/CameraNative.java b/src/jvmMain/java/com/dropbear/ffi/components/CameraNative.java
deleted file mode 100644
index 0614a62..0000000
--- a/src/jvmMain/java/com/dropbear/ffi/components/CameraNative.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.dropbear.ffi.components;
-
-import com.dropbear.Camera;
-import com.dropbear.NativeEngineLoader;
-
-public class CameraNative {
-    static {
-        NativeEngineLoader.ensureLoaded();
-    }
-
-    public static native Camera getCamera(long worldHandle, String label);
-    public static native Camera getAttachedCamera(long worldHandle, long entityHandle);
-    public static native void setCamera(long worldHandle, Camera camera);
-}
diff --git a/src/jvmMain/java/com/dropbear/ffi/components/ColliderNative.java b/src/jvmMain/java/com/dropbear/ffi/components/ColliderNative.java
deleted file mode 100644
index 59795c7..0000000
--- a/src/jvmMain/java/com/dropbear/ffi/components/ColliderNative.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.dropbear.ffi.components;
-
-import com.dropbear.physics.Collider;
-
-public class ColliderNative {
-    static {
-        com.dropbear.NativeEngineLoader.ensureLoaded();
-    }
-
-    public static native void setCollider(long worldHandle, long physicsEngineHandle, Collider collider);
-}
diff --git a/src/jvmMain/java/com/dropbear/ffi/components/CustomPropertiesNative.java b/src/jvmMain/java/com/dropbear/ffi/components/CustomPropertiesNative.java
deleted file mode 100644
index b0a9ae6..0000000
--- a/src/jvmMain/java/com/dropbear/ffi/components/CustomPropertiesNative.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.dropbear.ffi.components;
-
-import com.dropbear.NativeEngineLoader;
-
-public class CustomPropertiesNative {
-    static {
-        NativeEngineLoader.ensureLoaded();
-    }
-
-    // properties
-    public static native String getStringProperty(long worldHandle, long entityHandle, String label);
-    public static native int getIntProperty(long worldHandle, long entityHandle, String label);
-    public static native long getLongProperty(long worldHandle, long entityHandle, String label);
-    public static native double getFloatProperty(long worldHandle, long entityHandle, String label);
-    public static native boolean getBoolProperty(long worldHandle, long entityHandle, String label);
-    public static native float[] getVec3Property(long worldHandle, long entityHandle, String label);
-
-    public static native void setStringProperty(long worldHandle, long entityHandle, String label, String value);
-    public static native void setIntProperty(long worldHandle, long entityHandle, String label, int value);
-    public static native void setLongProperty(long worldHandle, long entityHandle, String label, long value);
-    public static native void setFloatProperty(long worldHandle, long entityHandle, String label, double value);
-    public static native void setBoolProperty(long worldHandle, long entityHandle, String label, boolean value);
-    public static native void setVec3Property(long worldHandle, long entityHandle, String label, float[] value);
-
-}
diff --git a/src/jvmMain/java/com/dropbear/ffi/components/EntityTransformNative.java b/src/jvmMain/java/com/dropbear/ffi/components/EntityTransformNative.java
deleted file mode 100644
index 4e92fbf..0000000
--- a/src/jvmMain/java/com/dropbear/ffi/components/EntityTransformNative.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.dropbear.ffi.components;
-
-import com.dropbear.EntityTransform;
-import com.dropbear.NativeEngineLoader;
-import com.dropbear.math.Transform;
-
-public class EntityTransformNative {
-    static {
-        NativeEngineLoader.ensureLoaded();
-    }
-
-    public static native EntityTransform getTransform(long handle, long entityHandle);
-    public static native Transform propagateTransform(long worldHandle, long id);
-    public static native void setTransform(long worldHandle, long id, EntityTransform transform);
-}
diff --git a/src/jvmMain/java/com/dropbear/ffi/components/HierarchyNative.java b/src/jvmMain/java/com/dropbear/ffi/components/HierarchyNative.java
deleted file mode 100644
index a5f51e6..0000000
--- a/src/jvmMain/java/com/dropbear/ffi/components/HierarchyNative.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.dropbear.ffi.components;
-
-import com.dropbear.NativeEngineLoader;
-
-public class HierarchyNative {
-    static {
-        NativeEngineLoader.ensureLoaded();
-    }
-
-    public static native long[] getChildren(long worldHandle, long entityId);
-    public static native long getChildByLabel(long worldHandle, long entityId, String label);
-    public static native long getParent(long worldHandle, long entityId);
-}
diff --git a/src/jvmMain/java/com/dropbear/ffi/components/LabelNative.java b/src/jvmMain/java/com/dropbear/ffi/components/LabelNative.java
deleted file mode 100644
index 9b51b86..0000000
--- a/src/jvmMain/java/com/dropbear/ffi/components/LabelNative.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.dropbear.ffi.components;
-
-import com.dropbear.NativeEngineLoader;
-
-public class LabelNative {
-    static {
-        NativeEngineLoader.ensureLoaded();
-    }
-
-    public static native String getEntityLabel(long worldHandle, long entityHandle);
-}
diff --git a/src/jvmMain/java/com/dropbear/ffi/components/MeshRendererNative.java b/src/jvmMain/java/com/dropbear/ffi/components/MeshRendererNative.java
deleted file mode 100644
index 0c32801..0000000
--- a/src/jvmMain/java/com/dropbear/ffi/components/MeshRendererNative.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.dropbear.ffi.components;
-
-import com.dropbear.NativeEngineLoader;
-
-public class MeshRendererNative {
-    static {
-        NativeEngineLoader.ensureLoaded();
-    }
-
-    // model
-    public static native long getModel(long worldHandle, long entityHandle);
-    public static native void setModel(long worldHandle, long assetHandle, long entityHandle, long modelHandle);
-    public static native boolean isModelHandle(long assetRegistryHandle, long handle);
-    public static native boolean isUsingModel(long worldHandle, long entityHandle, long modelHandle);
-
-    // texture
-    public static native long getTexture(long worldHandle, long assetHandle, long entityHandle, String name);
-    public static native String getTextureName(long assetHandle, long textureHandle);
-    public static native void setTexture(long worldHandle, long assetRegistryHandle, long entityHandle,
-                                         String oldMaterialName, long textureHandle);
-    public static native boolean isTextureHandle(long assetRegistryHandle, long handle);
-    public static native boolean isUsingTexture(long worldHandle, long entityHandle, long textureHandle);
-
-    public static native String[] getAllTextures(long worldHandle, long entityHandle);
-}
diff --git a/src/jvmMain/java/com/dropbear/ffi/components/RigidBodyNative.java b/src/jvmMain/java/com/dropbear/ffi/components/RigidBodyNative.java
deleted file mode 100644
index fa4f6c0..0000000
--- a/src/jvmMain/java/com/dropbear/ffi/components/RigidBodyNative.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.dropbear.ffi.components;
-
-import com.dropbear.physics.Collider;
-import com.dropbear.physics.Index;
-import com.dropbear.physics.RigidBody;
-
-public class RigidBodyNative {
-    static {
-        com.dropbear.NativeEngineLoader.ensureLoaded();
-    }
-
-    public static native void applyImpulse(long physicsEngineHandle, Index rigidBodyId, double x, double y, double z);
-    public static native void applyTorqueImpulse(long physicsEngineHandle, Index rigidBodyId, double x, double y, double z);
-
-    public static native void setRigidBody(long worldHandle, long physicsEngineHandle, RigidBody rigidBody);
-    public static native Collider[] getChildColliders(long worldHandle, long physicsEngineHandle, Index rigidBodyId);
-}
\ No newline at end of file
diff --git a/src/jvmMain/java/com/dropbear/input/GamepadNative.java b/src/jvmMain/java/com/dropbear/input/GamepadNative.java
new file mode 100644
index 0000000..7ae48fa
--- /dev/null
+++ b/src/jvmMain/java/com/dropbear/input/GamepadNative.java
@@ -0,0 +1,14 @@
+package com.dropbear.input;
+
+import com.dropbear.EucalyptusCoreLoader;
+import com.dropbear.math.Vector2d;
+
+public class GamepadNative {
+    static {
+        new EucalyptusCoreLoader().ensureLoaded();
+    }
+
+    public static native boolean isGamepadButtonPressed(long inputStateHandle, long gamepad, int gamepadButton);
+    public static native Vector2d getLeftStickPosition(long inputStateHandle, long gamepad);
+    public static native Vector2d getRightStickPosition(long inputStateHandle, long gamepad);
+}
diff --git a/src/jvmMain/java/com/dropbear/input/InputStateNative.java b/src/jvmMain/java/com/dropbear/input/InputStateNative.java
new file mode 100644
index 0000000..1f47bc3
--- /dev/null
+++ b/src/jvmMain/java/com/dropbear/input/InputStateNative.java
@@ -0,0 +1,22 @@
+package com.dropbear.input;
+
+import com.dropbear.EucalyptusCoreLoader;
+import com.dropbear.math.Vector2d;
+
+public class InputStateNative {
+    static {
+        new EucalyptusCoreLoader().ensureLoaded();
+    }
+
+    public static native void printInputState(long inputStateHandle);
+    public static native boolean isKeyPressed(long inputStateHandle, int keyCode);
+    public static native Vector2d getMousePosition(long inputStateHandle);
+    public static native boolean isMouseButtonPressed(long inputStateHandle, MouseButton mouseButton);
+    public static native Vector2d getMouseDelta(long inputStateHandle);
+    public static native boolean isCursorLocked(long inputStateHandle);
+    public static native void setCursorLocked(long commandBufferPtr, long inputStateHandle, boolean locked);
+    public static native Vector2d getLastMousePos(long inputStateHandle);
+    public static native boolean isCursorHidden(long inputStateHandle);
+    public static native void setCursorHidden(long commandBufferPtr, long inputStateHandle, boolean hidden);
+    public static native long[] getConnectedGamepads(long inputStateHandle);
+}
diff --git a/src/jvmMain/java/com/dropbear/physics/ColliderGroupNative.java b/src/jvmMain/java/com/dropbear/physics/ColliderGroupNative.java
new file mode 100644
index 0000000..faa5998
--- /dev/null
+++ b/src/jvmMain/java/com/dropbear/physics/ColliderGroupNative.java
@@ -0,0 +1,12 @@
+package com.dropbear.physics;
+
+import com.dropbear.EucalyptusCoreLoader;
+
+public class ColliderGroupNative {
+    static {
+        new EucalyptusCoreLoader().ensureLoaded();
+    }
+
+    public static native Collider[] getColliderGroupColliders(long worldPtr, long physicsPtr, long entityId);
+    public static native boolean colliderGroupExistsForEntity(long worldPtr, long entityId);
+}
\ No newline at end of file
diff --git a/src/jvmMain/java/com/dropbear/physics/ColliderNative.java b/src/jvmMain/java/com/dropbear/physics/ColliderNative.java
new file mode 100644
index 0000000..e1a2052
--- /dev/null
+++ b/src/jvmMain/java/com/dropbear/physics/ColliderNative.java
@@ -0,0 +1,34 @@
+package com.dropbear.physics;
+
+import com.dropbear.EucalyptusCoreLoader;
+import com.dropbear.math.Vector3d;
+
+public class ColliderNative {
+    static {
+        new EucalyptusCoreLoader().ensureLoaded();
+    }
+
+    public static native ColliderShape getColliderShape(long physicsPtr, Collider collider);
+    public static native void setColliderShape(long physicsPtr, Collider collider, ColliderShape shape);
+
+    public static native double getColliderDensity(long physicsPtr, Collider collider);
+    public static native void setColliderDensity(long physicsPtr, Collider collider, double density);
+
+    public static native double getColliderFriction(long physicsPtr, Collider collider);
+    public static native void setColliderFriction(long physicsPtr, Collider collider, double friction);
+
+    public static native double getColliderRestitution(long physicsPtr, Collider collider);
+    public static native void setColliderRestitution(long physicsPtr, Collider collider, double restitution);
+
+    public static native double getColliderMass(long physicsPtr, Collider collider);
+    public static native void setColliderMass(long physicsPtr, Collider collider, double mass);
+
+    public static native boolean getColliderIsSensor(long physicsPtr, Collider collider);
+    public static native void setColliderIsSensor(long physicsPtr, Collider collider, boolean isSensor);
+
+    public static native Vector3d getColliderTranslation(long physicsPtr, Collider collider);
+    public static native void setColliderTranslation(long physicsPtr, Collider collider, Vector3d translation);
+
+    public static native Vector3d getColliderRotation(long physicsPtr, Collider collider);
+    public static native void setColliderRotation(long physicsPtr, Collider collider, Vector3d rotation);
+}
\ No newline at end of file
diff --git a/src/jvmMain/java/com/dropbear/physics/PhysicsNative.java b/src/jvmMain/java/com/dropbear/physics/PhysicsNative.java
new file mode 100644
index 0000000..f6476f2
--- /dev/null
+++ b/src/jvmMain/java/com/dropbear/physics/PhysicsNative.java
@@ -0,0 +1,13 @@
+package com.dropbear.physics;
+
+import com.dropbear.EucalyptusCoreLoader;
+import com.dropbear.math.Vector3d;
+
+public class PhysicsNative {
+    static {
+        new EucalyptusCoreLoader().ensureLoaded();
+    }
+
+    public static native Vector3d getGravity(long physicsHandle);
+    public static native void setGravity(long physicsHandle, Vector3d gravity);
+}
diff --git a/src/jvmMain/java/com/dropbear/physics/RigidBodyNative.java b/src/jvmMain/java/com/dropbear/physics/RigidBodyNative.java
new file mode 100644
index 0000000..cf3fa23
--- /dev/null
+++ b/src/jvmMain/java/com/dropbear/physics/RigidBodyNative.java
@@ -0,0 +1,47 @@
+package com.dropbear.physics;
+
+import com.dropbear.EucalyptusCoreLoader;
+import com.dropbear.math.Vector3d;
+
+public class RigidBodyNative {
+    static {
+        new EucalyptusCoreLoader().ensureLoaded();
+    }
+
+    public static native Index rigidBodyExistsForEntity(long worldPtr, long physicsPtr, long entityId);
+
+    public static native int getRigidBodyMode(long worldPtr, long physicsPtr, RigidBody rigidBody);
+    public static native void setRigidBodyMode(long worldPtr, long physicsPtr, RigidBody rigidBody, int mode);
+
+    public static native double getRigidBodyGravityScale(long worldPtr, long physicsPtr, RigidBody rigidBody);
+    public static native void setRigidBodyGravityScale(long worldPtr, long physicsPtr, RigidBody rigidBody, double gravityScale);
+
+    public static native double getRigidBodyLinearDamping(long worldPtr, long physicsPtr, RigidBody rigidBody);
+    public static native void setRigidBodyLinearDamping(long worldPtr, long physicsPtr, RigidBody rigidBody, double linearDamping);
+
+    public static native double getRigidBodyAngularDamping(long worldPtr, long physicsPtr, RigidBody rigidBody);
+    public static native void setRigidBodyAngularDamping(long worldPtr, long physicsPtr, RigidBody rigidBody, double angularDamping);
+
+    public static native boolean getRigidBodySleep(long worldPtr, long physicsPtr, RigidBody rigidBody);
+    public static native void setRigidBodySleep(long worldPtr, long physicsPtr, RigidBody rigidBody, boolean canSleep);
+
+    public static native boolean getRigidBodyCcdEnabled(long worldPtr, long physicsPtr, RigidBody rigidBody);
+    public static native void setRigidBodyCcdEnabled(long worldPtr, long physicsPtr, RigidBody rigidBody, boolean ccdEnabled);
+
+    public static native Vector3d getRigidBodyLinearVelocity(long worldPtr, long physicsPtr, RigidBody rigidBody);
+    public static native void setRigidBodyLinearVelocity(long worldPtr, long physicsPtr, RigidBody rigidBody, Vector3d linearVelocity);
+
+    public static native Vector3d getRigidBodyAngularVelocity(long worldPtr, long physicsPtr, RigidBody rigidBody);
+    public static native void setRigidBodyAngularVelocity(long worldPtr, long physicsPtr, RigidBody rigidBody, Vector3d angularVelocity);
+
+    public static native AxisLock getRigidBodyLockTranslation(long worldPtr, long physicsPtr, RigidBody rigidBody);
+    public static native void setRigidBodyLockTranslation(long worldPtr, long physicsPtr, RigidBody rigidBody, AxisLock lockTranslation);
+
+    public static native AxisLock getRigidBodyLockRotation(long worldPtr, long physicsPtr, RigidBody rigidBody);
+    public static native void setRigidBodyLockRotation(long worldPtr, long physicsPtr, RigidBody rigidBody, AxisLock lockRotation);
+
+    public static native Collider[] getRigidBodyChildren(long worldPtr, long physicsPtr, RigidBody rigidBody);
+
+    public static native void applyImpulse(long worldPtr, long physicsPtr, RigidBody rigidBody, double x, double y, double z);
+    public static native void applyTorqueImpulse(long worldPtr, long physicsPtr, RigidBody rigidBody, double x, double y, double z);
+}
\ No newline at end of file
diff --git a/src/jvmMain/java/com/dropbear/scene/SceneLoadHandleNative.java b/src/jvmMain/java/com/dropbear/scene/SceneLoadHandleNative.java
new file mode 100644
index 0000000..28f72df
--- /dev/null
+++ b/src/jvmMain/java/com/dropbear/scene/SceneLoadHandleNative.java
@@ -0,0 +1,15 @@
+package com.dropbear.scene;
+
+import com.dropbear.EucalyptusCoreLoader;
+import com.dropbear.utils.Progress;
+
+public class SceneLoadHandleNative {
+    static {
+        new EucalyptusCoreLoader().ensureLoaded();
+    }
+
+    public static native String getSceneLoadHandleSceneName(long sceneLoaderHandle, long sceneId);
+    public static native void switchToSceneAsync(long commandBufferPtr, long sceneId);
+    public static native Progress getSceneLoadProgress(long sceneLoaderHandle, long sceneId);
+    public static native int getSceneLoadStatus(long sceneLoaderHandle, long sceneId);
+}
diff --git a/src/jvmMain/java/com/dropbear/scene/SceneManagerNative.java b/src/jvmMain/java/com/dropbear/scene/SceneManagerNative.java
new file mode 100644
index 0000000..7788660
--- /dev/null
+++ b/src/jvmMain/java/com/dropbear/scene/SceneManagerNative.java
@@ -0,0 +1,13 @@
+package com.dropbear.scene;
+
+import com.dropbear.EucalyptusCoreLoader;
+
+public class SceneManagerNative {
+    static {
+        new EucalyptusCoreLoader().ensureLoaded();
+    }
+
+    public static native Long loadSceneAsyncNative(long commandBufferPtr, long sceneManagerHandle, String sceneName);
+    public static native Long loadSceneAsyncNative(long commandBufferPtr, long sceneManagerHandle, String sceneName, String loadingScene);
+    public static native void switchToSceneImmediateNative(long commandBufferPtr, String sceneName);
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/DropbearEngine.jvm.kt b/src/jvmMain/kotlin/com/dropbear/DropbearEngine.jvm.kt
new file mode 100644
index 0000000..e9774cb
--- /dev/null
+++ b/src/jvmMain/kotlin/com/dropbear/DropbearEngine.jvm.kt
@@ -0,0 +1,15 @@
+package com.dropbear
+
+import com.dropbear.components.Camera
+
+actual fun getEntity(label: String): Long? {
+    return DropbearEngineNative.getEntity(DropbearEngine.native.worldHandle, label)
+}
+
+actual fun getAsset(eucaURI: String): Long? {
+    return DropbearEngineNative.getAsset(DropbearEngine.native.assetHandle, eucaURI)
+}
+
+actual fun quit() {
+    DropbearEngineNative.quit(DropbearEngine.native.commandBufferHandle)
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/EntityRef.jvm.kt b/src/jvmMain/kotlin/com/dropbear/EntityRef.jvm.kt
new file mode 100644
index 0000000..f7e1970
--- /dev/null
+++ b/src/jvmMain/kotlin/com/dropbear/EntityRef.jvm.kt
@@ -0,0 +1,32 @@
+package com.dropbear
+
+actual fun EntityRef.getEntityLabel(entity: EntityId): String {
+    return EntityRefNative.getEntityLabel(DropbearEngine.native.worldHandle, entity.raw)
+        ?: throw RuntimeException("All entities are expected to contain a \"Label\" component. If not, its an engine bug or you messed around with the scene config...")
+}
+
+actual fun EntityRef.getChildren(entityId: EntityId): Array<EntityRef>? {
+    return EntityRefNative.getChildren(DropbearEngine.native.worldHandle, entityId.raw)
+        .map { EntityRef(EntityId(it)) }.toList().toTypedArray()
+}
+
+actual fun EntityRef.getChildByLabel(
+    entityId: EntityId,
+    label: String
+): EntityRef? {
+    val result = EntityRefNative.getChildByLabel(DropbearEngine.native.worldHandle, entityId.raw, label)
+    return if (result != null) {
+        EntityRef(EntityId(result))
+    } else {
+        null
+    }
+}
+
+actual fun EntityRef.getParent(entityId: EntityId): EntityRef? {
+    val result = EntityRefNative.getParent(DropbearEngine.native.worldHandle, entityId.raw)
+    return if (result != null) {
+        EntityRef(EntityId(result))
+    } else {
+        null
+    }
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/asset/AssetHandle.jvm.kt b/src/jvmMain/kotlin/com/dropbear/asset/AssetHandle.jvm.kt
new file mode 100644
index 0000000..477b388
--- /dev/null
+++ b/src/jvmMain/kotlin/com/dropbear/asset/AssetHandle.jvm.kt
@@ -0,0 +1,11 @@
+package com.dropbear.asset
+
+import com.dropbear.DropbearEngine
+
+actual fun isModelHandle(id: Long): Boolean {
+    return AssetHandleNative.isModelHandle(DropbearEngine.native.assetHandle, id)
+}
+
+actual fun isTextureHandle(id: Long): Boolean {
+    return AssetHandleNative.isTextureHandle(DropbearEngine.native.assetHandle, id)
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/asset/TextureHandle.jvm.kt b/src/jvmMain/kotlin/com/dropbear/asset/TextureHandle.jvm.kt
new file mode 100644
index 0000000..575aa6d
--- /dev/null
+++ b/src/jvmMain/kotlin/com/dropbear/asset/TextureHandle.jvm.kt
@@ -0,0 +1,7 @@
+package com.dropbear.asset
+
+import com.dropbear.DropbearEngine
+
+actual fun TextureHandle.getTextureName(id: Long): String? {
+    return TextureHandleNative.getTextureName(DropbearEngine.native.assetHandle, id)
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/components/Camera.jvm.kt b/src/jvmMain/kotlin/com/dropbear/components/Camera.jvm.kt
new file mode 100644
index 0000000..64e328f
--- /dev/null
+++ b/src/jvmMain/kotlin/com/dropbear/components/Camera.jvm.kt
@@ -0,0 +1,96 @@
+package com.dropbear.components
+
+import com.dropbear.DropbearEngine
+import com.dropbear.EntityId
+import com.dropbear.math.Vector3d
+
+actual fun Camera.getCameraEye(entity: EntityId): Vector3d {
+    return CameraNative.getCameraEye(DropbearEngine.native.worldHandle, entity.raw)
+        ?: Vector3d.zero()
+}
+
+actual fun Camera.setCameraEye(entity: EntityId, value: Vector3d) {
+    CameraNative.setCameraEye(DropbearEngine.native.worldHandle, entity.raw, value)
+}
+
+actual fun Camera.getCameraTarget(entity: EntityId): Vector3d {
+    return CameraNative.getCameraTarget(DropbearEngine.native.worldHandle, entity.raw)
+        ?: Vector3d.zero()
+}
+
+actual fun Camera.setCameraTarget(entity: EntityId, value: Vector3d) {
+    CameraNative.setCameraTarget(DropbearEngine.native.worldHandle, entity.raw, value)
+}
+
+actual fun Camera.getCameraUp(entity: EntityId): Vector3d {
+    return CameraNative.getCameraUp(DropbearEngine.native.worldHandle, entity.raw)
+        ?: Vector3d.up()
+}
+
+actual fun Camera.setCameraUp(entity: EntityId, value: Vector3d) {
+    CameraNative.setCameraUp(DropbearEngine.native.worldHandle, entity.raw, value)
+}
+
+actual fun Camera.getCameraAspect(entity: EntityId): Double {
+    return CameraNative.getCameraAspect(DropbearEngine.native.worldHandle, entity.raw)
+}
+
+actual fun Camera.getCameraFovY(entity: EntityId): Double {
+    return CameraNative.getCameraFovY(DropbearEngine.native.worldHandle, entity.raw)
+}
+
+actual fun Camera.setCameraFovY(entity: EntityId, value: Double) {
+    CameraNative.setCameraFovY(DropbearEngine.native.worldHandle, entity.raw, value)
+}
+
+actual fun Camera.getCameraZNear(entity: EntityId): Double {
+    return CameraNative.getCameraZNear(DropbearEngine.native.worldHandle, entity.raw)
+}
+
+actual fun Camera.setCameraZNear(entity: EntityId, value: Double) {
+    CameraNative.setCameraZNear(DropbearEngine.native.worldHandle, entity.raw, value)
+}
+
+actual fun Camera.getCameraZFar(entity: EntityId): Double {
+    return CameraNative.getCameraZFar(DropbearEngine.native.worldHandle, entity.raw)
+}
+
+actual fun Camera.setCameraZFar(entity: EntityId, value: Double) {
+    CameraNative.setCameraZFar(DropbearEngine.native.worldHandle, entity.raw, value)
+}
+
+actual fun Camera.getCameraYaw(entity: EntityId): Double {
+    return CameraNative.getCameraYaw(DropbearEngine.native.worldHandle, entity.raw)
+}
+
+actual fun Camera.setCameraYaw(entity: EntityId, value: Double) {
+    CameraNative.setCameraYaw(DropbearEngine.native.worldHandle, entity.raw, value)
+}
+
+actual fun Camera.getCameraPitch(entity: EntityId): Double {
+    return CameraNative.getCameraPitch(DropbearEngine.native.worldHandle, entity.raw)
+}
+
+actual fun Camera.setCameraPitch(entity: EntityId, value: Double) {
+    CameraNative.setCameraPitch(DropbearEngine.native.worldHandle, entity.raw, value)
+}
+
+actual fun Camera.getCameraSpeed(entity: EntityId): Double {
+    return CameraNative.getCameraSpeed(DropbearEngine.native.worldHandle, entity.raw)
+}
+
+actual fun Camera.setCameraSpeed(entity: EntityId, value: Double) {
+    CameraNative.setCameraSpeed(DropbearEngine.native.worldHandle, entity.raw, value)
+}
+
+actual fun Camera.getCameraSensitivity(entity: EntityId): Double {
+    return CameraNative.getCameraSensitivity(DropbearEngine.native.worldHandle, entity.raw)
+}
+
+actual fun Camera.setCameraSensitivity(entity: EntityId, value: Double) {
+    CameraNative.setCameraSensitivity(DropbearEngine.native.worldHandle, entity.raw, value)
+}
+
+actual fun cameraExistsForEntity(entity: EntityId): Boolean {
+    return CameraNative.cameraExistsForEntity(DropbearEngine.native.worldHandle, entity.raw)
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/components/CustomProperties.jvm.kt b/src/jvmMain/kotlin/com/dropbear/components/CustomProperties.jvm.kt
new file mode 100644
index 0000000..086d64a
--- /dev/null
+++ b/src/jvmMain/kotlin/com/dropbear/components/CustomProperties.jvm.kt
@@ -0,0 +1,103 @@
+package com.dropbear.components
+
+import com.dropbear.DropbearEngine
+import com.dropbear.EntityId
+import com.dropbear.math.Vector3d
+
+actual fun CustomProperties.getStringProperty(
+    entityHandle: Long,
+    label: String
+): String? {
+    return CustomPropertiesNative.getStringProperty(DropbearEngine.native.worldHandle, entityHandle, label)
+}
+
+actual fun CustomProperties.getIntProperty(entityHandle: Long, label: String): Int? {
+    return CustomPropertiesNative.getIntProperty(DropbearEngine.native.worldHandle, entityHandle, label)
+}
+
+actual fun CustomProperties.getLongProperty(
+    entityHandle: Long,
+    label: String
+): Long? {
+    return CustomPropertiesNative.getLongProperty(DropbearEngine.native.worldHandle, entityHandle, label)
+}
+
+actual fun CustomProperties.getDoubleProperty(
+    entityHandle: Long,
+    label: String
+): Double? {
+    return CustomPropertiesNative.getDoubleProperty(DropbearEngine.native.worldHandle, entityHandle, label)
+}
+
+actual fun CustomProperties.getFloatProperty(
+    entityHandle: Long,
+    label: String
+): Float? {
+    return CustomPropertiesNative.getFloatProperty(DropbearEngine.native.worldHandle, entityHandle, label)
+}
+
+actual fun CustomProperties.getBoolProperty(
+    entityHandle: Long,
+    label: String
+): Boolean? {
+    return CustomPropertiesNative.getBoolProperty(DropbearEngine.native.worldHandle, entityHandle, label)
+}
+
+actual fun CustomProperties.getVec3Property(
+    entityHandle: Long,
+    label: String
+): Vector3d? {
+    return CustomPropertiesNative.getVec3Property(DropbearEngine.native.worldHandle, entityHandle, label)
+}
+
+actual fun CustomProperties.setStringProperty(
+    entityHandle: Long,
+    label: String,
+    value: String
+) {
+    CustomPropertiesNative.setStringProperty(DropbearEngine.native.worldHandle, entityHandle, label, value)
+}
+
+actual fun CustomProperties.setIntProperty(
+    entityHandle: Long,
+    label: String,
+    value: Int
+) {
+    CustomPropertiesNative.setIntProperty(DropbearEngine.native.worldHandle, entityHandle, label, value)
+}
+
+actual fun CustomProperties.setLongProperty(
+    entityHandle: Long,
+    label: String,
+    value: Long
+) {
+    CustomPropertiesNative.setLongProperty(DropbearEngine.native.worldHandle, entityHandle, label, value)
+}
+
+actual fun CustomProperties.setFloatProperty(
+    entityHandle: Long,
+    label: String,
+    value: Double
+) {
+    CustomPropertiesNative.setFloatProperty(DropbearEngine.native.worldHandle, entityHandle, label, value)
+}
+
+actual fun CustomProperties.setBoolProperty(
+    entityHandle: Long,
+    label: String,
+    value: Boolean
+) {
+    CustomPropertiesNative.setBoolProperty(DropbearEngine.native.worldHandle, entityHandle, label, value)
+}
+
+actual fun CustomProperties.setVec3Property(
+    entityHandle: Long,
+    label: String,
+    value: Vector3d
+) {
+    CustomPropertiesNative.setVec3Property(DropbearEngine.native.worldHandle, entityHandle, label, value)
+}
+
+actual fun customPropertiesExistsForEntity(entityId: EntityId): Boolean {
+    return CustomPropertiesNative.customPropertiesExistsForEntity(DropbearEngine.native.worldHandle, entityId.raw)
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/components/EntityTransform.jvm.kt b/src/jvmMain/kotlin/com/dropbear/components/EntityTransform.jvm.kt
new file mode 100644
index 0000000..9f429a3
--- /dev/null
+++ b/src/jvmMain/kotlin/com/dropbear/components/EntityTransform.jvm.kt
@@ -0,0 +1,44 @@
+package com.dropbear.components
+
+import com.dropbear.DropbearEngine
+import com.dropbear.EntityId
+import com.dropbear.math.Transform
+
+actual fun entityTransformExistsForEntity(entityId: EntityId): Boolean {
+    return EntityTransformNative.entityTransformExistsForEntity(DropbearEngine.native.worldHandle, entityId.raw)
+}
+
+actual fun EntityTransform.getLocalTransform(entityId: EntityId): Transform {
+    return EntityTransformNative.getLocalTransform(DropbearEngine.native.worldHandle, entityId.raw)
+        ?: Transform.identity()
+}
+
+actual fun EntityTransform.setLocalTransform(
+    entityId: EntityId,
+    transform: Transform
+) {
+    EntityTransformNative.setLocalTransform(
+        DropbearEngine.native.worldHandle,
+        entityId.raw,
+        transform
+    )
+}
+
+actual fun EntityTransform.getWorldTransform(entityId: EntityId): Transform {
+    return EntityTransformNative.getWorldTransform(DropbearEngine.native.worldHandle, entityId.raw) ?: Transform.identity()
+}
+
+actual fun EntityTransform.setWorldTransform(
+    entityId: EntityId,
+    transform: Transform
+) {
+    EntityTransformNative.setWorldTransform(
+        DropbearEngine.native.worldHandle,
+        entityId.raw,
+        transform
+    )
+}
+
+actual fun EntityTransform.propagateTransform(entityId: EntityId): Transform? {
+    return EntityTransformNative.propagateTransform(DropbearEngine.native.worldHandle, entityId.raw)
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/components/MeshRenderer.jvm.kt b/src/jvmMain/kotlin/com/dropbear/components/MeshRenderer.jvm.kt
new file mode 100644
index 0000000..3058546
--- /dev/null
+++ b/src/jvmMain/kotlin/com/dropbear/components/MeshRenderer.jvm.kt
@@ -0,0 +1,63 @@
+package com.dropbear.components
+
+import com.dropbear.DropbearEngine
+import com.dropbear.EntityId
+import com.dropbear.asset.ModelHandle
+import com.dropbear.asset.TextureHandle
+
+actual fun MeshRenderer.getModel(id: EntityId): ModelHandle? {
+    return ModelHandle(MeshRendererNative.getModel(DropbearEngine.native.worldHandle, id.raw))
+}
+
+actual fun MeshRenderer.setModel(id: EntityId, model: ModelHandle?) {
+    if (model == null) {
+        throw IllegalArgumentException("ModelHandle cannot be null")
+    }
+
+    return MeshRendererNative.setModel(
+        DropbearEngine.native.worldHandle,
+        DropbearEngine.native.assetHandle,
+        id.raw,
+        model.raw()
+    )
+}
+
+actual fun MeshRenderer.getAllTextureIds(id: EntityId): List<TextureHandle>? {
+    val textureHandles = MeshRendererNative.getAllTextureIds(
+        DropbearEngine.native.worldHandle,
+        DropbearEngine.native.assetHandle,
+        id.raw
+    ) ?: return null
+
+    return textureHandles.map { TextureHandle(it) }
+}
+
+actual fun MeshRenderer.getTexture(id: EntityId, materialName: String): Long? {
+    return MeshRendererNative.getTexture(
+        DropbearEngine.native.worldHandle,
+        DropbearEngine.native.assetHandle,
+        id.raw,
+        materialName
+    )
+}
+
+actual fun MeshRenderer.setTextureOverride(
+    id: EntityId,
+    materialName: String,
+    textureHandle: Long
+) {
+    return MeshRendererNative.setTextureOverride(
+        DropbearEngine.native.worldHandle,
+        DropbearEngine.native.assetHandle,
+        id.raw,
+        materialName,
+        textureHandle
+    )
+}
+
+actual fun meshRendererExistsForEntity(entityId: EntityId): Boolean {
+    return MeshRendererNative.meshRendererExistsForEntity(
+        DropbearEngine.native.worldHandle,
+        entityId.raw
+    )
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/ffi/NativeEngine.jvm.kt b/src/jvmMain/kotlin/com/dropbear/ffi/NativeEngine.jvm.kt
index 70745ec..9bcb833 100644
--- a/src/jvmMain/kotlin/com/dropbear/ffi/NativeEngine.jvm.kt
+++ b/src/jvmMain/kotlin/com/dropbear/ffi/NativeEngine.jvm.kt
@@ -1,45 +1,14 @@
 package com.dropbear.ffi
 
-import com.dropbear.Camera
-import com.dropbear.EntityId
-import com.dropbear.EntityRef
-import com.dropbear.EntityTransform
-import com.dropbear.asset.TextureHandle
-import com.dropbear.exception.DropbearNativeException
-import com.dropbear.exceptionOnError
-import com.dropbear.ffi.JNINative.*
-import com.dropbear.ffi.InputStateNative.*
-import com.dropbear.ffi.DropbearEngineNative.*
-import com.dropbear.ffi.components.HierarchyNative.*
-import com.dropbear.ffi.components.CameraNative.*
-import com.dropbear.ffi.components.ColliderNative
-import com.dropbear.ffi.components.LabelNative.*
-import com.dropbear.ffi.components.MeshRendererNative.*
-import com.dropbear.ffi.components.EntityTransformNative.*
-import com.dropbear.ffi.components.CustomPropertiesNative.*
-import com.dropbear.ffi.components.RigidBodyNative
-import com.dropbear.input.Gamepad
-import com.dropbear.input.GamepadButton
-import com.dropbear.input.KeyCode
-import com.dropbear.input.MouseButton
-import com.dropbear.input.MouseButtonCodes
 import com.dropbear.logging.Logger
-import com.dropbear.math.Transform
-import com.dropbear.math.Vector2D
-import com.dropbear.physics.Collider
-import com.dropbear.physics.Index
-import com.dropbear.physics.RigidBody
-import com.dropbear.scene.SceneLoadHandle
-import com.dropbear.scene.SceneLoadStatus
-import com.dropbear.utils.Progress
 
 actual class NativeEngine {
-    private var worldHandle: Long = 0L
-    private var inputHandle: Long = 0L
-    private var commandBufferHandle: Long = 0L
-    private var assetHandle: Long = 0L
-    private var sceneLoaderHandle: Long = 0L
-    private var physicsEngineHandle: Long = 0L
+    internal var worldHandle: Long = 0L
+    internal var inputHandle: Long = 0L
+    internal var commandBufferHandle: Long = 0L
+    internal var assetHandle: Long = 0L
+    internal var sceneLoaderHandle: Long = 0L
+    internal var physicsEngineHandle: Long = 0L
 
     @JvmName("init")
     fun init(ctx: DropbearContext) {
@@ -50,434 +19,29 @@ actual class NativeEngine {
         this.sceneLoaderHandle = ctx.sceneLoaderHandle
         this.physicsEngineHandle = ctx.physicsEngineHandle
 
-        if (this.worldHandle < 0L) {
+        if (this.worldHandle <= 0L) {
             Logger.error("NativeEngine: Error - Invalid world handle received!")
             return
         }
-        if (this.inputHandle < 0L) {
+        if (this.inputHandle <= 0L) {
             Logger.error("NativeEngine: Error - Invalid input handle received!")
             return
         }
-        if (this.commandBufferHandle < 0L) {
+        if (this.commandBufferHandle <= 0L) {
             Logger.error("NativeEngine: Error - Invalid graphics handle received!")
             return
         }
-        if (this.assetHandle < 0L) {
+        if (this.assetHandle <= 0L) {
             Logger.error("NativeEngine: Error - Invalid asset handle received!")
             return
         }
-        if (this.sceneLoaderHandle < 0L) {
+        if (this.sceneLoaderHandle <= 0L) {
             Logger.error("NativeEngine: Error - Invalid scene loader handle received!")
             return
         }
-        if (this.physicsEngineHandle < 0L) {
+        if (this.physicsEngineHandle <= 0L) {
             Logger.error("NativeEngine: Error - Invalid physics handle received!")
             return
         }
     }
-
-    actual fun getEntityLabel(entityHandle: Long) : String? {
-        val result = getEntityLabel(worldHandle, entityHandle) ?: if (exceptionOnError) {
-            throw DropbearNativeException("Unable to get entity label for entity $entityHandle")
-        } else {
-            return null
-        }
-        return result
-    }
-
-    actual fun getEntity(label: String): Long? {
-        val result = getEntity(worldHandle, label)
-        return if (result == -1L) {
-            if (exceptionOnError) {
-                throw DropbearNativeException("Unable to get entity: returned -1")
-            } else {
-                null
-            }
-        } else if (result == 0L) {
-            null
-        } else {
-            result
-        }
-    }
-
-
-    actual fun getTransform(entityId: EntityId): EntityTransform? {
-        return getTransform(worldHandle, entityId.id)
-    }
-
-    actual fun propagateTransform(entityId: EntityId): Transform? {
-        return propagateTransform(worldHandle, entityId.id)
-    }
-
-    actual fun setTransform(entityId: EntityId, transform: EntityTransform) {
-        return setTransform(worldHandle, entityId.id, transform)
-    }
-
-    actual fun printInputState() {
-        return printInputState(inputHandle)
-    }
-
-    actual fun isKeyPressed(key: KeyCode): Boolean {
-        return isKeyPressed(inputHandle, key.ordinal)
-    }
-
-    actual fun getMousePosition(): Vector2D? {
-        val result = getMousePosition(inputHandle);
-        return Vector2D(result[0].toDouble(), result[1].toDouble())
-    }
-
-    actual fun isMouseButtonPressed(button: MouseButton): Boolean {
-        val buttonCode: Int = when (button) {
-            MouseButton.Left -> MouseButtonCodes.LEFT
-            MouseButton.Right -> MouseButtonCodes.RIGHT
-            MouseButton.Middle -> MouseButtonCodes.MIDDLE
-            MouseButton.Back -> MouseButtonCodes.BACK
-            MouseButton.Forward -> MouseButtonCodes.FORWARD
-            is MouseButton.Other -> button.value
-        }
-
-        return isMouseButtonPressed(inputHandle, buttonCode)
-    }
-
-    actual fun getConnectedGamepads(): List<Gamepad> {
-        val result = getConnectedGamepads(inputHandle)
-        return result.toList()
-    }
-
-    actual fun isGamepadButtonPressed(id: Long, button: GamepadButton): Boolean {
-        return isGamepadButtonPressed(inputHandle, id, button.ordinal)
-    }
-
-    actual fun getMouseDelta(): Vector2D? {
-        val result = getMouseDelta(inputHandle);
-        return Vector2D(result[0].toDouble(), result[1].toDouble())
-    }
-
-    actual fun isCursorLocked(): Boolean {
-        return isCursorLocked(inputHandle)
-    }
-
-    actual fun setCursorLocked(locked: Boolean) {
-        setCursorLocked(inputHandle, commandBufferHandle, locked)
-    }
-
-    actual fun getLastMousePos(): Vector2D? {
-        val result = getLastMousePos(inputHandle);
-        return Vector2D(result[0].toDouble(), result[1].toDouble())
-    }
-
-    actual fun getStringProperty(entityHandle: Long, label: String): String? {
-        return getStringProperty(worldHandle, entityHandle, label)
-    }
-
-    actual fun getIntProperty(entityHandle: Long, label: String): Int? {
-        val result = getIntProperty(worldHandle, entityHandle, label)
-        return if (result == 650911) {
-            if (exceptionOnError) {
-                throw DropbearNativeException("Unable to get integer property for entity $label")
-            } else {
-                null
-            }
-        } else {
-            result
-        }
-    }
-
-    actual fun getLongProperty(entityHandle: Long, label: String): Long? {
-        val result = getLongProperty(worldHandle, entityHandle, label)
-        return if (result == 6509112938) {
-            if (exceptionOnError) {
-                throw DropbearNativeException("Unable to get long property for entity $label")
-            } else {
-                null
-            }
-        } else {
-            result
-        }
-    }
-
-    actual fun getFloatProperty(entityHandle: Long, label: String): Float? {
-        val result = getFloatProperty(worldHandle, entityHandle, label)
-        return if (result.isNaN()) {
-            if (exceptionOnError) {
-                throw DropbearNativeException("Unable to get float property for entity $label")
-            } else {
-                null
-            }
-        } else {
-            result.toFloat()
-        }
-    }
-
-    actual fun getDoubleProperty(entityHandle: Long, label: String): Double? {
-        val result = getFloatProperty(worldHandle, entityHandle, label)
-        return if (result.isNaN()) {
-            if (exceptionOnError) {
-                throw DropbearNativeException("Unable to get double (float) property")
-            } else {
-                null
-            }
-        } else {
-            result
-        }
-    }
-
-    actual fun getBoolProperty(entityHandle: Long, label: String): Boolean? {
-        return getBoolProperty(worldHandle, entityHandle, label)
-    }
-
-    actual fun getVec3Property(entityHandle: Long, label: String): FloatArray? {
-        return getVec3Property(worldHandle, entityHandle, label)
-    }
-
-    actual fun setStringProperty(entityHandle: Long, label: String, value: String) {
-        setStringProperty(worldHandle, entityHandle, label, value)
-    }
-
-    actual fun setIntProperty(entityHandle: Long, label: String, value: Int) {
-        setIntProperty(worldHandle, entityHandle, label, value)
-    }
-
-    actual fun setLongProperty(entityHandle: Long, label: String, value: Long) {
-        setLongProperty(worldHandle, entityHandle, label, value)
-    }
-
-    actual fun setFloatProperty(entityHandle: Long, label: String, value: Double) {
-        setFloatProperty(worldHandle, entityHandle, label, value)
-    }
-
-    actual fun setBoolProperty(entityHandle: Long, label: String, value: Boolean) {
-        setBoolProperty(worldHandle, entityHandle, label, value)
-    }
-
-    actual fun setVec3Property(entityHandle: Long, label: String, value: FloatArray) {
-        setVec3Property(worldHandle, entityHandle, label, value)
-    }
-
-    actual fun getCamera(label: String): Camera? {
-        return getCamera(worldHandle, label)
-    }
-
-    actual fun getAttachedCamera(entityId: EntityId): Camera? {
-        return getAttachedCamera(worldHandle, entityId.id)
-    }
-
-    actual fun setCamera(camera: Camera) {
-        setCamera(worldHandle, camera)
-    }
-
-    actual fun isCursorHidden(): Boolean {
-        return isCursorHidden(inputHandle)
-    }
-
-    actual fun setCursorHidden(hidden: Boolean) {
-        setCursorHidden(inputHandle, commandBufferHandle, hidden)
-    }
-
-    actual fun getModel(entityHandle: Long): Long? {
-        val result = getModel(worldHandle, entityHandle)
-        return if (result == -1L) {
-            if (exceptionOnError) {
-                throw DropbearNativeException("Unable to get model for entity $entityHandle")
-            } else {
-                null
-            }
-        } else if (result == 0L) {
-            null
-        } else {
-            result
-        }
-    }
-
-    actual fun setModel(entityHandle: Long, modelHandle: Long) {
-        setModel(worldHandle, assetHandle, entityHandle, modelHandle)
-    }
-
-    actual fun getTexture(entityHandle: Long, name: String): Long? {
-        val result = getTexture(worldHandle, assetHandle, entityHandle, name)
-        return if (result == -1L) {
-            if (exceptionOnError) {
-                throw DropbearNativeException("Unable to get texture for entity $entityHandle")
-            } else {
-                null
-            }
-        } else if (result == 0L) {
-            null
-        } else {
-            result
-        }
-    }
-
-    actual fun setTextureOverride(entityHandle: Long, oldMaterialName: String, newTextureHandle: TextureHandle) {
-        return setTexture(
-            worldHandle,
-            assetHandle,
-            entityHandle,
-            oldMaterialName,
-            newTextureHandle.raw()
-        )
-    }
-
-    actual fun getTextureName(textureHandle: Long): String? {
-        return getTextureName(assetHandle, textureHandle)
-    }
-
-    actual fun isUsingModel(entityHandle: Long, modelHandle: Long): Boolean {
-        return isUsingModel(worldHandle, entityHandle, modelHandle)
-    }
-
-    actual fun isUsingTexture(entityHandle: Long, textureHandle: Long): Boolean {
-        return isUsingTexture(worldHandle, entityHandle, textureHandle)
-    }
-
-    actual fun getAsset(eucaURI: String): Long? {
-        val result = getAsset(assetHandle, eucaURI)
-        return if (result == -1L) {
-            if (exceptionOnError) {
-                throw DropbearNativeException("Unable to get asset for URI $eucaURI")
-            } else {
-                null
-            }
-        } else if (result == 0L) {
-            // no asset found
-            null
-        } else {
-            result
-        }
-    }
-
-    actual fun isModelHandle(id: Long): Boolean {
-        return isModelHandle(assetHandle, id)
-    }
-
-    actual fun isTextureHandle(id: Long): Boolean {
-        return isTextureHandle(assetHandle, id)
-    }
-
-    actual fun getAllTextures(entityHandle: Long): Array<String> {
-        return getAllTextures(worldHandle, entityHandle) ?: emptyArray()
-    }
-
-    actual fun getChildren(entityId: EntityId): Array<EntityRef>? {
-        val result = getChildren(worldHandle, entityId.id)
-        // i shouldn't expect it to return null unless an error, otherwise it must
-        // return an empty array
-        if (result == null) {
-            if (exceptionOnError) {
-                throw DropbearNativeException("Unable to query for all children for entity ${entityId.id}")
-            } else {
-                return null
-            }
-        } else {
-            val entityRefs = mutableListOf<EntityRef>()
-            result.forEach { e ->
-                entityRefs.add(EntityRef(EntityId(e)))
-            }
-            return entityRefs.toTypedArray() // must be an array so it cannot be mutated
-        }
-    }
-
-    actual fun getChildByLabel(entityId: EntityId, label: String): EntityRef? {
-        val result = getChildByLabel(worldHandle, entityId.id, label)
-        return if (result == -1L) {
-            if (exceptionOnError) {
-                throw DropbearNativeException("Unable to get child by label $entityId $label")
-            } else {
-                null
-            }
-        } else if (result == -2L) {
-            null
-        } else {
-            EntityRef(EntityId(result))
-        }
-    }
-
-    actual fun getParent(entityId: EntityId): EntityRef? {
-        val result = getParent(worldHandle, entityId.id)
-        return if (result == -1L) {
-            if (exceptionOnError) {
-                throw DropbearNativeException("Unable to get parent of entity $entityId")
-            } else {
-                null
-            }
-        } else if (result == -2L) {
-            null
-        } else {
-            EntityRef(EntityId(result))
-        }
-    }
-
-    actual fun quit() {
-        quit(commandBufferHandle)
-    }
-
-    actual fun switchToSceneImmediate(sceneName: String) {
-        SceneNative.switchToSceneImmediate(commandBufferHandle, sceneName)
-    }
-
-    actual fun loadSceneAsync(sceneName: String): SceneLoadHandle? {
-        return SceneNative.loadSceneAsync(commandBufferHandle, sceneLoaderHandle, sceneName, this)
-    }
-
-    actual fun loadSceneAsync(sceneName: String, loadingScene: String): SceneLoadHandle? {
-        return SceneNative.loadSceneAsync(commandBufferHandle, sceneLoaderHandle, sceneName, loadingScene, this)
-    }
-
-    actual fun switchToSceneAsync(sceneLoadHandle: SceneLoadHandle) {
-        SceneNative.switchToSceneAsync(commandBufferHandle, sceneLoadHandle) // will throw exception from JNI interface
-    }
-
-    actual fun getSceneLoadProgress(sceneLoadHandle: SceneLoadHandle): Progress {
-        return SceneNative.getSceneLoadProgress(sceneLoaderHandle, sceneLoadHandle)
-    }
-
-    actual fun getSceneLoadStatus(sceneLoadHandle: SceneLoadHandle): SceneLoadStatus {
-        return SceneNative.getSceneLoadStatus(sceneLoaderHandle, sceneLoadHandle)
-    }
-
-    actual fun setPhysicsEnabled(entityId: Long, enabled: Boolean) {
-        return PhysicsNative.setPhysicsEnabled(worldHandle, physicsEngineHandle, entityId, enabled)
-    }
-
-    actual fun isPhysicsEnabled(entityId: Long): Boolean {
-        return PhysicsNative.isPhysicsEnabled(worldHandle, physicsEngineHandle, entityId)
-    }
-
-    actual fun getRigidBody(entityId: Long): RigidBody? {
-        val result = PhysicsNative.getRigidBody(worldHandle, physicsEngineHandle, entityId)
-        result.native = this@NativeEngine
-        return result
-    }
-
-    actual fun getAllColliders(entityId: Long): List<Collider> {
-        val result = PhysicsNative.getAllColliders(worldHandle, physicsEngineHandle, entityId)
-        result.forEach {
-            it.native = this@NativeEngine
-        }
-        return result.toList()
-    }
-
-    actual fun applyImpulse(index: Index, x: Double, y: Double, z: Double) {
-        return RigidBodyNative.applyImpulse(physicsEngineHandle, index, x, y, z)
-    }
-
-    actual fun applyTorqueImpulse(index: Index, x: Double, y: Double, z: Double) {
-        return RigidBodyNative.applyTorqueImpulse(physicsEngineHandle, index, x, y, z)
-    }
-
-    actual fun setRigidbody(rigidBody: RigidBody) {
-        return RigidBodyNative.setRigidBody(worldHandle, physicsEngineHandle, rigidBody)
-    }
-
-    actual fun getChildColliders(index: Index): List<Collider> {
-        val result = RigidBodyNative.getChildColliders(worldHandle, physicsEngineHandle, index)
-        result.forEach {
-            it.native = this@NativeEngine
-        }
-        return result.toList()
-    }
-
-    actual fun setCollider(collider: Collider) {
-        return ColliderNative.setCollider(worldHandle, physicsEngineHandle, collider)
-    }
-}
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/host/SystemManager.kt b/src/jvmMain/kotlin/com/dropbear/host/SystemManager.kt
index 70abe46..670429c 100644
--- a/src/jvmMain/kotlin/com/dropbear/host/SystemManager.kt
+++ b/src/jvmMain/kotlin/com/dropbear/host/SystemManager.kt
@@ -1,7 +1,7 @@
 package com.dropbear.host
 
 import com.dropbear.DropbearEngine
-import com.dropbear.System
+import com.dropbear.ecs.System
 import com.dropbear.logging.LogLevel
 import com.dropbear.logging.LogWriter
 import com.dropbear.logging.Logger
diff --git a/src/jvmMain/kotlin/com/dropbear/input/Gamepad.jvm.kt b/src/jvmMain/kotlin/com/dropbear/input/Gamepad.jvm.kt
new file mode 100644
index 0000000..9c7d2e0
--- /dev/null
+++ b/src/jvmMain/kotlin/com/dropbear/input/Gamepad.jvm.kt
@@ -0,0 +1,35 @@
+package com.dropbear.input
+
+import com.dropbear.DropbearEngine
+import com.dropbear.math.Vector2d
+
+actual fun Gamepad.isGamepadButtonPressed(
+    id: Long,
+    button: GamepadButton
+): Boolean {
+    return GamepadNative.isGamepadButtonPressed(
+        DropbearEngine.native.inputHandle,
+        id,
+        button.ordinal
+    )
+}
+
+@Suppress("UNCHECKED_CAST")
+actual fun Gamepad.getLeftStickPosition(id: Long): Vector2d {
+    val result = GamepadNative.getLeftStickPosition(
+        DropbearEngine.native.inputHandle,
+        id
+    )
+
+    return result ?: Vector2d.zero()
+}
+
+@Suppress("UNCHECKED_CAST")
+actual fun Gamepad.getRightStickPosition(id: Long): Vector2d {
+    val result = GamepadNative.getRightStickPosition(
+        DropbearEngine.native.inputHandle,
+        id
+    )
+
+    return result ?: Vector2d.zero()
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/input/InputState.jvm.kt b/src/jvmMain/kotlin/com/dropbear/input/InputState.jvm.kt
new file mode 100644
index 0000000..30dffa8
--- /dev/null
+++ b/src/jvmMain/kotlin/com/dropbear/input/InputState.jvm.kt
@@ -0,0 +1,51 @@
+package com.dropbear.input
+
+import com.dropbear.DropbearEngine
+import com.dropbear.math.Vector2d
+
+actual class InputState actual constructor() {
+    actual fun printInputState() {
+        return InputStateNative.printInputState(DropbearEngine.native.inputHandle)
+    }
+
+    actual fun isKeyPressed(key: KeyCode): Boolean {
+        return InputStateNative.isKeyPressed(DropbearEngine.native.inputHandle, key.ordinal)
+    }
+
+    actual fun getMousePosition(): Vector2d {
+        return InputStateNative.getMousePosition(DropbearEngine.native.inputHandle)
+    }
+
+    actual fun isMouseButtonPressed(button: MouseButton): Boolean {
+        return InputStateNative.isMouseButtonPressed(DropbearEngine.native.inputHandle, button)
+    }
+
+    actual fun getMouseDelta(): Vector2d {
+        return InputStateNative.getMouseDelta(DropbearEngine.native.inputHandle)
+    }
+
+    actual fun isCursorLocked(): Boolean {
+        return InputStateNative.isCursorLocked(DropbearEngine.native.inputHandle)
+    }
+
+    actual fun setCursorLocked(locked: Boolean) {
+        return InputStateNative.setCursorLocked(DropbearEngine.native.commandBufferHandle, DropbearEngine.native.inputHandle, locked)
+    }
+
+    actual fun getLastMousePos(): Vector2d {
+        return InputStateNative.getLastMousePos(DropbearEngine.native.inputHandle)
+    }
+
+    actual fun isCursorHidden(): Boolean {
+        return InputStateNative.isCursorHidden(DropbearEngine.native.inputHandle)
+    }
+
+    actual fun setCursorHidden(hidden: Boolean) {
+        return InputStateNative.setCursorHidden(DropbearEngine.native.commandBufferHandle, DropbearEngine.native.inputHandle, hidden)
+    }
+
+    actual fun getConnectedGamepads(): List<Gamepad> {
+        val result = InputStateNative.getConnectedGamepads(DropbearEngine.native.inputHandle)
+        return result.map { Gamepad(it) }.toList()
+    }
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/physics/Collider.jvm.kt b/src/jvmMain/kotlin/com/dropbear/physics/Collider.jvm.kt
new file mode 100644
index 0000000..f306c88
--- /dev/null
+++ b/src/jvmMain/kotlin/com/dropbear/physics/Collider.jvm.kt
@@ -0,0 +1,91 @@
+package com.dropbear.physics
+
+import com.dropbear.DropbearEngine
+import com.dropbear.math.Vector3d
+
+actual fun Collider.getColliderShape(collider: Collider): ColliderShape {
+    return ColliderNative.getColliderShape(DropbearEngine.native.physicsEngineHandle, this)
+}
+
+actual fun Collider.setColliderShape(
+    collider: Collider,
+    shape: ColliderShape
+) {
+    ColliderNative.setColliderShape(DropbearEngine.native.physicsEngineHandle, this, shape)
+}
+
+actual fun Collider.getColliderDensity(collider: Collider): Double {
+    return ColliderNative.getColliderDensity(DropbearEngine.native.physicsEngineHandle, this)
+}
+
+actual fun Collider.setColliderDensity(
+    collider: Collider,
+    density: Double
+) {
+    ColliderNative.setColliderDensity(DropbearEngine.native.physicsEngineHandle, this, density)
+}
+
+actual fun Collider.getColliderFriction(collider: Collider): Double {
+    return ColliderNative.getColliderFriction(DropbearEngine.native.physicsEngineHandle, this)
+}
+
+actual fun Collider.setColliderFriction(
+    collider: Collider,
+    friction: Double
+) {
+    ColliderNative.setColliderFriction(DropbearEngine.native.physicsEngineHandle, this, friction)
+}
+
+actual fun Collider.getColliderRestitution(collider: Collider): Double {
+    return ColliderNative.getColliderRestitution(DropbearEngine.native.physicsEngineHandle, this)
+}
+
+actual fun Collider.setColliderRestitution(
+    collider: Collider,
+    restitution: Double
+) {
+    ColliderNative.setColliderRestitution(DropbearEngine.native.physicsEngineHandle, this, restitution)
+}
+
+actual fun Collider.getColliderIsSensor(collider: Collider): Boolean {
+    return ColliderNative.getColliderIsSensor(DropbearEngine.native.physicsEngineHandle, this)
+}
+
+actual fun Collider.setColliderIsSensor(
+    collider: Collider,
+    isSensor: Boolean
+) {
+    ColliderNative.setColliderIsSensor(DropbearEngine.native.physicsEngineHandle, this, isSensor)
+}
+
+actual fun Collider.getColliderTranslation(collider: Collider): Vector3d {
+    return ColliderNative.getColliderTranslation(DropbearEngine.native.physicsEngineHandle, this)
+        ?: Vector3d.zero()
+}
+
+actual fun Collider.setColliderTranslation(
+    collider: Collider,
+    translation: Vector3d
+) {
+    ColliderNative.setColliderTranslation(DropbearEngine.native.physicsEngineHandle, this, translation)
+}
+
+actual fun Collider.getColliderRotation(collider: Collider): Vector3d {
+    return ColliderNative.getColliderRotation(DropbearEngine.native.physicsEngineHandle, this)
+        ?: Vector3d.zero()
+}
+
+actual fun Collider.setColliderRotation(
+    collider: Collider,
+    rotation: Vector3d
+) {
+    ColliderNative.setColliderRotation(DropbearEngine.native.physicsEngineHandle, this, rotation)
+}
+
+actual fun Collider.getColliderMass(collider: Collider): Double {
+    return ColliderNative.getColliderMass(DropbearEngine.native.physicsEngineHandle, this)
+}
+
+actual fun Collider.setColliderMass(collider: Collider, mass: Double) {
+    ColliderNative.setColliderMass(DropbearEngine.native.physicsEngineHandle, this, mass)
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/physics/ColliderGroup.jvm.kt b/src/jvmMain/kotlin/com/dropbear/physics/ColliderGroup.jvm.kt
new file mode 100644
index 0000000..ef22fb9
--- /dev/null
+++ b/src/jvmMain/kotlin/com/dropbear/physics/ColliderGroup.jvm.kt
@@ -0,0 +1,12 @@
+package com.dropbear.physics
+
+import com.dropbear.DropbearEngine
+import com.dropbear.EntityId
+
+actual fun ColliderGroup.getColliderGroupColliders(colliderGroup: ColliderGroup): List<Collider> {
+    return ColliderGroupNative.getColliderGroupColliders(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, colliderGroup.parentEntity.raw).toList()
+}
+
+actual fun colliderGroupExistsForEntity(entityId: EntityId): Boolean {
+    return ColliderGroupNative.colliderGroupExistsForEntity(DropbearEngine.native.worldHandle, entityId.raw)
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/physics/Physics.jvm.kt b/src/jvmMain/kotlin/com/dropbear/physics/Physics.jvm.kt
new file mode 100644
index 0000000..61e4b1c
--- /dev/null
+++ b/src/jvmMain/kotlin/com/dropbear/physics/Physics.jvm.kt
@@ -0,0 +1,12 @@
+package com.dropbear.physics
+
+import com.dropbear.DropbearEngine
+import com.dropbear.math.Vector3d
+
+internal actual fun getGravity(): Vector3d {
+    return PhysicsNative.getGravity(DropbearEngine.native.physicsEngineHandle) ?: Vector3d(0.0, -9.81, 0.0)
+}
+
+internal actual fun setGravity(gravity: Vector3d) {
+    return PhysicsNative.setGravity(DropbearEngine.native.physicsEngineHandle, gravity)
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/physics/RigidBody.jvm.kt b/src/jvmMain/kotlin/com/dropbear/physics/RigidBody.jvm.kt
new file mode 100644
index 0000000..caa7f73
--- /dev/null
+++ b/src/jvmMain/kotlin/com/dropbear/physics/RigidBody.jvm.kt
@@ -0,0 +1,149 @@
+package com.dropbear.physics
+
+import com.dropbear.DropbearEngine
+import com.dropbear.EntityId
+import com.dropbear.math.Vector3d
+
+actual fun RigidBody.getRigidbodyMode(rigidBody: RigidBody): RigidBodyMode {
+    val result = RigidBodyNative.getRigidBodyMode(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this)
+    return RigidBodyMode.entries[result]
+}
+
+actual fun RigidBody.setRigidbodyMode(
+    rigidBody: RigidBody,
+    mode: RigidBodyMode
+) {
+    RigidBodyNative.setRigidBodyMode(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, mode.ordinal)
+}
+
+actual fun RigidBody.getRigidbodyGravityScale(rigidBody: RigidBody): Double {
+    return RigidBodyNative.getRigidBodyGravityScale(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this)
+}
+
+actual fun RigidBody.setRigidbodyGravityScale(
+    rigidBody: RigidBody,
+    gravityScale: Double
+) {
+    RigidBodyNative.setRigidBodyGravityScale(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, gravityScale)
+}
+
+actual fun RigidBody.getRigidBodySleep(rigidBody: RigidBody): Boolean {
+    return RigidBodyNative.getRigidBodySleep(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this)
+}
+
+actual fun RigidBody.setRigidBodySleep(
+    rigidBody: RigidBody,
+    canSleep: Boolean
+) {
+    RigidBodyNative.setRigidBodySleep(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, canSleep)
+}
+
+actual fun RigidBody.getRigidbodyCcdEnabled(rigidBody: RigidBody): Boolean {
+    return RigidBodyNative.getRigidBodyCcdEnabled(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this)
+}
+
+actual fun RigidBody.setRigidbodyCcdEnabled(
+    rigidBody: RigidBody,
+    ccdEnabled: Boolean
+) {
+    RigidBodyNative.setRigidBodyCcdEnabled(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, ccdEnabled)
+}
+
+actual fun RigidBody.getRigidbodyLinearVelocity(rigidBody: RigidBody): Vector3d {
+    return RigidBodyNative.getRigidBodyLinearVelocity(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this)
+        ?: Vector3d.zero()
+}
+
+actual fun RigidBody.setRigidbodyLinearVelocity(
+    rigidBody: RigidBody,
+    linearVelocity: Vector3d
+) {
+    RigidBodyNative.setRigidBodyLinearVelocity(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, linearVelocity)
+}
+
+actual fun RigidBody.getRigidbodyAngularVelocity(rigidBody: RigidBody): Vector3d {
+    return RigidBodyNative.getRigidBodyAngularVelocity(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this)
+        ?: Vector3d.zero()
+}
+
+actual fun RigidBody.setRigidbodyAngularVelocity(
+    rigidBody: RigidBody,
+    angularVelocity: Vector3d
+) {
+    RigidBodyNative.setRigidBodyAngularVelocity(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, angularVelocity)
+}
+
+actual fun RigidBody.getRigidbodyLinearDamping(rigidBody: RigidBody): Double {
+    return RigidBodyNative.getRigidBodyLinearDamping(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this)
+}
+
+actual fun RigidBody.setRigidbodyLinearDamping(
+    rigidBody: RigidBody,
+    linearDamping: Double
+) {
+    RigidBodyNative.setRigidBodyLinearDamping(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, linearDamping)
+}
+
+actual fun RigidBody.getRigidbodyAngularDamping(rigidBody: RigidBody): Double {
+    return RigidBodyNative.getRigidBodyAngularDamping(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this)
+}
+
+actual fun RigidBody.setRigidbodyAngularDamping(
+    rigidBody: RigidBody,
+    angularDamping: Double
+) {
+    RigidBodyNative.setRigidBodyAngularDamping(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, angularDamping)
+}
+
+actual fun RigidBody.getRigidbodyLockTranslation(rigidBody: RigidBody): AxisLock {
+    return RigidBodyNative.getRigidBodyLockTranslation(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this)
+}
+
+actual fun RigidBody.setRigidbodyLockTranslation(
+    rigidBody: RigidBody,
+    lockTranslation: AxisLock
+) {
+    RigidBodyNative.setRigidBodyLockTranslation(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, lockTranslation)
+}
+
+actual fun RigidBody.getRigidbodyLockRotation(rigidBody: RigidBody): AxisLock {
+    return RigidBodyNative.getRigidBodyLockRotation(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this)
+}
+
+actual fun RigidBody.setRigidbodyLockRotation(
+    rigidBody: RigidBody,
+    lockRotation: AxisLock
+) {
+    RigidBodyNative.setRigidBodyLockRotation(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, lockRotation)
+}
+
+actual fun RigidBody.getRigidbodyChildren(rigidBody: RigidBody): List<Collider> {
+    val result = RigidBodyNative.getRigidBodyChildren(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this)
+    return result.toList()
+}
+
+actual fun RigidBody.applyImpulse(
+    index: Index,
+    x: Double,
+    y: Double,
+    z: Double
+) {
+    RigidBodyNative.applyImpulse(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, x, y, z)
+}
+
+actual fun RigidBody.applyTorqueImpulse(
+    index: Index,
+    x: Double,
+    y: Double,
+    z: Double
+) {
+    RigidBodyNative.applyTorqueImpulse(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, x, y, z)
+}
+
+actual fun rigidBodyExistsForEntity(entityId: EntityId): Index? {
+    return RigidBodyNative.rigidBodyExistsForEntity(
+        DropbearEngine.native.worldHandle,
+        DropbearEngine.native.physicsEngineHandle,
+        entityId.raw
+    )
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/scene/SceneLoadHandle.jvm.kt b/src/jvmMain/kotlin/com/dropbear/scene/SceneLoadHandle.jvm.kt
new file mode 100644
index 0000000..a6bc25c
--- /dev/null
+++ b/src/jvmMain/kotlin/com/dropbear/scene/SceneLoadHandle.jvm.kt
@@ -0,0 +1,21 @@
+package com.dropbear.scene
+
+import com.dropbear.DropbearEngine
+import com.dropbear.utils.Progress
+
+actual fun SceneLoadHandle.getSceneLoadHandleSceneName(id: Long): String {
+    return SceneLoadHandleNative.getSceneLoadHandleSceneName(DropbearEngine.native.sceneLoaderHandle, id)
+}
+
+actual fun SceneLoadHandle.switchToSceneAsync() {
+    return SceneLoadHandleNative.switchToSceneAsync(DropbearEngine.native.commandBufferHandle, this.id)
+}
+
+actual fun SceneLoadHandle.getSceneLoadProgress(): Progress {
+    return SceneLoadHandleNative.getSceneLoadProgress(DropbearEngine.native.sceneLoaderHandle, this.id)
+}
+
+actual fun SceneLoadHandle.getSceneLoadStatus(): SceneLoadStatus {
+    val result = SceneLoadHandleNative.getSceneLoadStatus(DropbearEngine.native.sceneLoaderHandle, this.id)
+    return SceneLoadStatus.entries[result]
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/scene/SceneManager.jvm.kt b/src/jvmMain/kotlin/com/dropbear/scene/SceneManager.jvm.kt
new file mode 100644
index 0000000..6e3c201
--- /dev/null
+++ b/src/jvmMain/kotlin/com/dropbear/scene/SceneManager.jvm.kt
@@ -0,0 +1,32 @@
+package com.dropbear.scene
+
+import com.dropbear.DropbearEngine
+
+actual fun SceneManager.loadSceneAsyncNative(sceneName: String): SceneLoadHandle? {
+    val result = SceneManagerNative.loadSceneAsyncNative(
+        DropbearEngine.native.commandBufferHandle,
+        DropbearEngine.native.sceneLoaderHandle,
+        sceneName
+    )
+    return if (result != null) SceneLoadHandle(result) else null
+}
+
+actual fun SceneManager.loadSceneAsyncNative(
+    sceneName: String,
+    loadingScene: String
+): SceneLoadHandle? {
+    val result = SceneManagerNative.loadSceneAsyncNative(
+        DropbearEngine.native.commandBufferHandle,
+        DropbearEngine.native.sceneLoaderHandle,
+        sceneName,
+        loadingScene
+    )
+    return if (result != null) SceneLoadHandle(result) else null
+}
+
+actual fun SceneManager.switchToSceneImmediateNative(sceneName: String) {
+    SceneManagerNative.switchToSceneImmediateNative(
+        DropbearEngine.native.commandBufferHandle,
+        sceneName
+    )
+}
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/DropbearEngine.native.kt b/src/nativeMain/kotlin/com/dropbear/DropbearEngine.native.kt
new file mode 100644
index 0000000..6562bca
--- /dev/null
+++ b/src/nativeMain/kotlin/com/dropbear/DropbearEngine.native.kt
@@ -0,0 +1,14 @@
+package com.dropbear
+
+import com.dropbear.components.Camera
+
+actual fun getEntity(label: String): Long? {
+    TODO("Not yet implemented")
+}
+
+actual fun getAsset(eucaURI: String): Long? {
+    TODO("Not yet implemented")
+}
+
+actual fun quit() {
+}
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/EntityRef.native.kt b/src/nativeMain/kotlin/com/dropbear/EntityRef.native.kt
new file mode 100644
index 0000000..a0db775
--- /dev/null
+++ b/src/nativeMain/kotlin/com/dropbear/EntityRef.native.kt
@@ -0,0 +1,20 @@
+package com.dropbear
+
+actual fun EntityRef.getEntityLabel(entity: EntityId): String {
+    TODO("Not yet implemented")
+}
+
+actual fun EntityRef.getChildren(entityId: EntityId): Array<EntityRef>? {
+    TODO("Not yet implemented")
+}
+
+actual fun EntityRef.getChildByLabel(
+    entityId: EntityId,
+    label: String
+): EntityRef? {
+    TODO("Not yet implemented")
+}
+
+actual fun EntityRef.getParent(entityId: EntityId): EntityRef? {
+    TODO("Not yet implemented")
+}
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/asset/AssetHandle.native.kt b/src/nativeMain/kotlin/com/dropbear/asset/AssetHandle.native.kt
new file mode 100644
index 0000000..819c8ba
--- /dev/null
+++ b/src/nativeMain/kotlin/com/dropbear/asset/AssetHandle.native.kt
@@ -0,0 +1,9 @@
+package com.dropbear.asset
+
+actual fun isModelHandle(id: Long): Boolean {
+    TODO("Not yet implemented")
+}
+
+actual fun isTextureHandle(id: Long): Boolean {
+    TODO("Not yet implemented")
+}
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/asset/TextureHandle.native.kt b/src/nativeMain/kotlin/com/dropbear/asset/TextureHandle.native.kt
new file mode 100644
index 0000000..9c5832c
--- /dev/null
+++ b/src/nativeMain/kotlin/com/dropbear/asset/TextureHandle.native.kt
@@ -0,0 +1,5 @@
+package com.dropbear.asset
+
+actual fun TextureHandle.getTextureName(id: Long): String? {
+    TODO("Not yet implemented")
+}
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/components/Camera.native.kt b/src/nativeMain/kotlin/com/dropbear/components/Camera.native.kt
new file mode 100644
index 0000000..bcffc6e
--- /dev/null
+++ b/src/nativeMain/kotlin/com/dropbear/components/Camera.native.kt
@@ -0,0 +1,82 @@
+package com.dropbear.components
+
+import com.dropbear.EntityId
+import com.dropbear.math.Vector3d
+
+actual fun Camera.getCameraEye(entity: EntityId): Vector3d {
+    TODO("Not yet implemented")
+}
+
+actual fun Camera.setCameraEye(entity: EntityId, value: Vector3d) {
+}
+
+actual fun Camera.getCameraTarget(entity: EntityId): Vector3d {
+    TODO("Not yet implemented")
+}
+
+actual fun Camera.setCameraTarget(entity: EntityId, value: Vector3d) {
+}
+
+actual fun Camera.getCameraUp(entity: EntityId): Vector3d {
+    TODO("Not yet implemented")
+}
+
+actual fun Camera.setCameraUp(entity: EntityId, value: Vector3d) {
+}
+
+actual fun Camera.getCameraAspect(entity: EntityId): Double {
+    TODO("Not yet implemented")
+}
+
+actual fun Camera.getCameraFovY(entity: EntityId): Double {
+    TODO("Not yet implemented")
+}
+
+actual fun Camera.setCameraFovY(entity: EntityId, value: Double) {
+}
+
+actual fun Camera.getCameraZNear(entity: EntityId): Double {
+    TODO("Not yet implemented")
+}
+
+actual fun Camera.setCameraZNear(entity: EntityId, value: Double) {
+}
+
+actual fun Camera.getCameraZFar(entity: EntityId): Double {
+    TODO("Not yet implemented")
+}
+
+actual fun Camera.setCameraZFar(entity: EntityId, value: Double) {
+}
+
+actual fun Camera.getCameraYaw(entity: EntityId): Double {
+    TODO("Not yet implemented")
+}
+
+actual fun Camera.getCameraPitch(entity: EntityId): Double {
+    TODO("Not yet implemented")
+}
+
+actual fun Camera.setCameraYaw(entity: EntityId, value: Double) {
+}
+
+actual fun Camera.setCameraPitch(entity: EntityId, value: Double) {
+}
+
+actual fun Camera.getCameraSpeed(entity: EntityId): Double {
+    TODO("Not yet implemented")
+}
+
+actual fun Camera.setCameraSpeed(entity: EntityId, value: Double) {
+}
+
+actual fun Camera.getCameraSensitivity(entity: EntityId): Double {
+    TODO("Not yet implemented")
+}
+
+actual fun Camera.setCameraSensitivity(entity: EntityId, value: Double) {
+}
+
+actual fun cameraExistsForEntity(entity: EntityId): Boolean {
+    TODO("Not yet implemented")
+}
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/components/CustomProperties.native.kt b/src/nativeMain/kotlin/com/dropbear/components/CustomProperties.native.kt
new file mode 100644
index 0000000..70d6faa
--- /dev/null
+++ b/src/nativeMain/kotlin/com/dropbear/components/CustomProperties.native.kt
@@ -0,0 +1,96 @@
+package com.dropbear.components
+
+import com.dropbear.EntityId
+import com.dropbear.math.Vector3d
+
+actual fun CustomProperties.getStringProperty(
+    entityHandle: Long,
+    label: String
+): String? {
+    TODO("Not yet implemented")
+}
+
+actual fun CustomProperties.getIntProperty(entityHandle: Long, label: String): Int? {
+    TODO("Not yet implemented")
+}
+
+actual fun CustomProperties.getLongProperty(
+    entityHandle: Long,
+    label: String
+): Long? {
+    TODO("Not yet implemented")
+}
+
+actual fun CustomProperties.getDoubleProperty(
+    entityHandle: Long,
+    label: String
+): Double? {
+    TODO("Not yet implemented")
+}
+
+actual fun CustomProperties.getFloatProperty(
+    entityHandle: Long,
+    label: String
+): Float? {
+    TODO("Not yet implemented")
+}
+
+actual fun CustomProperties.getBoolProperty(
+    entityHandle: Long,
+    label: String
+): Boolean? {
+    TODO("Not yet implemented")
+}
+
+actual fun CustomProperties.getVec3Property(
+    entityHandle: Long,
+    label: String
+): Vector3d? {
+    TODO("Not yet implemented")
+}
+
+actual fun CustomProperties.setStringProperty(
+    entityHandle: Long,
+    label: String,
+    value: String
+) {
+}
+
+actual fun CustomProperties.setIntProperty(
+    entityHandle: Long,
+    label: String,
+    value: Int
+) {
+}
+
+actual fun CustomProperties.setLongProperty(
+    entityHandle: Long,
+    label: String,
+    value: Long
+) {
+}
+
+actual fun CustomProperties.setFloatProperty(
+    entityHandle: Long,
+    label: String,
+    value: Double
+) {
+}
+
+actual fun CustomProperties.setBoolProperty(
+    entityHandle: Long,
+    label: String,
+    value: Boolean
+) {
+}
+
+actual fun CustomProperties.setVec3Property(
+    entityHandle: Long,
+    label: String,
+    value: Vector3d
+) {
+}
+
+actual fun customPropertiesExistsForEntity(entityId: EntityId): Boolean {
+    TODO("Not yet implemented")
+}
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/components/EntityTransform.native.kt b/src/nativeMain/kotlin/com/dropbear/components/EntityTransform.native.kt
new file mode 100644
index 0000000..a796e97
--- /dev/null
+++ b/src/nativeMain/kotlin/com/dropbear/components/EntityTransform.native.kt
@@ -0,0 +1,32 @@
+package com.dropbear.components
+
+import com.dropbear.EntityId
+import com.dropbear.math.Transform
+
+actual fun EntityTransform.getLocalTransform(entityId: EntityId): Transform {
+    TODO("Not yet implemented")
+}
+
+actual fun EntityTransform.setLocalTransform(
+    entityId: EntityId,
+    transform: Transform
+) {
+}
+
+actual fun EntityTransform.getWorldTransform(entityId: EntityId): Transform {
+    TODO("Not yet implemented")
+}
+
+actual fun EntityTransform.setWorldTransform(
+    entityId: EntityId,
+    transform: Transform
+) {
+}
+
+actual fun EntityTransform.propagateTransform(entityId: EntityId): Transform? {
+    TODO("Not yet implemented")
+}
+
+actual fun entityTransformExistsForEntity(entityId: EntityId): Boolean {
+    TODO("Not yet implemented")
+}
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/components/MeshRenderer.native.kt b/src/nativeMain/kotlin/com/dropbear/components/MeshRenderer.native.kt
new file mode 100644
index 0000000..4525c23
--- /dev/null
+++ b/src/nativeMain/kotlin/com/dropbear/components/MeshRenderer.native.kt
@@ -0,0 +1,31 @@
+package com.dropbear.components
+
+import com.dropbear.EntityId
+import com.dropbear.asset.ModelHandle
+import com.dropbear.asset.TextureHandle
+
+actual fun MeshRenderer.getModel(id: EntityId): ModelHandle? {
+    TODO("Not yet implemented")
+}
+
+actual fun MeshRenderer.setModel(id: EntityId, model: ModelHandle?) {
+}
+
+actual fun MeshRenderer.getAllTextureIds(id: EntityId): List<TextureHandle>? {
+    TODO("Not yet implemented")
+}
+
+actual fun MeshRenderer.getTexture(id: EntityId, materialName: String): Long? {
+    TODO("Not yet implemented")
+}
+
+actual fun MeshRenderer.setTextureOverride(
+    id: EntityId,
+    materialName: String,
+    textureHandle: Long
+) {
+}
+
+actual fun meshRendererExistsForEntity(entityId: EntityId): Boolean {
+    TODO("Not yet implemented")
+}
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/ffi/ConverterUtilities.kt b/src/nativeMain/kotlin/com/dropbear/ffi/ConverterUtilities.kt
deleted file mode 100644
index 2ad7f8d..0000000
--- a/src/nativeMain/kotlin/com/dropbear/ffi/ConverterUtilities.kt
+++ /dev/null
@@ -1,197 +0,0 @@
-@file:OptIn(ExperimentalForeignApi::class, ExperimentalNativeApi::class)
-@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
-
-package com.dropbear.ffi
-
-import com.dropbear.EntityId
-import com.dropbear.exception.DropbearNativeException
-import com.dropbear.ffi.generated.ColliderShape
-import com.dropbear.ffi.generated.ColliderShape_Box
-import com.dropbear.ffi.generated.ColliderShape_Capsule
-import com.dropbear.ffi.generated.ColliderShape_Cone
-import com.dropbear.ffi.generated.ColliderShape_Cylinder
-import com.dropbear.ffi.generated.ColliderShape_Sphere
-import com.dropbear.ffi.generated.RigidBodyMode
-import com.dropbear.ffi.generated.SceneLoadResult
-import com.dropbear.ffi.generated.Vector3D
-import com.dropbear.physics.AxisLock
-import com.dropbear.physics.Collider
-import com.dropbear.physics.Index
-import com.dropbear.physics.RigidBody
-import com.dropbear.scene.SceneLoadStatus
-import kotlinx.cinterop.ExperimentalForeignApi
-import kotlin.experimental.ExperimentalNativeApi
-
-internal fun SceneLoadResult.fromNative(): SceneLoadStatus {
-    return when (this) {
-        SceneLoadResult.SCENE_LOAD_PENDING -> SceneLoadStatus.PENDING
-        SceneLoadResult.SCENE_LOAD_SUCCESS -> SceneLoadStatus.READY
-        SceneLoadResult.SCENE_LOAD_ERROR -> SceneLoadStatus.FAILED
-    }
-}
-
-internal fun Vector3D.toKotlin(): com.dropbear.math.Vector3D {
-    return com.dropbear.math.Vector3D(this.x, this.y, this.z)
-}
-
-internal fun RigidBodyMode.toKotlin(): com.dropbear.physics.RigidBodyMode {
-    return when (this) {
-        RigidBodyMode.RIGIDBODY_MODE_DYNAMIC -> com.dropbear.physics.RigidBodyMode.Dynamic
-        RigidBodyMode.RIGIDBODY_MODE_FIXED -> com.dropbear.physics.RigidBodyMode.Fixed
-        RigidBodyMode.RIGIDBODY_MODE_KINEMATIC_POSITION -> com.dropbear.physics.RigidBodyMode.KinematicPosition
-        RigidBodyMode.RIGIDBODY_MODE_KINEMATIC_VELOCITY -> com.dropbear.physics.RigidBodyMode.KinematicVelocity
-    }
-}
-
-internal fun com.dropbear.ffi.generated.AxisLock.toKotlin(): AxisLock {
-    return AxisLock(
-        this.x,
-        this.y,
-        this.z,
-    )
-}
-
-internal fun com.dropbear.ffi.generated.RigidBody.toKotlin(nativeEngine: NativeEngine): RigidBody {
-    return RigidBody(
-        index = Index(this.index.index, this.index.generation),
-        entity = EntityId(this.entity),
-        rigidBodyMode = this.mode.toKotlin(),
-        gravityScale = this.gravity_scale,
-        canSleep = this.can_sleep,
-        ccdEnabled = this.ccd_enabled,
-        linearVelocity = this.linear_velocity.toKotlin(),
-        angularVelocity = this.angualar_velocity.toKotlin(),
-        linearDamping = this.linear_damping,
-        angularDamping = this.angular_damping,
-        lockTranslation = this.lock_translation.toKotlin(),
-        lockRotation = this.lock_rotation.toKotlin(),
-        native = nativeEngine
-    )
-}
-
-internal fun RigidBody.populateCStruct(cBody: com.dropbear.ffi.generated.RigidBody) {
-    cBody.index.index = this.index.index
-    cBody.index.generation = this.index.generation
-    cBody.entity = this.entity.id
-    cBody.mode = when (this.rigidBodyMode) {
-        com.dropbear.physics.RigidBodyMode.Dynamic -> RigidBodyMode.RIGIDBODY_MODE_DYNAMIC
-        com.dropbear.physics.RigidBodyMode.Fixed -> RigidBodyMode.RIGIDBODY_MODE_FIXED
-        com.dropbear.physics.RigidBodyMode.KinematicPosition -> RigidBodyMode.RIGIDBODY_MODE_KINEMATIC_POSITION
-        com.dropbear.physics.RigidBodyMode.KinematicVelocity -> RigidBodyMode.RIGIDBODY_MODE_KINEMATIC_VELOCITY
-    }
-    cBody.gravity_scale = this.gravityScale
-    cBody.can_sleep = this.canSleep
-    cBody.ccd_enabled = this.ccdEnabled
-
-    cBody.linear_velocity.x = this.linearVelocity.x
-    cBody.linear_velocity.y = this.linearVelocity.y
-    cBody.linear_velocity.z = this.linearVelocity.z
-
-    cBody.angualar_velocity.x = this.angularVelocity.x
-    cBody.angualar_velocity.y = this.angularVelocity.y
-    cBody.angualar_velocity.z = this.angularVelocity.z
-
-    cBody.linear_damping = this.linearDamping
-    cBody.angular_damping = this.angularDamping
-
-    cBody.lock_translation.x = this.lockTranslation.x
-    cBody.lock_translation.y = this.lockTranslation.y
-    cBody.lock_translation.z = this.lockTranslation.z
-
-    cBody.lock_rotation.x = this.lockRotation.x
-    cBody.lock_rotation.y = this.lockRotation.y
-    cBody.lock_rotation.z = this.lockRotation.z
-}
-
-internal fun ColliderShape.toKotlin(): com.dropbear.physics.ColliderShape {
-    return when (this.tag) {
-        ColliderShape_Box -> {
-            com.dropbear.physics.ColliderShape.Box(
-                halfExtents = com.dropbear.math.Vector3D(
-                    this.data.box.half_extents.x,
-                    this.data.box.half_extents.y,
-                    this.data.box.half_extents.z
-                )
-            )
-        }
-        ColliderShape_Sphere -> {
-            com.dropbear.physics.ColliderShape.Sphere(this.data.sphere.radius)
-        }
-        ColliderShape_Capsule -> {
-            com.dropbear.physics.ColliderShape.Capsule(this.data.capsule.half_height, this.data.capsule.radius)
-        }
-        ColliderShape_Cylinder -> {
-            com.dropbear.physics.ColliderShape.Cylinder(this.data.cylinder.half_height, this.data.cylinder.radius)
-        }
-        ColliderShape_Cone -> {
-            com.dropbear.physics.ColliderShape.Cone(this.data.cone.half_height, this.data.cone.radius)
-        }
-        else -> throw DropbearNativeException("Unknown collider tag: ${this.tag}")
-    }
-}
-
-internal fun com.dropbear.ffi.generated.Collider.toKotlin(nativeEngine: NativeEngine): Collider {
-    return Collider(
-        index = Index(this.index.index, this.index.generation),
-        entity = EntityId(this.entity),
-        colliderShape = this.collider_shape.toKotlin(),
-        density = this.density,
-        friction = this.friction,
-        restitution = this.restitution,
-        isSensor = this.is_sensor,
-        translation = this.translation.toKotlin(),
-        rotation = this.rotation.toKotlin(),
-        native = nativeEngine,
-        id = this.id,
-    )
-}
-
-internal fun Collider.populateCStruct(struct: com.dropbear.ffi.generated.Collider) {
-    struct.index.index = this.index.index
-    struct.index.generation = this.index.generation
-    struct.entity = this.entity.id
-    struct.density = this.density
-    struct.friction = this.friction
-    struct.restitution = this.restitution
-    struct.is_sensor = this.isSensor
-
-    struct.translation.x = this.translation.x
-    struct.translation.y = this.translation.y
-    struct.translation.z = this.translation.z
-
-    struct.rotation.x = this.rotation.x
-    struct.rotation.y = this.rotation.y
-    struct.rotation.z = this.rotation.z
-
-    this.colliderShape.populateCStruct(struct.collider_shape)
-}
-
-internal fun com.dropbear.physics.ColliderShape.populateCStruct(struct: ColliderShape) {
-    when (this) {
-        is com.dropbear.physics.ColliderShape.Box -> {
-            struct.tag = ColliderShape_Box
-            struct.data.box.half_extents.x = this.halfExtents.x
-            struct.data.box.half_extents.y = this.halfExtents.y
-            struct.data.box.half_extents.z = this.halfExtents.z
-        }
-        is com.dropbear.physics.ColliderShape.Sphere -> {
-            struct.tag = ColliderShape_Sphere
-            struct.data.sphere.radius = this.radius
-        }
-        is com.dropbear.physics.ColliderShape.Capsule -> {
-            struct.tag = ColliderShape_Capsule
-            struct.data.capsule.half_height = this.halfHeight
-            struct.data.capsule.radius = this.radius
-        }
-        is com.dropbear.physics.ColliderShape.Cylinder -> {
-            struct.tag = ColliderShape_Cylinder
-            struct.data.cylinder.half_height = this.halfHeight
-            struct.data.cylinder.radius = this.radius
-        }
-        is com.dropbear.physics.ColliderShape.Cone -> {
-            struct.tag = ColliderShape_Cone
-            struct.data.cone.half_height = this.halfHeight
-            struct.data.cone.radius = this.radius
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/ffi/NativeEngine.native.kt b/src/nativeMain/kotlin/com/dropbear/ffi/NativeEngine.native.kt
index 69b1200..61d52e1 100644
--- a/src/nativeMain/kotlin/com/dropbear/ffi/NativeEngine.native.kt
+++ b/src/nativeMain/kotlin/com/dropbear/ffi/NativeEngine.native.kt
@@ -1,36 +1,15 @@
 @file:OptIn(ExperimentalForeignApi::class, ExperimentalNativeApi::class)
 @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
 
-/// guys how do i remove the reinterpret error its genuinely pmo and intellij keeps
-/// on catching it.
-
 package com.dropbear.ffi
 
-import com.dropbear.Camera
-import com.dropbear.EntityId
-import com.dropbear.EntityRef
-import com.dropbear.EntityTransform
-import com.dropbear.asset.TextureHandle
 import com.dropbear.exception.DropbearNativeException
-import com.dropbear.exception.PrematureSceneSwitchException
 import com.dropbear.exceptionOnError
-import com.dropbear.ffi.generated.*
-import com.dropbear.input.Gamepad
-import com.dropbear.input.GamepadButton
-import com.dropbear.input.KeyCode
-import com.dropbear.input.MouseButton
-import com.dropbear.input.MouseButtonCodes
+import com.dropbear.ffi.generated.DropbearContext
 import com.dropbear.logging.Logger
-import com.dropbear.math.Transform
-import com.dropbear.math.Vector2D
-import com.dropbear.physics.AxisLock
-import com.dropbear.physics.Collider
-import com.dropbear.physics.Index
-import com.dropbear.physics.RigidBody
-import com.dropbear.scene.SceneLoadHandle
-import com.dropbear.scene.SceneLoadStatus
-import com.dropbear.utils.Progress
-import kotlinx.cinterop.*
+import kotlinx.cinterop.COpaquePointer
+import kotlinx.cinterop.ExperimentalForeignApi
+import kotlinx.cinterop.interpretCPointer
 import kotlin.experimental.ExperimentalNativeApi
 
 actual class NativeEngine {
@@ -88,1563 +67,4 @@ actual class NativeEngine {
             }
         }
     }
-
-    actual fun getEntity(label: String): Long? {
-        val world = worldHandle ?: return null
-        memScoped {
-            val outEntity = alloc<LongVar>()
-            val result = dropbear_get_entity(
-                label = label,
-                world_ptr = world.reinterpret(),
-                out_entity = outEntity.ptr
-            )
-            return if (result == 0) outEntity.value else if (exceptionOnError) {
-                throw DropbearNativeException("getEntity failed with code: $result")
-            } else {
-                println("getEntity failed with code: $result")
-                null
-            }
-        }
-    }
-
-    actual fun getEntityLabel(entityHandle: Long): String? {
-        val world = worldHandle ?: return null
-        memScoped {
-            val bufferSize = 256
-            val outLabel = allocArray<ByteVar>(bufferSize)
-
-            val result = dropbear_get_entity_name(
-                world_ptr = world.reinterpret(),
-                entity_id = entityHandle,
-                out_name = outLabel,
-                max_len = bufferSize.toULong()
-            )
-
-            if (result == 0) {
-                return outLabel.toKString()
-            } else {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("getEntityLabel failed with code for entity '$entityHandle': $result")
-                } else {
-                    println("getEntityLabel failed with code: $result")
-                    return null
-                }
-            }
-        }
-    }
-
-    actual fun getTransform(entityId: EntityId): EntityTransform? {
-        val world = worldHandle ?: return null
-        memScoped {
-            val outTransform = alloc<NativeEntityTransform>()
-            val result = dropbear_get_transform(
-                world_ptr = world.reinterpret(),
-                entity_handle = entityId.id,
-                out_transform = outTransform.ptr
-            )
-            if (result == 0) {
-                return EntityTransform(
-                    local = Transform(
-                        position = com.dropbear.math.Vector3D(
-                            outTransform.local.position_x,
-                            outTransform.local.position_y,
-                            outTransform.local.position_z
-                        ),
-                        rotation = com.dropbear.math.QuaternionD(
-                            outTransform.local.rotation_x,
-                            outTransform.local.rotation_y,
-                            outTransform.local.rotation_z,
-                            outTransform.local.rotation_w
-                        ),
-                        scale = com.dropbear.math.Vector3D(
-                            outTransform.local.scale_x, outTransform.local.scale_y,
-                            outTransform.local.scale_z
-                        )
-                    ),
-                    world = Transform(
-                        position = com.dropbear.math.Vector3D(
-                            outTransform.world.position_x,
-                            outTransform.world.position_y,
-                            outTransform.world.position_z
-                        ),
-                        rotation = com.dropbear.math.QuaternionD(
-                            outTransform.world.rotation_x,
-                            outTransform.world.rotation_y,
-                            outTransform.world.rotation_z,
-                            outTransform.world.rotation_w
-                        ),
-                        scale = com.dropbear.math.Vector3D(
-                            outTransform.world.scale_x,
-                            outTransform.world.scale_y,
-                            outTransform.world.scale_z
-                        )
-                    )
-                )
-            } else {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("getTransform failed with code: $result")
-                } else {
-                    println("getTransform failed with code: $result")
-                    return null
-                }
-            }
-        }
-    }
-
-    actual fun propagateTransform(entityId: EntityId): Transform? {
-        val world = worldHandle ?: return null
-        memScoped {
-            val outTransform = alloc<NativeTransform>()
-            val result = dropbear_propagate_transform(
-                world_ptr = world.reinterpret(),
-                entity_id = entityId.id,
-                out_transform = outTransform.ptr
-            )
-            if (result == 0) {
-                return Transform(
-                    position = com.dropbear.math.Vector3D(
-                        outTransform.position_x,
-                        outTransform.position_y,
-                        outTransform.position_z
-                    ),
-                    rotation = com.dropbear.math.QuaternionD(
-                        outTransform.rotation_x,
-                        outTransform.rotation_y,
-                        outTransform.rotation_z,
-                        outTransform.rotation_w
-                    ),
-                    scale = com.dropbear.math.Vector3D(
-                        outTransform.scale_x,
-                        outTransform.scale_y,
-                        outTransform.scale_z
-                    )
-                )
-            } else {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("propagateTransform failed with code: $result")
-                } else {
-                    println("propagateTransform failed with code: $result")
-                    return null
-                }
-            }
-        }
-    }
-
-    actual fun setTransform(entityId: EntityId, transform: EntityTransform) {
-        val worldHandle = worldHandle ?: return
-        memScoped {
-            val nativeTransform = cValue<NativeEntityTransform> {
-                local.position_x = transform.local.position.x
-                local.position_y = transform.local.position.y
-                local.position_z = transform.local.position.z
-                local.rotation_w = transform.local.rotation.w
-                local.rotation_x = transform.local.rotation.x
-                local.rotation_y = transform.local.rotation.y
-                local.rotation_z = transform.local.rotation.z
-                local.scale_x = transform.local.scale.x
-                local.scale_y = transform.local.scale.y
-                local.scale_z = transform.local.scale.z
-
-                world.position_x = transform.world.position.x
-                world.position_y = transform.world.position.y
-                world.position_z = transform.world.position.z
-                world.rotation_w = transform.world.rotation.w
-                world.rotation_x = transform.world.rotation.x
-                world.rotation_y = transform.world.rotation.y
-                world.rotation_z = transform.world.rotation.z
-                world.scale_x = transform.world.scale.x
-                world.scale_y = transform.world.scale.y
-                world.scale_z = transform.world.scale.z
-            }
-
-            val result = dropbear_set_transform(
-                world_ptr = worldHandle.reinterpret(),
-                entity_id = entityId.id,
-                transform = nativeTransform
-            )
-
-            if (result != 0) {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("setTransform failed with code: $result")
-                } else {
-                    println("setTransform failed with code: $result")
-                }
-            }
-        }
-    }
-
-    actual fun printInputState() {
-        val input = inputHandle ?: return
-        dropbear_print_input_state(input_ptr = input.reinterpret())
-    }
-
-    actual fun isKeyPressed(key: KeyCode): Boolean {
-        val input = inputHandle ?: return false
-        memScoped {
-            val out = alloc<IntVar>()
-            val result = dropbear_is_key_pressed(
-                input.reinterpret(),
-                key.ordinal,
-                out.ptr
-            )
-            return if (result == 0) out.value != 0 else if (exceptionOnError) {
-                throw DropbearNativeException("isKeyPressed failed with code: $result")
-            } else {
-                println("isKeyPressed failed with code: $result")
-                false
-            }
-        }
-    }
-
-    actual fun getMousePosition(): Vector2D? {
-        val input = inputHandle ?: return null
-        memScoped {
-            val xVar = alloc<FloatVar>()
-            val yVar = alloc<FloatVar>()
-
-            val result = dropbear_get_mouse_position(
-                input.reinterpret(),
-                xVar.ptr,
-                yVar.ptr
-            )
-
-            if (result == 0) {
-                val x = xVar.value.toDouble()
-                val y = yVar.value.toDouble()
-                return Vector2D(x, y)
-            } else {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("getMousePosition failed with code: $result")
-                } else {
-                    println("getMousePosition failed with code: $result")
-                    return null
-                }
-            }
-        }
-    }
-
-    actual fun isMouseButtonPressed(button: MouseButton): Boolean {
-        val buttonCode: Int = when (button) {
-            MouseButton.Left -> MouseButtonCodes.LEFT
-            MouseButton.Right -> MouseButtonCodes.RIGHT
-            MouseButton.Middle -> MouseButtonCodes.MIDDLE
-            MouseButton.Back -> MouseButtonCodes.BACK
-            MouseButton.Forward -> MouseButtonCodes.FORWARD
-            is MouseButton.Other -> button.value
-        }
-
-        val input = inputHandle ?: return false
-
-        memScoped {
-            val pressedVar = alloc<IntVar>()
-
-            val result = dropbear_is_mouse_button_pressed(
-                input.reinterpret(),
-                buttonCode,
-                pressedVar.ptr
-            )
-
-            if (result == 0) {
-                return pressedVar.value != 0
-            } else {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("isMouseButtonPressed failed with code: $result")
-                } else {
-                    println("isMouseButtonPressed failed with code: $result")
-                    return false
-                }
-            }
-        }
-    }
-
-    actual fun getMouseDelta(): Vector2D? {
-        val input = inputHandle ?: return null
-        memScoped {
-            val deltaXVar = alloc<FloatVar>()
-            val deltaYVar = alloc<FloatVar>()
-
-            val result = dropbear_get_mouse_delta(
-                input.reinterpret(),
-                deltaXVar.ptr,
-                deltaYVar.ptr
-            )
-
-            if (result == 0) {
-                val deltaX = deltaXVar.value.toDouble()
-                val deltaY = deltaYVar.value.toDouble()
-                return Vector2D(deltaX, deltaY)
-            } else {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("getMouseDelta failed with code: $result")
-                } else {
-                    println("getMouseDelta failed with code: $result")
-                    return null
-                }
-            }
-        }
-    }
-
-    actual fun isCursorLocked(): Boolean {
-        val input = inputHandle ?: return false
-        memScoped {
-            val lockedVar = alloc<IntVar>()
-
-            val result = dropbear_is_cursor_locked(
-                input.reinterpret(),
-                lockedVar.ptr
-            )
-
-            if (result == 0) {
-                return lockedVar.value != 0
-            } else {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("isCursorLocked failed with code: $result")
-                } else {
-                    println("isCursorLocked failed with code: $result")
-                    return false
-                }
-            }
-        }
-    }
-
-    actual fun setCursorLocked(locked: Boolean) {
-        val lockedInt = if (locked) 1 else 0
-        val input = inputHandle ?: return
-        val graphics = commandBufferHandle ?: return
-
-        val result = dropbear_set_cursor_locked(
-            input.reinterpret(),
-            graphics.reinterpret(),
-            lockedInt
-        )
-
-        if (result != 0) {
-            if (exceptionOnError) {
-                throw DropbearNativeException("setCursorLocked failed with code: $result")
-            } else {
-                println("setCursorLocked failed with code: $result")
-            }
-        }
-    }
-
-    actual fun getLastMousePos(): Vector2D? {
-        val input = inputHandle ?: return null
-        memScoped {
-            val xVar = alloc<FloatVar>()
-            val yVar = alloc<FloatVar>()
-
-            val result = dropbear_get_last_mouse_pos(
-                input.reinterpret(),
-                xVar.ptr,
-                yVar.ptr
-            )
-
-            if (result == 0) {
-                val x = xVar.value.toDouble()
-                val y = yVar.value.toDouble()
-                return Vector2D(x, y)
-            } else {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("getLastMousePos failed with code: $result")
-                } else {
-                    println("getLastMousePos failed with code: $result")
-                    return null
-                }
-            }
-        }
-    }
-
-    actual fun isCursorHidden(): Boolean {
-        val input = inputHandle ?: return false
-        memScoped {
-            val hiddenVar = alloc<IntVar>()
-
-            val result = dropbear_is_cursor_hidden(
-                input.reinterpret(),
-                hiddenVar.ptr
-            )
-
-            if (result == 0) {
-                return hiddenVar.value != 0
-            } else {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("isCursorHidden failed with code: $result")
-                } else {
-                    println("isCursorHidden failed with code: $result")
-                    return false
-                }
-            }
-        }
-    }
-
-    actual fun setCursorHidden(hidden: Boolean) {
-        val hiddenInt = if (hidden) 1 else 0
-        val input = inputHandle ?: return
-        val graphics = commandBufferHandle ?: return
-
-        val result = dropbear_set_cursor_hidden(
-            input.reinterpret(),
-            graphics.reinterpret(),
-            hiddenInt
-        )
-
-        if (result != 0) {
-            if (exceptionOnError) {
-                throw DropbearNativeException("setCursorHidden failed with code: $result")
-            } else {
-                println("setCursorHidden failed with code: $result")
-            }
-        }
-    }
-
-    actual fun getConnectedGamepads(): List<Gamepad> {
-        val input = inputHandle ?: return emptyList()
-        memScoped {
-            val gamepadsPtr = alloc<CPointerVar<com.dropbear.ffi.generated.Gamepad>>()
-            val count = alloc<IntVar>()
-
-            val result = dropbear_get_connected_gamepads(
-                input.reinterpret(),
-                gamepadsPtr.ptr,
-                count.ptr
-            )
-
-            if (result == 0) {
-                val gamepadArray = gamepadsPtr.value
-                val gamepadCount = count.value
-
-                if (gamepadArray == null || gamepadCount == 0) {
-                    return emptyList()
-                }
-
-                return List(gamepadCount) { index ->
-                    val nativeGamepad = gamepadArray[index]
-                    Gamepad(
-                        id = nativeGamepad.id.toLong(),
-                        leftStickPosition = Vector2D(
-                            x = nativeGamepad.left_stick_pos.x,
-                            y = nativeGamepad.left_stick_pos.y
-                        ),
-                        rightStickPosition = Vector2D(
-                            x = nativeGamepad.right_stick_pos.x,
-                            y = nativeGamepad.right_stick_pos.y
-                        ),
-                        native = this@NativeEngine
-                    )
-                }
-            } else {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("getConnectedGamepads failed with code: $result")
-                } else {
-                    println("getConnectedGamepads failed with code: $result")
-                    return emptyList()
-                }
-            }
-        }
-    }
-
-    actual fun isGamepadButtonPressed(id: Long, button: GamepadButton): Boolean {
-        val input = inputHandle ?: return false
-        memScoped {
-            val outPressed = alloc<IntVar>()
-
-            val result = dropbear_is_gamepad_button_pressed(
-                input_ptr = input.reinterpret(),
-                gamepad_id = id,
-                ordinal = button.ordinal,
-                out_pressed = outPressed.ptr
-            )
-
-            return if (result == 0) {
-                outPressed.value != 0
-            } else if (exceptionOnError) {
-                throw DropbearNativeException(
-                    "isGamepadButtonPressed failed for gamepadId='$id' button='${button.name}' with code: $result"
-                )
-            } else {
-                println(
-                    "isGamepadButtonPressed failed for gamepadId='$id' button='${button.name}' with code: $result"
-                )
-                false
-            }
-        }
-    }
-
-    actual fun getStringProperty(entityHandle: Long, label: String): String? {
-        val world = worldHandle ?: return null
-        memScoped {
-            val output = alloc<CPointerVar<ByteVar>>()
-
-            val result = dropbear_get_string_property(
-                world.reinterpret(),
-                entityHandle,
-                label,
-                output.ptr
-            )
-
-            if (result == 0) {
-                val string = output.value?.toKString()
-                return string
-            } else {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("getStringProperty [$label] failed with code: $result")
-                } else {
-                    println("getStringProperty [$label] failed with code: $result")
-                    return null
-                }
-            }
-        }
-    }
-
-    actual fun getIntProperty(entityHandle: Long, label: String): Int? {
-        val world = worldHandle ?: return null
-        memScoped {
-            val output = alloc<IntVar>()
-
-            val result = dropbear_get_int_property(
-                world.reinterpret(),
-                entityHandle,
-                label,
-                output.ptr,
-            )
-
-            if (result == 0) {
-                return output.value
-            } else {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("getIntProperty [$label] failed with code: $result")
-                } else {
-                    println("getIntProperty [$label] failed with code: $result")
-                    return null
-                }
-            }
-        }
-    }
-
-    actual fun getLongProperty(entityHandle: Long, label: String): Long? {
-        val world = worldHandle ?: return null
-        memScoped {
-            val output = alloc<LongVar>()
-
-            val result = dropbear_get_long_property(
-                world.reinterpret(),
-                entityHandle,
-                label,
-                output.ptr
-            )
-
-            if (result == 0) {
-                return output.value
-            } else {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("getLongProperty [$label] failed with code: $result")
-                } else {
-                    println("getLongProperty [$label] failed with code: $result")
-                    return null
-                }
-            }
-        }
-    }
-
-    actual fun getFloatProperty(entityHandle: Long, label: String): Float? {
-        val world = worldHandle ?: return null
-        memScoped {
-            val output = alloc<DoubleVar>()
-
-            val result = dropbear_get_float_property(
-                world.reinterpret(),
-                entityHandle,
-                label,
-                output.ptr
-            )
-
-            if (result == 0) {
-                return output.value.toFloat()
-            } else {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("getFloatProperty [$label] failed with code: $result")
-                } else {
-                    println("getFloatProperty [$label] failed with code: $result")
-                    return null
-                }
-            }
-        }
-    }
-
-    actual fun getDoubleProperty(entityHandle: Long, label: String): Double? {
-        val world = worldHandle ?: return null
-        memScoped {
-            val output = alloc<DoubleVar>()
-
-            val result = dropbear_get_float_property(
-                world.reinterpret(),
-                entityHandle,
-                label,
-                output.ptr
-            )
-
-            if (result == 0) {
-                return output.value
-            } else {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("getDoubleProperty [$label] failed with code: $result")
-                } else {
-                    println("getDoubleProperty [$label] failed with code: $result")
-                    return null
-                }
-            }
-        }
-    }
-
-    actual fun getBoolProperty(entityHandle: Long, label: String): Boolean? {
-        val world = worldHandle ?: return null
-        memScoped {
-            val output = alloc<IntVar>()
-
-            val result = dropbear_get_bool_property(
-                world.reinterpret(),
-                entityHandle,
-                label,
-                output.ptr
-            )
-
-            if (result == 0) {
-                return output.value != 0
-            } else {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("getBoolProperty [$label] failed with code: $result")
-                } else {
-                    println("getBoolProperty [$label] failed with code: $result")
-                    return null
-                }
-            }
-        }
-    }
-
-    actual fun getVec3Property(entityHandle: Long, label: String): FloatArray? {
-        val world = worldHandle ?: return null
-        memScoped {
-            val outVec = alloc<Vector3D>()
-
-            val result = dropbear_get_vec3_property(
-                world.reinterpret(),
-                entityHandle,
-                label,
-                outVec.ptr
-            )
-
-            if (result == 0) {
-                return floatArrayOf(outVec.x.toFloat(), outVec.y.toFloat(), outVec.z.toFloat())
-            } else {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("getVec3Property [$label] failed with code: $result")
-                } else {
-                    println("getVec3Property [$label] failed with code: $result")
-                    return null
-                }
-            }
-        }
-    }
-
-    actual fun setStringProperty(entityHandle: Long, label: String, value: String) {
-        val world = worldHandle ?: return
-
-        val result = dropbear_set_string_property(
-            world.reinterpret(),
-            entityHandle,
-            label,
-            value
-        )
-
-        if (result != 0) {
-            if (exceptionOnError) {
-                throw DropbearNativeException("setStringProperty [$label] failed with code: $result")
-            } else {
-                println("setStringProperty [$label] failed with code: $result")
-            }
-        }
-    }
-
-    actual fun setIntProperty(entityHandle: Long, label: String, value: Int) {
-        val world = worldHandle ?: return
-
-        val result = dropbear_set_int_property(
-            world.reinterpret(),
-            entityHandle,
-            label,
-            value
-        )
-
-        if (result != 0) {
-            if (exceptionOnError) {
-                throw DropbearNativeException("setIntProperty [$label] failed with code: $result")
-            } else {
-                println("setIntProperty [$label] failed with code: $result")
-            }
-        }
-    }
-
-    actual fun setLongProperty(entityHandle: Long, label: String, value: Long) {
-        val world = worldHandle ?: return
-
-        val result = dropbear_set_long_property(
-            world.reinterpret(),
-            entityHandle,
-            label,
-            value
-        )
-
-        if (result != 0) {
-            if (exceptionOnError) {
-                throw DropbearNativeException("setLongProperty [$label] failed with code: $result")
-            } else {
-                println("setLongProperty [$label] failed with code: $result")
-            }
-        }
-    }
-
-    actual fun setFloatProperty(entityHandle: Long, label: String, value: Double) {
-        val world = worldHandle ?: return
-
-        val result = dropbear_set_float_property(
-            world.reinterpret(),
-            entityHandle,
-            label,
-            value
-        )
-
-        if (result != 0) {
-            if (exceptionOnError) {
-                throw DropbearNativeException("setFloatProperty [$label] failed with code: $result")
-            } else {
-                println("setFloatProperty [$label] failed with code: $result")
-            }
-        }
-    }
-
-    actual fun setBoolProperty(entityHandle: Long, label: String, value: Boolean) {
-        val world = worldHandle ?: return
-        val intValue = if (value) 1 else 0
-
-        val result = dropbear_set_bool_property(
-            world.reinterpret(),
-            entityHandle,
-            label,
-            intValue
-        )
-
-        if (result != 0) {
-            if (exceptionOnError) {
-                throw DropbearNativeException("setBoolProperty [$label] failed with code: $result")
-            } else {
-                println("setBoolProperty [$label] failed with code: $result")
-            }
-        }
-    }
-
-    actual fun setVec3Property(entityHandle: Long, label: String, value: FloatArray) {
-        val world = worldHandle ?: return
-
-        if (value.size < 3) {
-            if (exceptionOnError) {
-                throw DropbearNativeException("setVec3Property: FloatArray must have at least 3 elements")
-            } else {
-                println("setVec3Property: FloatArray must have at least 3 elements")
-                return
-            }
-        }
-
-        memScoped {
-            val vec = cValue<Vector3D> {
-                x = value[0].toDouble()
-                y = value[1].toDouble()
-                z = value[2].toDouble()
-            }
-
-            val result = dropbear_set_vec3_property(
-                world.reinterpret(),
-                entityHandle,
-                label,
-                vec
-            )
-
-            if (result != 0) {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("setVec3Property [$label] failed with code: $result")
-                } else {
-                    println("setVec3Property [$label] failed with code: $result")
-                }
-            }
-        }
-    }
-
-    actual fun getCamera(label: String): Camera? {
-        val world = worldHandle ?: return null
-        memScoped {
-            val outCamera = alloc<NativeCamera>()
-
-            val result = dropbear_get_camera(
-                world.reinterpret(),
-                label,
-                outCamera.ptr
-            )
-
-            if (result == 0) {
-                return Camera(
-                    label = outCamera.label?.toKString() ?: "",
-                    id = EntityId(outCamera.entity_id),
-                    eye = com.dropbear.math.Vector3D(
-                        outCamera.eye.x,
-                        outCamera.eye.y,
-                        outCamera.eye.z
-                    ),
-                    target = com.dropbear.math.Vector3D(
-                        outCamera.target.x,
-                        outCamera.target.y,
-                        outCamera.target.z
-                    ),
-                    up = com.dropbear.math.Vector3D(
-                        outCamera.up.x,
-                        outCamera.up.y,
-                        outCamera.up.z
-                    ),
-                    aspect = outCamera.aspect,
-                    fov_y = outCamera.fov_y,
-                    znear = outCamera.znear,
-                    zfar = outCamera.zfar,
-                    yaw = outCamera.yaw,
-                    pitch = outCamera.pitch,
-                    speed = outCamera.speed,
-                    sensitivity = outCamera.sensitivity
-                )
-            } else {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("getCamera failed with code: $result")
-                } else {
-                    println("getCamera failed with code: $result")
-                    return null
-                }
-            }
-        }
-    }
-
-    actual fun getAttachedCamera(entityId: EntityId): Camera? {
-        val world = worldHandle ?: return null
-        memScoped {
-            val outCamera = alloc<NativeCamera>()
-
-            val result = dropbear_get_attached_camera(
-                world.reinterpret(),
-                entityId.id,
-                outCamera.ptr
-            )
-
-            if (result == 0) {
-                return Camera(
-                    label = outCamera.label?.toKString() ?: "",
-                    id = EntityId(outCamera.entity_id),
-                    eye = com.dropbear.math.Vector3D(
-                        outCamera.eye.x,
-                        outCamera.eye.y,
-                        outCamera.eye.z
-                    ),
-                    target = com.dropbear.math.Vector3D(
-                        outCamera.target.x,
-                        outCamera.target.y,
-                        outCamera.target.z
-                    ),
-                    up = com.dropbear.math.Vector3D(
-                        outCamera.up.x,
-                        outCamera.up.y,
-                        outCamera.up.z
-                    ),
-                    aspect = outCamera.aspect,
-                    fov_y = outCamera.fov_y,
-                    znear = outCamera.znear,
-                    zfar = outCamera.zfar,
-                    yaw = outCamera.yaw,
-                    pitch = outCamera.pitch,
-                    speed = outCamera.speed,
-                    sensitivity = outCamera.sensitivity
-                )
-            } else {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("getAttachedCamera failed with code: $result")
-                } else {
-                    println("getAttachedCamera failed with code: $result")
-                    return null
-                }
-            }
-        }
-    }
-
-    actual fun setCamera(camera: Camera) {
-        val world = worldHandle ?: return
-        memScoped {
-            val nativeCamera = cValue<NativeCamera> {
-                label = camera.label.cstr.ptr
-                entity_id = camera.id.id
-
-                eye.x = camera.eye.x
-                eye.y = camera.eye.y
-                eye.z = camera.eye.z
-
-                target.x = camera.target.x
-                target.y = camera.target.y
-                target.z = camera.target.z
-
-                up.x = camera.up.x
-                up.y = camera.up.y
-                up.z = camera.up.z
-
-                aspect = camera.aspect
-                fov_y = camera.fov_y
-                znear = camera.znear
-                zfar = camera.zfar
-
-                yaw = camera.yaw
-                pitch = camera.pitch
-                speed = camera.speed
-                sensitivity = camera.sensitivity
-            }
-
-            val result = dropbear_set_camera(
-                world.reinterpret(),
-                nativeCamera
-            )
-
-            if (result != 0) {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("setCamera failed with code: $result")
-                } else {
-                    println("setCamera failed with code: $result")
-                }
-            }
-        }
-    }
-
-    actual fun getModel(entityHandle: Long): Long? {
-        val world = worldHandle ?: return null
-        val asset = assetHandle ?: return null
-        memScoped {
-            val outModel = alloc<LongVar>()
-            val result = dropbear_get_model(
-                world.reinterpret(),
-                asset.reinterpret(),
-                entityHandle,
-                outModel.ptr
-            )
-            return if (result == 0) outModel.value else if (exceptionOnError) throw DropbearNativeException("getModel failed with code: $result") else null
-        }
-    }
-
-    actual fun setModel(entityHandle: Long, modelHandle: Long) {
-        val world = worldHandle ?: return
-        val asset = assetHandle ?: return
-
-        val result = dropbear_set_model(
-            world.reinterpret(),
-            asset.reinterpret(),
-            entityHandle,
-            modelHandle
-        )
-
-        if (result != 0) {
-            if (exceptionOnError) {
-                throw DropbearNativeException("setModel failed with code: $result")
-            } else {
-                println("setModel failed with code: $result")
-            }
-        }
-    }
-
-    actual fun getTexture(entityHandle: Long, name: String): Long? {
-        val world = worldHandle ?: return null
-        val asset = assetHandle ?: return null
-        memScoped {
-            val outTexture = alloc<LongVar>()
-            val result = dropbear_get_texture(
-                world.reinterpret(),
-                asset.reinterpret(),
-                entityHandle,
-                name,
-                outTexture.ptr
-            )
-            return if (result == 0) outTexture.value else if (exceptionOnError) throw DropbearNativeException("getTexture failed with code: $result") else null
-        }
-    }
-
-    actual fun isUsingModel(entityHandle: Long, modelHandle: Long): Boolean {
-        val world = worldHandle ?: return false
-        memScoped {
-            val outUsing = alloc<IntVar>()
-            val result = dropbear_is_using_model(
-                world.reinterpret(),
-                entityHandle,
-                modelHandle,
-                outUsing.ptr
-            )
-            return if (result == 0) outUsing.value != 0 else false
-        }
-    }
-
-    actual fun isUsingTexture(entityHandle: Long, textureHandle: Long): Boolean {
-        val world = worldHandle ?: return false
-        memScoped {
-            val outUsing = alloc<IntVar>()
-            val result = dropbear_is_using_texture(
-                world.reinterpret(),
-                entityHandle,
-                textureHandle,
-                outUsing.ptr
-            )
-            return if (result == 0) outUsing.value != 0 else false
-        }
-    }
-
-    actual fun getAsset(eucaURI: String): Long? {
-        val asset = assetHandle ?: return null
-        memScoped {
-            val outAsset = alloc<LongVar>()
-            val result = dropbear_get_asset(
-                asset.reinterpret(),
-                eucaURI,
-                outAsset.ptr
-            )
-            return if (result == 0) outAsset.value else null
-        }
-    }
-
-    actual fun isModelHandle(id: Long): Boolean {
-        val asset = assetHandle ?: return false
-        memScoped {
-            val outIsModel = alloc<IntVar>()
-            val result = dropbear_is_model_handle(
-                asset.reinterpret(),
-                id,
-                outIsModel.ptr
-            )
-            return if (result == 0) outIsModel.value != 0 else false
-        }
-    }
-
-    actual fun isTextureHandle(id: Long): Boolean {
-        val asset = assetHandle ?: return false
-        memScoped {
-            val outIsTexture = alloc<IntVar>()
-            val result = dropbear_is_texture_handle(
-                asset.reinterpret(),
-                id,
-                outIsTexture.ptr
-            )
-            return if (result == 0) outIsTexture.value != 0 else false
-        }
-    }
-
-    actual fun setTextureOverride(entityHandle: Long, oldMaterialName: String, newTextureHandle: TextureHandle) {
-        val world = worldHandle ?: return
-        val asset = assetHandle ?: return
-
-        val result = dropbear_set_texture(
-            world.reinterpret(),
-            asset.reinterpret(),
-            entityHandle,
-            oldMaterialName,
-            newTextureHandle.raw()
-        )
-
-        if (result != 0) {
-            if (exceptionOnError) {
-                throw DropbearNativeException("setTextureOverride failed with code: $result")
-            } else {
-                println("setTextureOverride failed with code: $result")
-            }
-        }
-    }
-
-    actual fun getTextureName(textureHandle: Long): String? {
-        val asset = assetHandle ?: return null
-        memScoped {
-            val outName = alloc<CPointerVar<ByteVar>>()
-            val result = dropbear_get_texture_name(
-                asset.reinterpret(),
-                textureHandle,
-                outName.ptr
-            )
-            return if (result == 0) outName.value?.toKString() else if (exceptionOnError) throw DropbearNativeException("getTextureName failed with code: $result") else null
-        }
-    }
-
-    actual fun getAllTextures(entityHandle: Long): Array<String> {
-        val world = worldHandle ?: return emptyArray()
-        val asset = assetHandle ?: return emptyArray()
-        memScoped {
-            val outTextures = alloc<CPointerVar<CPointerVar<ByteVar>>>()
-            val outCount = alloc<ULongVar>()
-
-            val result = dropbear_get_all_textures(
-                asset.reinterpret(),
-                world.reinterpret(),
-                entityHandle,
-                outTextures.ptr,
-                outCount.ptr
-            )
-
-            if (result == 0) {
-                val count = outCount.value.toInt()
-                val textureArray = Array(count) { i ->
-                    outTextures.value!![i]?.toKString() ?: ""
-                }
-                return textureArray
-            } else {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("getAllTextures failed with code: $result")
-                } else {
-                    println("getAllTextures failed with code: $result")
-                    return emptyArray()
-                }
-            }
-        }
-    }
-
-    actual fun getChildren(entityId: EntityId): Array<EntityRef>? {
-        val world = worldHandle ?: return null
-        memScoped {
-            val outChildren = alloc<CPointerVar<LongVar>>()
-            val outCount = alloc<ULongVar>()
-
-            val result = dropbear_get_children(
-                world.reinterpret(),
-                entityId.id,
-                outChildren.ptr,
-                outCount.ptr
-            )
-
-            if (result == 0) {
-                val count = outCount.value.toInt()
-                val childArray = Array(count) { i ->
-                    EntityRef(EntityId(outChildren.value!![i]))
-                }
-                return childArray
-            } else {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("getChildren failed with code: $result")
-                } else {
-                    println("getChildren failed with code: $result")
-                    return null
-                }
-            }
-        }
-    }
-
-    actual fun getChildByLabel(entityId: EntityId, label: String): EntityRef? {
-        val world = worldHandle ?: return null
-        memScoped {
-            val outChild = alloc<LongVar>()
-            val result = dropbear_get_child_by_label(
-                world.reinterpret(),
-                entityId.id,
-                label,
-                outChild.ptr
-            )
-            return if (result == 0) EntityRef(EntityId(outChild.value)) else if (exceptionOnError) throw DropbearNativeException("getChildByLabel failed with code: $result") else null
-        }
-    }
-
-    actual fun getParent(entityId: EntityId): EntityRef? {
-        val world = worldHandle ?: return null
-        memScoped {
-            val outParent = alloc<LongVar>()
-            val result = dropbear_get_parent(
-                world.reinterpret(),
-                entityId.id,
-                outParent.ptr
-            )
-            return if (result == 0) EntityRef(EntityId(outParent.value)) else if (exceptionOnError) throw DropbearNativeException("getParent failed with code: $result") else null
-        }
-    }
-
-    actual fun quit() {
-        val command = commandBufferHandle ?: if (exceptionOnError) throw DropbearNativeException("Unable to quit: graphicsHandle does not exist") else return
-        dropbear_quit(command.reinterpret())
-    }
-
-    actual fun loadSceneAsync(sceneName: String): SceneLoadHandle? {
-        val sceneLoader = sceneLoaderHandle ?: if (exceptionOnError) throw DropbearNativeException("sceneLoaderHandle is empty") else return null
-        val commandBuffer = commandBufferHandle ?: if (exceptionOnError) throw DropbearNativeException("commandBufferHandle is empty") else return null
-        memScoped {
-            val outHandle = alloc<com.dropbear.ffi.generated.SceneLoadHandle>()
-            val result = dropbear_load_scene_async_1(
-                command_ptr = commandBuffer.reinterpret(),
-                scene_loader_ptr = sceneLoader.reinterpret(),
-                name = sceneName,
-                sceneLoadHandle = outHandle.ptr
-            )
-
-            if (result != 0) {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("loadSceneAsync failed with code: $result")
-                } else {
-                    println("loadSceneAsync failed with code: $result")
-                }
-            }
-
-            return SceneLoadHandle(
-                id = outHandle.id,
-                sceneName = outHandle.name?.toKString() ?: throw Exception("loadSceneAsync failed: sceneName is null"),
-                native = this@NativeEngine,
-            )
-        }
-    }
-
-    actual fun loadSceneAsync(sceneName: String, loadingScene: String): SceneLoadHandle? {
-        val sceneLoader = sceneLoaderHandle ?: if (exceptionOnError) throw DropbearNativeException("sceneLoaderHandle is empty") else return null
-        val commandBuffer = commandBufferHandle ?: if (exceptionOnError) throw DropbearNativeException("commandBufferHandle is empty") else return null
-        memScoped {
-            val outHandle = alloc<com.dropbear.ffi.generated.SceneLoadHandle>()
-            val result = dropbear_load_scene_async_2(
-                command_ptr = commandBuffer.reinterpret(),
-                scene_loader_ptr = sceneLoader.reinterpret(),
-                name = sceneName,
-                loadingScene = loadingScene,
-                sceneLoadHandle = outHandle.ptr
-            )
-
-            if (result != 0) {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("loadSceneAsync with loading scene failed with code: $result")
-                } else {
-                    println("loadSceneAsync with loading scene failed with code: $result")
-                }
-            }
-
-            return SceneLoadHandle(
-                id = outHandle.id,
-                sceneName = outHandle.name?.toKString() ?: throw Exception("loadSceneAsync with loading scene failed: sceneName is null"),
-                native = this@NativeEngine,
-            )
-        }
-    }
-
-    actual fun switchToSceneAsync(sceneLoadHandle: SceneLoadHandle) {
-        val command = commandBufferHandle ?: if (exceptionOnError) throw DropbearNativeException("commandBufferHandle is empty") else return
-        memScoped {
-            val nativeHandle = alloc<com.dropbear.ffi.generated.SceneLoadHandle>()
-            nativeHandle.id = sceneLoadHandle.id
-            nativeHandle.name = sceneLoadHandle.sceneName.cstr.ptr
-
-            val result = dropbear_switch_to_scene_async(command.reinterpret(), nativeHandle.readValue())
-
-            if (result == -10) {
-                throw PrematureSceneSwitchException("Attempted to switch to scene before it finished loading")
-            }
-
-            if (result != 0) {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("switchToSceneAsync failed with code: $result")
-                } else {
-                    println("switchToSceneAsync failed with code: $result")
-                }
-            }
-        }
-    }
-
-    actual fun switchToSceneImmediate(sceneName: String) {
-        val command = commandBufferHandle ?: if (exceptionOnError) throw DropbearNativeException("commandBufferHandle is empty") else return
-        val result = dropbear_switch_to_scene_immediate(
-            command_ptr = command.reinterpret(),
-            name = sceneName,
-        )
-
-        if (result != 0) {
-            if (exceptionOnError) {
-                throw DropbearNativeException("switchToSceneImmediate failed with code: $result")
-            } else {
-                println("switchToSceneImmediate failed with code: $result")
-            }
-        }
-    }
-
-    actual fun getSceneLoadProgress(sceneLoadHandle: SceneLoadHandle): Progress {
-        val sceneLoader = sceneLoaderHandle ?: if (exceptionOnError) throw DropbearNativeException("sceneLoaderHandle is empty") else return Progress.nothing("Error: commandBufferHandle is empty")
-        memScoped {
-            val nativeHandle = alloc<com.dropbear.ffi.generated.SceneLoadHandle>()
-            nativeHandle.id = sceneLoadHandle.id
-            nativeHandle.name = sceneLoadHandle.sceneName.cstr.ptr
-
-            val outProgress = alloc<com.dropbear.ffi.generated.Progress>()
-
-            val result = dropbear_get_scene_load_progress(
-                scene_loader_ptr = sceneLoader.reinterpret(),
-                handle = nativeHandle.readValue(),
-                progress = outProgress.ptr
-            )
-
-            if (result != 0) {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("getSceneLoadProgress failed with code: $result")
-                } else {
-                    println("getSceneLoadProgress failed with code: $result")
-                }
-            }
-
-            val progress = Progress(outProgress.current, outProgress.total, outProgress.message?.toKString());
-
-            return progress
-        }
-    }
-
-    actual fun getSceneLoadStatus(sceneLoadHandle: SceneLoadHandle): SceneLoadStatus {
-        val sceneLoader = sceneLoaderHandle ?: if (exceptionOnError) throw DropbearNativeException("sceneLoaderHandle is empty") else return SceneLoadStatus.FAILED
-        memScoped {
-            val nativeHandle = alloc<com.dropbear.ffi.generated.SceneLoadHandle>()
-            nativeHandle.id = sceneLoadHandle.id
-            nativeHandle.name = sceneLoadHandle.sceneName.cstr.ptr
-
-            val nativeStatus = alloc<SceneLoadResult.Var>()
-
-            val result = dropbear_get_scene_load_status(
-                scene_loader_ptr = sceneLoader.reinterpret(),
-                handle = nativeHandle.readValue(),
-                result = nativeStatus.ptr
-            )
-            if (result == 0) {
-                return nativeStatus.value.fromNative()
-            } else {
-                if (exceptionOnError) {
-                    throw DropbearNativeException("getSceneLoadStatus failed with code: $result")
-                } else {
-                    println("getSceneLoadStatus failed with code: $result")
-                    return SceneLoadStatus.FAILED
-                }
-            }
-        }
-    }
-
-    actual fun setPhysicsEnabled(entityId: Long, enabled: Boolean) {
-        val world = worldHandle ?: if (exceptionOnError) throw DropbearNativeException("world handle null") else return
-        val pe = physicsEngineHandle ?: if (exceptionOnError) throw DropbearNativeException("Physics engine handle is null") else return
-
-        val result = dropbear_set_physics_enabled(
-            world.reinterpret(),
-            pe.reinterpret(),
-            entityId,
-            enabled
-        )
-
-        handleResult(result, "setPhysicsEnabled")
-    }
-
-    actual fun isPhysicsEnabled(entityId: Long): Boolean {
-        val world = worldHandle ?: if (exceptionOnError) throw DropbearNativeException("world handle null") else return false
-        val pe = physicsEngineHandle ?: if (exceptionOnError) throw DropbearNativeException("Physics engine handle is null") else return false
-
-        memScoped {
-            val outEnabled = alloc<BooleanVar>()
-
-            val result = dropbear_is_physics_enabled(
-                world.reinterpret(),
-                pe.reinterpret(),
-                entityId,
-                outEnabled.ptr
-            )
-
-            if (result != 0) {
-                handleResult(result, "isPhysicsEnabled")
-                return false
-            }
-
-            return outEnabled.value
-        }
-    }
-
-    actual fun getRigidBody(entityId: Long): RigidBody? {
-        val world = worldHandle ?: if (exceptionOnError) throw DropbearNativeException("world handle null") else return null
-        val pe = physicsEngineHandle ?: if (exceptionOnError) throw DropbearNativeException("Physics engine handle is null") else return null
-
-        memScoped {
-            val outRigidBody = alloc<com.dropbear.ffi.generated.RigidBody>()
-
-            val result = dropbear_get_rigidbody(
-                world.reinterpret(),
-                pe.reinterpret(),
-                entityId,
-                outRigidBody.ptr
-            )
-
-            if (result != 0) {
-                handleResult(result, "getRigidBody")
-                return null
-            }
-
-            return outRigidBody.toKotlin(this@NativeEngine)
-        }
-    }
-
-    actual fun getAllColliders(entityId: Long): List<Collider> {
-        val world = worldHandle ?: if (exceptionOnError) throw DropbearNativeException("world handle null") else return emptyList()
-        val pe = physicsEngineHandle ?: if (exceptionOnError) throw DropbearNativeException("Physics engine handle is null") else return emptyList()
-
-        memScoped {
-            val outCollidersPtr = alloc<CPointerVar<com.dropbear.ffi.generated.Collider>>()
-            val outCount = alloc<UIntVar>()
-
-            val result = dropbear_get_all_colliders(
-                world.reinterpret(),
-                pe.reinterpret(),
-                entityId,
-                outCollidersPtr.ptr,
-                outCount.ptr
-            )
-
-            if (result != 0) {
-                handleResult(result, "getAllColliders")
-                return emptyList()
-            }
-
-            val count = outCount.value.toInt()
-            val cArray = outCollidersPtr.value
-
-            if (cArray == null || count == 0) {
-                return emptyList()
-            }
-
-            val list = ArrayList<Collider>(count)
-            for (i in 0 until count) {
-                val cCollider = cArray[i]
-                list.add(cCollider.toKotlin(this@NativeEngine))
-            }
-
-            dropbear_free_colliders(cArray, outCount.value)
-
-            return list
-        }
-    }
-
-    actual fun applyImpulse(index: Index, x: Double, y: Double, z: Double) {
-        val pe = physicsEngineHandle ?: return
-
-        memScoped {
-            val cIndex = alloc<com.dropbear.ffi.generated.Index>()
-            cIndex.index = index.index
-            cIndex.generation = index.generation
-
-            val cImpulse = alloc<com.dropbear.ffi.generated.Vector3D>()
-            cImpulse.x = x
-            cImpulse.y = y
-            cImpulse.z = z
-
-            dropbear_apply_impulse(
-                pe.reinterpret(),
-                cIndex.readValue(),
-                cImpulse.readValue()
-            )
-        }
-    }
-
-    actual fun applyTorqueImpulse(index: Index, x: Double, y: Double, z: Double) {
-        val pe = physicsEngineHandle ?: return
-
-        memScoped {
-            val cIndex = alloc<com.dropbear.ffi.generated.Index>()
-            cIndex.index = index.index
-            cIndex.generation = index.generation
-
-            val cTorque = alloc<Vector3D>()
-            cTorque.x = x
-            cTorque.y = y
-            cTorque.z = z
-
-            dropbear_apply_torque_impulse(
-                pe.reinterpret(),
-                cIndex.readValue(),
-                cTorque.readValue()
-            )
-        }
-    }
-
-    actual fun setRigidbody(rigidBody: RigidBody) {
-        val world = worldHandle ?: if (exceptionOnError) throw DropbearNativeException("world handle null") else return
-        val pe = physicsEngineHandle ?: if (exceptionOnError) throw DropbearNativeException("Handle null") else return
-
-        memScoped {
-            val cBody = alloc<com.dropbear.ffi.generated.RigidBody>()
-            rigidBody.populateCStruct(cBody)
-
-            val result = dropbear_set_rigidbody(
-                world.reinterpret(),
-                pe.reinterpret(),
-                cBody.readValue()
-            )
-            handleResult(result, "setRigidbody")
-        }
-    }
-
-    actual fun getChildColliders(index: Index): List<Collider> {
-        val world = worldHandle ?: if (exceptionOnError) throw DropbearNativeException("world handle null") else return emptyList()
-        val pe = physicsEngineHandle ?: if (exceptionOnError) throw DropbearNativeException("Handle null") else return emptyList()
-
-        memScoped {
-            val cIndex = alloc<com.dropbear.ffi.generated.Index>()
-            cIndex.index = index.index
-            cIndex.generation = index.generation
-
-            val outCollidersPtr = alloc<CPointerVar<com.dropbear.ffi.generated.Collider>>()
-            val outCount = alloc<UIntVar>()
-
-            val result = dropbear_get_child_colliders(
-                world.reinterpret(),
-                pe.reinterpret(),
-                cIndex.readValue(),
-                outCollidersPtr.ptr,
-                outCount.ptr
-            )
-
-            if (result != 0) {
-                handleResult(result, "getChildColliders")
-                return emptyList()
-            }
-
-            val count = outCount.value.toInt()
-            val cArray = outCollidersPtr.value
-
-            if (cArray == null || count == 0) return emptyList()
-
-            val list = ArrayList<Collider>(count)
-            for (i in 0 until count) {
-                list.add(cArray[i].toKotlin(this@NativeEngine))
-            }
-
-            dropbear_free_colliders(cArray, outCount.value)
-            return list
-        }
-    }
-
-    actual fun setCollider(collider: Collider) {
-        val world = worldHandle ?: if (exceptionOnError) throw DropbearNativeException("world handle null") else return
-        val pe = physicsEngineHandle ?: if (exceptionOnError) throw DropbearNativeException("Handle null") else return
-
-        memScoped {
-            val cCollider = alloc<com.dropbear.ffi.generated.Collider>()
-            collider.populateCStruct(cCollider)
-
-            val result = dropbear_set_collider(
-                world.reinterpret(),
-                pe.reinterpret(),
-                cCollider.readValue()
-            )
-            handleResult(result, "setCollider")
-        }
-    }
-}
-
-private fun handleResult(result: Int, funcName: String) {
-    if (result != 0) {
-        val msg = "$funcName failed with code: $result"
-        if (exceptionOnError) {
-            throw DropbearNativeException(msg)
-        } else {
-            Logger.error(msg)
-        }
-    }
 }
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/input/Gamepad.native.kt b/src/nativeMain/kotlin/com/dropbear/input/Gamepad.native.kt
new file mode 100644
index 0000000..262b6ac
--- /dev/null
+++ b/src/nativeMain/kotlin/com/dropbear/input/Gamepad.native.kt
@@ -0,0 +1,19 @@
+package com.dropbear.input
+
+import com.dropbear.math.Vector2d
+
+actual fun Gamepad.isGamepadButtonPressed(
+    id: Long,
+    button: GamepadButton
+): Boolean {
+    TODO("Not yet implemented")
+}
+
+actual fun Gamepad.getLeftStickPosition(id: Long): Vector2d {
+    TODO("Not yet implemented")
+}
+
+actual fun Gamepad.getRightStickPosition(id: Long): Vector2d {
+    TODO("Not yet implemented")
+}
+
diff --git a/src/nativeMain/kotlin/com/dropbear/input/InputState.native.kt b/src/nativeMain/kotlin/com/dropbear/input/InputState.native.kt
new file mode 100644
index 0000000..2844b4d
--- /dev/null
+++ b/src/nativeMain/kotlin/com/dropbear/input/InputState.native.kt
@@ -0,0 +1,46 @@
+package com.dropbear.input
+
+import com.dropbear.math.Vector2d
+
+actual class InputState actual constructor() {
+    actual fun printInputState() {
+    }
+
+    actual fun isKeyPressed(key: KeyCode): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    actual fun getMousePosition(): Vector2d {
+        TODO("Not yet implemented")
+    }
+
+    actual fun isMouseButtonPressed(button: MouseButton): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    actual fun getMouseDelta(): Vector2d {
+        TODO("Not yet implemented")
+    }
+
+    actual fun isCursorLocked(): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    actual fun setCursorLocked(locked: Boolean) {
+    }
+
+    actual fun getLastMousePos(): Vector2d {
+        TODO("Not yet implemented")
+    }
+
+    actual fun isCursorHidden(): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    actual fun setCursorHidden(hidden: Boolean) {
+    }
+
+    actual fun getConnectedGamepads(): List<Gamepad> {
+        TODO("Not yet implemented")
+    }
+}
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/physics/Collider.native.kt b/src/nativeMain/kotlin/com/dropbear/physics/Collider.native.kt
new file mode 100644
index 0000000..0e82eef
--- /dev/null
+++ b/src/nativeMain/kotlin/com/dropbear/physics/Collider.native.kt
@@ -0,0 +1,81 @@
+package com.dropbear.physics
+
+import com.dropbear.math.Vector3d
+
+actual fun Collider.getColliderShape(collider: Collider): ColliderShape {
+    TODO("Not yet implemented")
+}
+
+actual fun Collider.setColliderShape(
+    collider: Collider,
+    shape: ColliderShape
+) {
+}
+
+actual fun Collider.setColliderDensity(
+    collider: Collider,
+    density: Double
+) {
+}
+
+actual fun Collider.getColliderFriction(collider: Collider): Double {
+    TODO("Not yet implemented")
+}
+
+actual fun Collider.setColliderFriction(
+    collider: Collider,
+    friction: Double
+) {
+}
+
+actual fun Collider.getColliderRestitution(collider: Collider): Double {
+    TODO("Not yet implemented")
+}
+
+actual fun Collider.setColliderRestitution(
+    collider: Collider,
+    restitution: Double
+) {
+}
+
+actual fun Collider.getColliderIsSensor(collider: Collider): Boolean {
+    TODO("Not yet implemented")
+}
+
+actual fun Collider.setColliderIsSensor(
+    collider: Collider,
+    isSensor: Boolean
+) {
+}
+
+actual fun Collider.getColliderTranslation(collider: Collider): Vector3d {
+    TODO("Not yet implemented")
+}
+
+actual fun Collider.setColliderTranslation(
+    collider: Collider,
+    translation: Vector3d
+) {
+}
+
+actual fun Collider.getColliderRotation(collider: Collider): Vector3d {
+    TODO("Not yet implemented")
+}
+
+actual fun Collider.setColliderRotation(
+    collider: Collider,
+    rotation: Vector3d
+) {
+}
+
+actual fun Collider.getColliderMass(collider: Collider): Double {
+    TODO("Not yet implemented")
+}
+
+actual fun Collider.setColliderMass(collider: Collider, mass: Double) {
+}
+
+actual fun Collider.getColliderDensity(collider: Collider): Double {
+    TODO("Not yet implemented")
+}
+
diff --git a/src/nativeMain/kotlin/com/dropbear/physics/ColliderGroup.native.kt b/src/nativeMain/kotlin/com/dropbear/physics/ColliderGroup.native.kt
new file mode 100644
index 0000000..24537dc
--- /dev/null
+++ b/src/nativeMain/kotlin/com/dropbear/physics/ColliderGroup.native.kt
@@ -0,0 +1,11 @@
+package com.dropbear.physics
+
+import com.dropbear.EntityId
+
+actual fun ColliderGroup.getColliderGroupColliders(colliderGroup: ColliderGroup): List<Collider> {
+    TODO("Not yet implemented")
+}
+
+actual fun colliderGroupExistsForEntity(entityId: EntityId): Boolean {
+    TODO("Not yet implemented")
+}
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/physics/Physics.native.kt b/src/nativeMain/kotlin/com/dropbear/physics/Physics.native.kt
new file mode 100644
index 0000000..e8e232f
--- /dev/null
+++ b/src/nativeMain/kotlin/com/dropbear/physics/Physics.native.kt
@@ -0,0 +1,10 @@
+package com.dropbear.physics
+
+import com.dropbear.math.Vector3d
+
+internal actual fun getGravity(): Vector3d {
+    TODO("Not yet implemented")
+}
+
+internal actual fun setGravity(gravity: Vector3d) {
+}
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/physics/RigidBody.native.kt b/src/nativeMain/kotlin/com/dropbear/physics/RigidBody.native.kt
new file mode 100644
index 0000000..d45559f
--- /dev/null
+++ b/src/nativeMain/kotlin/com/dropbear/physics/RigidBody.native.kt
@@ -0,0 +1,128 @@
+package com.dropbear.physics
+
+import com.dropbear.EntityId
+import com.dropbear.math.Vector3d
+
+actual fun RigidBody.setRigidbodyMode(
+    rigidBody: RigidBody,
+    mode: RigidBodyMode
+) {
+}
+
+actual fun RigidBody.getRigidbodyMode(rigidBody: RigidBody): RigidBodyMode {
+    TODO("Not yet implemented")
+}
+
+actual fun RigidBody.getRigidbodyGravityScale(rigidBody: RigidBody): Double {
+    TODO("Not yet implemented")
+}
+
+actual fun RigidBody.setRigidbodyGravityScale(
+    rigidBody: RigidBody,
+    gravityScale: Double
+) {
+}
+
+actual fun RigidBody.getRigidBodySleep(rigidBody: RigidBody): Boolean {
+    TODO("Not yet implemented")
+}
+
+actual fun RigidBody.setRigidBodySleep(
+    rigidBody: RigidBody,
+    canSleep: Boolean
+) {
+}
+
+actual fun RigidBody.getRigidbodyCcdEnabled(rigidBody: RigidBody): Boolean {
+    TODO("Not yet implemented")
+}
+
+actual fun RigidBody.setRigidbodyCcdEnabled(
+    rigidBody: RigidBody,
+    ccdEnabled: Boolean
+) {
+}
+
+actual fun RigidBody.getRigidbodyLinearVelocity(rigidBody: RigidBody): Vector3d {
+    TODO("Not yet implemented")
+}
+
+actual fun RigidBody.setRigidbodyLinearDamping(
+    rigidBody: RigidBody,
+    linearDamping: Double
+) {
+}
+
+actual fun RigidBody.setRigidbodyLinearVelocity(
+    rigidBody: RigidBody,
+    linearVelocity: Vector3d
+) {
+}
+
+actual fun RigidBody.getRigidbodyAngularVelocity(rigidBody: RigidBody): Vector3d {
+    TODO("Not yet implemented")
+}
+
+actual fun RigidBody.setRigidbodyAngularVelocity(
+    rigidBody: RigidBody,
+    angularVelocity: Vector3d
+) {
+}
+
+actual fun RigidBody.getRigidbodyLinearDamping(rigidBody: RigidBody): Double {
+    TODO("Not yet implemented")
+}
+
+actual fun RigidBody.getRigidbodyAngularDamping(rigidBody: RigidBody): Double {
+    TODO("Not yet implemented")
+}
+
+actual fun RigidBody.setRigidbodyAngularDamping(
+    rigidBody: RigidBody,
+    angularDamping: Double
+) {
+}
+
+actual fun RigidBody.getRigidbodyLockTranslation(rigidBody: RigidBody): AxisLock {
+    TODO("Not yet implemented")
+}
+
+actual fun RigidBody.setRigidbodyLockTranslation(
+    rigidBody: RigidBody,
+    lockTranslation: AxisLock
+) {
+}
+
+actual fun RigidBody.getRigidbodyLockRotation(rigidBody: RigidBody): AxisLock {
+    TODO("Not yet implemented")
+}
+
+actual fun RigidBody.setRigidbodyLockRotation(
+    rigidBody: RigidBody,
+    lockRotation: AxisLock
+) {
+}
+
+actual fun RigidBody.getRigidbodyChildren(rigidBody: RigidBody): List<Collider> {
+    TODO("Not yet implemented")
+}
+
+actual fun RigidBody.applyImpulse(
+    index: Index,
+    x: Double,
+    y: Double,
+    z: Double
+) {
+}
+
+actual fun RigidBody.applyTorqueImpulse(
+    index: Index,
+    x: Double,
+    y: Double,
+    z: Double
+) {
+}
+
+actual fun rigidBodyExistsForEntity(entityId: EntityId): Index? {
+    TODO("Not yet implemented")
+}
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/scene/SceneLoadHandle.native.kt b/src/nativeMain/kotlin/com/dropbear/scene/SceneLoadHandle.native.kt
new file mode 100644
index 0000000..6ca7428
--- /dev/null
+++ b/src/nativeMain/kotlin/com/dropbear/scene/SceneLoadHandle.native.kt
@@ -0,0 +1,19 @@
+package com.dropbear.scene
+
+import com.dropbear.utils.Progress
+
+actual fun SceneLoadHandle.switchToSceneAsync() {
+    TODO("Not yet implemented")
+}
+
+actual fun SceneLoadHandle.getSceneLoadProgress(): Progress {
+    TODO("Not yet implemented")
+}
+
+actual fun SceneLoadHandle.getSceneLoadStatus(): SceneLoadStatus {
+    TODO("Not yet implemented")
+}
+
+actual fun SceneLoadHandle.getSceneLoadHandleSceneName(id: Long): String {
+    TODO("Not yet implemented")
+}
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/scene/SceneManager.native.kt b/src/nativeMain/kotlin/com/dropbear/scene/SceneManager.native.kt
new file mode 100644
index 0000000..8217884
--- /dev/null
+++ b/src/nativeMain/kotlin/com/dropbear/scene/SceneManager.native.kt
@@ -0,0 +1,15 @@
+package com.dropbear.scene
+
+actual fun SceneManager.loadSceneAsyncNative(sceneName: String): SceneLoadHandle? {
+    TODO("Not yet implemented")
+}
+
+actual fun SceneManager.loadSceneAsyncNative(
+    sceneName: String,
+    loadingScene: String
+): SceneLoadHandle? {
+    TODO("Not yet implemented")
+}
+
+actual fun SceneManager.switchToSceneImmediateNative(sceneName: String) {
+}
\ No newline at end of file