kitgit

tirbofish/dropbear · commit

ebb639445af691a841f8704f8d62a4f4f2699b5d

what a fucking mess. i dont even think this works, but it can build a nice bridge (hopefully)...

Unverified

tk <4tkbytes@pm.me> · 2025-10-03 06:29

view full diff

diff --git a/.github/workflows/build_fatty.yaml b/.github/workflows/build_fatty.yaml
new file mode 100644
index 0000000..c6f126d
--- /dev/null
+++ b/.github/workflows/build_fatty.yaml
@@ -0,0 +1,35 @@
+name: Build and Upload JAR
+
+on:
+  push:
+    branches:
+      - '**'
+  workflow_dispatch:
+
+jobs:
+  build:
+    if: contains(github.event.head_commit.message, 'jarvis, run github actions')
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Set up JDK 21
+        uses: actions/setup-java@v4
+        with:
+          java-version: '21'
+          distribution: 'temurin'
+
+      - name: Grant execute permission for gradlew
+        run: chmod +x gradlew
+
+      - name: Build with Gradle
+        run: ./gradlew build
+
+      - name: Upload JAR artifact
+        uses: actions/upload-artifact@v4
+        with:
+          name: application-jar
+          path: build/libs/*.jar
+          retention-days: 30
\ No newline at end of file
diff --git a/README.md b/README.md
index 90af976..b5d3dd4 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ If you might have not realised, all the crates/projects names are after Australi
 
 To build, ensure build requirements, clone the repository, then build it. It will build in debug mode, and use a lot of packages, so if your CPU is not fast enough for building you should brew a cup of coffee during the build time.
 
-With Unix systems (macOS not tested), you will have to download a couple dependencies if building locally:
+With Unix systems (macOS not tested), you will have to download a couple of dependencies if building locally:
 
 <!-- If you have a macOS system, please create a PR and add your own implementation. I know you need to use brew, but I don't know what dependencies to install.  -->
 
@@ -30,10 +30,10 @@ With Unix systems (macOS not tested), you will have to download a couple depende
 
 ```bash
 # ubuntu
-sudo apt install libudev-dev pkg-config libssl-dev clang cmake meson assimp-utils
+sudo apt install libudev-dev pkg-config libssl-dev clang cmake meson assimp-utils openjdk-21-jdk
 
 # i use arch btw
-sudo pacman -Syu base-devel systemd pkgconf openssl clang cmake meson assimp
+sudo pacman -Syu base-devel systemd pkgconf openssl clang cmake meson assimp jdk21-openjdk
 
 ```
 
@@ -53,6 +53,12 @@ cargo build
 
 [//]: # (git submodule update)
 
+> [!TIP]
+> During the development of your game, you should download JetBrains IntelliJ (for kotlin) for the best support. 
+> 
+> For a development build of the eucalyptus editor, it is recommended that you use JetBrains IntelliJ (for jvm) and 
+> JetBrains RustRover (for rust)
+
 ### Prebuilt
 
 If you do not want to build it locally, you are able to download the latest action build (if no releases have been made).
diff --git a/build.gradle.kts b/build.gradle.kts
index cbfc7df..42b7b28 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,5 +1,6 @@
 plugins {
     kotlin("jvm") version "2.1.21"
+//    kotlin("multiplatform") version "2.1.21"
 }
 
 group = "com.dropbear"
diff --git a/docs/notes.md b/docs/notes.md
new file mode 100644
index 0000000..7ebf38e
--- /dev/null
+++ b/docs/notes.md
@@ -0,0 +1,17 @@
+# notes to self
+
+## hybrid approach:
+
+- during dev builds, interpret using the JVM
+- on production build, build to native.
+
+to check if its prod or dev, we use a config to build.
+
+workflow:
+- user creates a new project, which loads the project
+- use jvm to watch (because editor is always on desktop)
+- on production build, create native build for specific OS
+
+## required dependencies
+
+- jdk 21
diff --git a/eucalyptus-core/Cargo.toml b/eucalyptus-core/Cargo.toml
index 202141c..70c6ad2 100644
--- a/eucalyptus-core/Cargo.toml
+++ b/eucalyptus-core/Cargo.toml
@@ -6,6 +6,9 @@ license-file.workspace = true
 repository.workspace = true
 readme = "README.md"
 
+[lib]
+crate-type = ["cdylib", "rlib"]
+
 [dependencies]
 anyhow.workspace = true
 bincode.workspace = true
diff --git a/eucalyptus-core/build.rs b/eucalyptus-core/build.rs
index 8966f89..714eb7a 100644
--- a/eucalyptus-core/build.rs
+++ b/eucalyptus-core/build.rs
@@ -1,59 +1,60 @@
 use std::fs::{self, File};
 use std::io::Cursor;
+use std::process::Command;
 
 fn main() -> anyhow::Result<()> {
     // todo: move this into the "setup" process
-    let repo_zip_url = "https://github.com/4tkbytes/dropbear/archive/refs/heads/main.zip";
-    let response = reqwest::blocking::get(repo_zip_url)
-        .map_err(|e| anyhow::anyhow!("Failed to download repo zip: {}", e))?
-        .bytes()
-        .map_err(|e| anyhow::anyhow!("Failed to read zip bytes: {}", e))?;
-
-    let reader = Cursor::new(response);
-    let mut zip = zip::ZipArchive::new(reader)
-        .map_err(|e| anyhow::anyhow!("Failed to read zip archive: {}", e))?;
-
-    let app_info = app_dirs2::AppInfo {
-        name: "Eucalyptus",
-        author: "4tkbytes",
-    };
-    let app_data_dir = app_dirs2::app_root(app_dirs2::AppDataType::UserData, &app_info)
-        .map_err(|e| anyhow::anyhow!("Could not determine app data directory: {}", e))?;
-
-    fs::create_dir_all(&app_data_dir)
-        .map_err(|e| anyhow::anyhow!("Failed to create app data directory: {}", e))?;
-
-    let resource_prefix = "dropbear-main/resources/";
-    let mut found_resource = false;
-    for i in 0..zip.len() {
-        let mut file = zip.by_index(i).unwrap();
-        let name = file.name();
-
-        if name.starts_with(resource_prefix) && !name.ends_with('/') {
-            found_resource = true;
-            let rel_path = &name[resource_prefix.len()..];
-            let rel_path = rel_path.strip_prefix('/').unwrap_or(rel_path);
-            let dest_path = app_data_dir.join(rel_path);
-
-            if let Some(parent) = dest_path.parent() {
-                fs::create_dir_all(parent)
-                    .map_err(|e| anyhow::anyhow!("Failed to create parent directory: {}", e))?;
-            }
-
-            println!("Copying {} to {:?}", name, dest_path);
-
-            let mut outfile = File::create(&dest_path)
-                .map_err(|e| anyhow::anyhow!("Failed to create file: {}", e))?;
-            std::io::copy(&mut file, &mut outfile)
-                .map_err(|e| anyhow::anyhow!("Failed to copy file: {}", e))?;
-        }
-    }
-
-    if !found_resource {
-        return Err(anyhow::anyhow!(
-            "No resources folder found in the github repository [4tkbytes/dropbear] :("
-        ));
-    }
+    // let repo_zip_url = "https://github.com/4tkbytes/dropbear/archive/refs/heads/main.zip";
+    // let response = reqwest::blocking::get(repo_zip_url)
+    //     .map_err(|e| anyhow::anyhow!("Failed to download repo zip: {}", e))?
+    //     .bytes()
+    //     .map_err(|e| anyhow::anyhow!("Failed to read zip bytes: {}", e))?;
+    // 
+    // let reader = Cursor::new(response);
+    // let mut zip = zip::ZipArchive::new(reader)
+    //     .map_err(|e| anyhow::anyhow!("Failed to read zip archive: {}", e))?;
+    // 
+    // let app_info = app_dirs2::AppInfo {
+    //     name: "Eucalyptus",
+    //     author: "4tkbytes",
+    // };
+    // let app_data_dir = app_dirs2::app_root(app_dirs2::AppDataType::UserData, &app_info)
+    //     .map_err(|e| anyhow::anyhow!("Could not determine app data directory: {}", e))?;
+    // 
+    // fs::create_dir_all(&app_data_dir)
+    //     .map_err(|e| anyhow::anyhow!("Failed to create app data directory: {}", e))?;
+    // 
+    // let resource_prefix = "dropbear-main/resources/";
+    // let mut found_resource = false;
+    // for i in 0..zip.len() {
+    //     let mut file = zip.by_index(i).unwrap();
+    //     let name = file.name();
+    // 
+    //     if name.starts_with(resource_prefix) && !name.ends_with('/') {
+    //         found_resource = true;
+    //         let rel_path = &name[resource_prefix.len()..];
+    //         let rel_path = rel_path.strip_prefix('/').unwrap_or(rel_path);
+    //         let dest_path = app_data_dir.join(rel_path);
+    // 
+    //         if let Some(parent) = dest_path.parent() {
+    //             fs::create_dir_all(parent)
+    //                 .map_err(|e| anyhow::anyhow!("Failed to create parent directory: {}", e))?;
+    //         }
+    // 
+    //         println!("Copying {} to {:?}", name, dest_path);
+    // 
+    //         let mut outfile = File::create(&dest_path)
+    //             .map_err(|e| anyhow::anyhow!("Failed to create file: {}", e))?;
+    //         std::io::copy(&mut file, &mut outfile)
+    //             .map_err(|e| anyhow::anyhow!("Failed to copy file: {}", e))?;
+    //     }
+    // }
+    // 
+    // if !found_resource {
+    //     return Err(anyhow::anyhow!(
+    //         "No resources folder found in the github repository [4tkbytes/dropbear] :("
+    //     ));
+    // }    
 
     // fuck you windows :(
     #[cfg(target_os = "windows")]
diff --git a/eucalyptus-core/src/scripting.rs b/eucalyptus-core/src/scripting.rs
index 0475600..532f7f4 100644
--- a/eucalyptus-core/src/scripting.rs
+++ b/eucalyptus-core/src/scripting.rs
@@ -83,19 +83,18 @@ impl DropbearScriptingAPIContext {
 
 /// A message from Kotlin that gets sent to Rust
 pub enum KotlinMessage {
-
+    
 }
 
 /// A message from Rust that gets sent to Kotlin
 pub enum RustMessage {
-
+    Ping
 }
 
 pub struct ScriptManager {
+    #[allow(dead_code)]
     script_context: DropbearScriptingAPIContext,
     java: JavaContext,
-    from_kotlin: crossbeam_channel::Receiver<KotlinMessage>,
-    to_kotlin: crossbeam_channel::Sender<RustMessage>,
 }
 
 impl ScriptManager {
@@ -115,7 +114,7 @@ impl ScriptManager {
     pub fn load_script(
         &mut self,
         script_name: &String,
-        script_content: String,
+        _script_content: String,
     ) -> anyhow::Result<String> {
         
         log::debug!("Loaded library [{}]", script_name);
@@ -131,6 +130,20 @@ impl ScriptManager {
     ) -> anyhow::Result<()> {
         log_once::debug_once!("init_entity_script: {} for {:?}", script_name, entity_id);
 
+        // Update the script context
+        java::update_script_context(
+            entity_id,
+            world,
+            input_state.mouse_pos,
+            &input_state.pressed_keys.iter().copied().collect::<Vec<_>>()
+        );
+
+        // Try to load and initialize the script
+        if let Ok(script_instance) = self.java.load_script_class(script_name) {
+            self.java.call_script_load(script_instance)?;
+            log::info!("Initialized script {} for entity {:?}", script_name, entity_id);
+        }
+
         Ok(())
     }
 
@@ -140,10 +153,26 @@ impl ScriptManager {
         script_name: &str,
         world: &mut World,
         input_state: &InputState,
-        dt: f32,
+        _dt: f32,
     ) -> anyhow::Result<()> {
         log_once::debug_once!("Update entity script name: {}", script_name);
 
+        // Update the script context before calling script
+        java::update_script_context(
+            entity_id,
+            world,
+            input_state.mouse_pos,
+            &input_state.pressed_keys.iter().copied().collect::<Vec<_>>()
+        );
+
+        // Call the update method on the script
+        if let Ok(script_instance) = self.java.load_script_class(script_name) {
+            self.java.call_script_update(script_instance)?;
+        }
+
+        // Sync changes back to world
+        java::sync_transform_to_world(world);
+
         Ok(())
     }
 }
diff --git a/eucalyptus-core/src/scripting/java.rs b/eucalyptus-core/src/scripting/java.rs
index 62b3c3c..38a8a7f 100644
--- a/eucalyptus-core/src/scripting/java.rs
+++ b/eucalyptus-core/src/scripting/java.rs
@@ -1,5 +1,22 @@
+#![allow(non_snake_case)]
 use jni::{InitArgsBuilder, JNIEnv, JNIVersion, JavaVM};
-use jni::objects::{JObject, JString};
+use jni::objects::{JClass, JObject};
+use jni::sys::{jdouble, jlong, jboolean};
+use parking_lot::RwLock;
+use once_cell::sync::Lazy;
+use dropbear_engine::entity::Transform;
+use glam::{DVec3, DQuat};
+use hecs::{Entity, World};
+use winit::keyboard::KeyCode;
+
+// Thread-safe global context using RwLock instead of Mutex for better performance
+static CURRENT_ENTITY: Lazy<RwLock<Option<Entity>>> = Lazy::new(|| RwLock::new(None));
+static CURRENT_WORLD: Lazy<RwLock<Option<usize>>> = Lazy::new(|| RwLock::new(None)); // Store as usize
+static CURRENT_POSITION: Lazy<RwLock<DVec3>> = Lazy::new(|| RwLock::new(DVec3::ZERO));
+static CURRENT_ROTATION: Lazy<RwLock<DQuat>> = Lazy::new(|| RwLock::new(DQuat::IDENTITY));
+static CURRENT_SCALE: Lazy<RwLock<DVec3>> = Lazy::new(|| RwLock::new(DVec3::ONE));
+static CURRENT_MOUSE: Lazy<RwLock<(f64, f64)>> = Lazy::new(|| RwLock::new((0.0, 0.0)));
+static CURRENT_KEYS: Lazy<RwLock<Vec<i64>>> = Lazy::new(|| RwLock::new(Vec::new()));
 
 /// A dropbear wrapper for Java Virtual Machine (JVM) based functions
 pub(crate) struct JavaContext {
@@ -11,6 +28,7 @@ impl JavaContext {
     pub fn new() -> anyhow::Result<Self> {
         let jvm_args = InitArgsBuilder::new()
             .version(JNIVersion::V8)
+            .option("-Djava.class.path=./build/libs/dropbear-1.0-SNAPSHOT.jar")
             .build()?;
 
         let jvm = JavaVM::new(jvm_args)?;
@@ -19,25 +37,266 @@ impl JavaContext {
             jvm
         })
     }
+
+    /// Get a JNI environment for the current thread
+    pub fn get_env(&self) -> anyhow::Result<jni::AttachGuard<'_>> {
+        Ok(self.jvm.attach_current_thread()?)
+    }
+
+    /// Call a script's load method
+    pub fn call_script_load(&self, script_instance: JObject) -> anyhow::Result<()> {
+        let mut env = self.get_env()?;
+        env.call_method(script_instance, "load", "()V", &[])?;
+        Ok(())
+    }
+
+    /// Call a script's update method
+    pub fn call_script_update(&self, script_instance: JObject) -> anyhow::Result<()> {
+        let mut env = self.get_env()?;
+        env.call_method(script_instance, "update", "()V", &[])?;
+        Ok(())
+    }
+
+    /// Load a script class and create an instance
+    pub fn load_script_class(&self, class_name: &str) -> anyhow::Result<JObject<'_>> {
+        let mut env = self.get_env()?;
+        
+        // Find the class
+        let class = env.find_class(class_name)?;
+        
+        // Create an instance
+        let instance = env.new_object(class, "()V", &[])?;
+        
+        Ok(instance)
+    }
+}
+
+/// Update context from Rust side before calling scripts
+pub fn update_script_context(entity: Entity, world: &World, mouse_pos: (f64, f64), pressed_keys: &[KeyCode]) {
+    // Store entity
+    *CURRENT_ENTITY.write() = Some(entity);
+    
+    // Store world pointer as usize
+    *CURRENT_WORLD.write() = Some(world as *const World as usize);
+    
+    // Read transform from world
+    if let Ok(transform) = world.get::<&Transform>(entity) {
+        *CURRENT_POSITION.write() = transform.position;
+        *CURRENT_ROTATION.write() = transform.rotation;
+        *CURRENT_SCALE.write() = transform.scale;
+    }
+    
+    // Store input state
+    *CURRENT_MOUSE.write() = mouse_pos;
+    *CURRENT_KEYS.write() = pressed_keys.iter().map(|k| keycode_to_i64(k)).collect();
+}
+
+/// Sync changes back to world after script execution
+pub fn sync_transform_to_world(world: &mut World) {
+    if let Some(entity) = *CURRENT_ENTITY.read() {
+        if let Ok(mut transform) = world.get::<&mut Transform>(entity) {
+            transform.position = *CURRENT_POSITION.read();
+            transform.rotation = *CURRENT_ROTATION.read();
+            transform.scale = *CURRENT_SCALE.read();
+        }
+    }
+}
+
+// =============================================================================
+// JNI Native Functions - Called from Kotlin
+// =============================================================================
+
+/// Get the transform position of the current entity
+#[unsafe(no_mangle)]
+pub extern "system" fn Java_com_dropbear_ffi_NativeEngine_getPositionX(
+    _env: JNIEnv,
+    _class: JClass,
+) -> jdouble {
+    CURRENT_POSITION.read().x
+}
+
+#[unsafe(no_mangle)]
+pub extern "system" fn Java_com_dropbear_ffi_NativeEngine_getPositionY(
+    _env: JNIEnv,
+    _class: JClass,
+) -> jdouble {
+    CURRENT_POSITION.read().y
+}
+
+#[unsafe(no_mangle)]
+pub extern "system" fn Java_com_dropbear_ffi_NativeEngine_getPositionZ(
+    _env: JNIEnv,
+    _class: JClass,
+) -> jdouble {
+    CURRENT_POSITION.read().z
+}
+
+/// Set the transform position of the current entity
+#[unsafe(no_mangle)]
+pub extern "system" fn Java_com_dropbear_ffi_NativeEngine_setPosition(
+    _env: JNIEnv,
+    _class: JClass,
+    x: jdouble,
+    y: jdouble,
+    z: jdouble,
+) {
+    *CURRENT_POSITION.write() = DVec3::new(x, y, z);
+}
+
+/// Get rotation quaternion components
+#[unsafe(no_mangle)]
+pub extern "system" fn Java_com_dropbear_ffi_NativeEngine_getRotationX(
+    _env: JNIEnv,
+    _class: JClass,
+) -> jdouble {
+    CURRENT_ROTATION.read().x
+}
+
+#[unsafe(no_mangle)]
+pub extern "system" fn Java_com_dropbear_ffi_NativeEngine_getRotationY(
+    _env: JNIEnv,
+    _class: JClass,
+) -> jdouble {
+    CURRENT_ROTATION.read().y
+}
+
+#[unsafe(no_mangle)]
+pub extern "system" fn Java_com_dropbear_ffi_NativeEngine_getRotationZ(
+    _env: JNIEnv,
+    _class: JClass,
+) -> jdouble {
+    CURRENT_ROTATION.read().z
+}
+
+#[unsafe(no_mangle)]
+pub extern "system" fn Java_com_dropbear_ffi_NativeEngine_getRotationW(
+    _env: JNIEnv,
+    _class: JClass,
+) -> jdouble {
+    CURRENT_ROTATION.read().w
+}
+
+/// Set rotation quaternion
+#[unsafe(no_mangle)]
+pub extern "system" fn Java_com_dropbear_ffi_NativeEngine_setRotation(
+    _env: JNIEnv,
+    _class: JClass,
+    x: jdouble,
+    y: jdouble,
+    z: jdouble,
+    w: jdouble,
+) {
+    *CURRENT_ROTATION.write() = DQuat::from_xyzw(x, y, z, w);
+}
+
+/// Get scale components
+#[unsafe(no_mangle)]
+pub extern "system" fn Java_com_dropbear_ffi_NativeEngine_getScaleX(
+    _env: JNIEnv,
+    _class: JClass,
+) -> jdouble {
+    CURRENT_SCALE.read().x
+}
+
+#[unsafe(no_mangle)]
+pub extern "system" fn Java_com_dropbear_ffi_NativeEngine_getScaleY(
+    _env: JNIEnv,
+    _class: JClass,
+) -> jdouble {
+    CURRENT_SCALE.read().y
+}
+
+#[unsafe(no_mangle)]
+pub extern "system" fn Java_com_dropbear_ffi_NativeEngine_getScaleZ(
+    _env: JNIEnv,
+    _class: JClass,
+) -> jdouble {
+    CURRENT_SCALE.read().z
+}
+
+/// Set scale
+#[unsafe(no_mangle)]
+pub extern "system" fn Java_com_dropbear_ffi_NativeEngine_setScale(
+    _env: JNIEnv,
+    _class: JClass,
+    x: jdouble,
+    y: jdouble,
+    z: jdouble,
+) {
+    *CURRENT_SCALE.write() = DVec3::new(x, y, z);
+}
+
+// =============================================================================
+// Input System JNI Functions
+// =============================================================================
+
+/// Check if a key is pressed
+#[unsafe(no_mangle)]
+pub extern "system" fn Java_com_dropbear_ffi_NativeEngine_isKeyPressed(
+    _env: JNIEnv,
+    _class: JClass,
+    keycode: jlong,
+) -> jboolean {
+    let keys = CURRENT_KEYS.read();
+    keys.contains(&keycode) as jboolean
 }
 
-// JNIEXPORT jstring JNICALL Java_com_dropbear_ffi_NativeEngine_Ping
-//    (JNIEnv *, jobject, jstring);
+/// Get mouse X position
 #[unsafe(no_mangle)]
-#[allow(non_snake_case)]
-pub extern "system" fn Java_com_dropbear_ffi_NativeEngine_Ping<'local>(
-    mut env: JNIEnv<'local>,
-    _this: JObject<'local>,
-    input: JString<'local>,
-) -> JString<'local> {
-    let message: String = env.get_string(&input)
-        .expect("Failed to get input string")
-        .into();
+pub extern "system" fn Java_com_dropbear_ffi_NativeEngine_getMouseX(
+    _env: JNIEnv,
+    _class: JClass,
+) -> jdouble {
+    CURRENT_MOUSE.read().0
+}
 
-    let response = format!("Pong! You sent: {}", message);
+/// Get mouse Y position
+#[unsafe(no_mangle)]
+pub extern "system" fn Java_com_dropbear_ffi_NativeEngine_getMouseY(
+    _env: JNIEnv,
+    _class: JClass,
+) -> jdouble {
+    CURRENT_MOUSE.read().1
+}
 
-    let output = env.new_string(response)
-        .expect("Failed to create output string");
+/// Helper function to convert KeyCode to i64
+fn keycode_to_i64(key: &KeyCode) -> i64 {
+    match key {
+        KeyCode::KeyA => 65,
+        KeyCode::KeyB => 66,
+        KeyCode::KeyC => 67,
+        KeyCode::KeyD => 68,
+        KeyCode::KeyE => 69,
+        KeyCode::KeyF => 70,
+        KeyCode::KeyG => 71,
+        KeyCode::KeyH => 72,
+        KeyCode::KeyI => 73,
+        KeyCode::KeyJ => 74,
+        KeyCode::KeyK => 75,
+        KeyCode::KeyL => 76,
+        KeyCode::KeyM => 77,
+        KeyCode::KeyN => 78,
+        KeyCode::KeyO => 79,
+        KeyCode::KeyP => 80,
+        KeyCode::KeyQ => 81,
+        KeyCode::KeyR => 82,
+        KeyCode::KeyS => 83,
+        KeyCode::KeyT => 84,
+        KeyCode::KeyU => 85,
+        KeyCode::KeyV => 86,
+        KeyCode::KeyW => 87,
+        KeyCode::KeyX => 88,
+        KeyCode::KeyY => 89,
+        KeyCode::KeyZ => 90,
+        KeyCode::ArrowLeft => 37,
+        KeyCode::ArrowUp => 38,
+        KeyCode::ArrowRight => 39,
+        KeyCode::ArrowDown => 40,
+        KeyCode::Space => 32,
+        KeyCode::ShiftLeft | KeyCode::ShiftRight => 16,
+        KeyCode::ControlLeft | KeyCode::ControlRight => 17,
+        KeyCode::Escape => 27,
+        _ => 0,
+    }
+}
 
-    output.into()
-}
\ No newline at end of file
diff --git a/eucalyptus-core/src/states.rs b/eucalyptus-core/src/states.rs
index b2231cb..7962834 100644
--- a/eucalyptus-core/src/states.rs
+++ b/eucalyptus-core/src/states.rs
@@ -1353,6 +1353,7 @@ pub enum EditorTab {
     ResourceInspector, // left side,
     ModelEntityList,   // right side,
     Viewport,          // middle,
+    KotlinREPL,        // bottom side - new REPL tab
 }
 
 /// An enum that describes the status of loading the world.
diff --git a/eucalyptus-editor/build.rs b/eucalyptus-editor/build.rs
index 6f5a5fc..5a899d4 100644
--- a/eucalyptus-editor/build.rs
+++ b/eucalyptus-editor/build.rs
@@ -17,57 +17,57 @@ fn main() -> anyhow::Result<()> {
     println!("cargo:rerun-if-changed=.git/refs/heads");
 
     // todo: move this into the "setup" process
-    let repo_zip_url = "https://github.com/4tkbytes/dropbear/archive/refs/heads/main.zip";
-    let response = reqwest::blocking::get(repo_zip_url)
-        .map_err(|e| anyhow::anyhow!("Failed to download repo zip: {}", e))?
-        .bytes()
-        .map_err(|e| anyhow::anyhow!("Failed to read zip bytes: {}", e))?;
-
-    let reader = Cursor::new(response);
-    let mut zip = zip::ZipArchive::new(reader)
-        .map_err(|e| anyhow::anyhow!("Failed to read zip archive: {}", e))?;
-
-    let app_info = app_dirs2::AppInfo {
-        name: "Eucalyptus",
-        author: "4tkbytes",
-    };
-    let app_data_dir = app_dirs2::app_root(app_dirs2::AppDataType::UserData, &app_info)
-        .map_err(|e| anyhow::anyhow!("Could not determine app data directory: {}", e))?;
-
-    fs::create_dir_all(&app_data_dir)
-        .map_err(|e| anyhow::anyhow!("Failed to create app data directory: {}", e))?;
-
-    let resource_prefix = "dropbear-main/resources/";
-    let mut found_resource = false;
-    for i in 0..zip.len() {
-        let mut file = zip.by_index(i).unwrap();
-        let name = file.name();
-
-        if name.starts_with(resource_prefix) && !name.ends_with('/') {
-            found_resource = true;
-            let rel_path = &name[resource_prefix.len()..];
-            let rel_path = rel_path.strip_prefix('/').unwrap_or(rel_path);
-            let dest_path = app_data_dir.join(rel_path);
-
-            if let Some(parent) = dest_path.parent() {
-                fs::create_dir_all(parent)
-                    .map_err(|e| anyhow::anyhow!("Failed to create parent directory: {}", e))?;
-            }
-
-            println!("Copying {} to {:?}", name, dest_path);
-
-            let mut outfile = File::create(&dest_path)
-                .map_err(|e| anyhow::anyhow!("Failed to create file: {}", e))?;
-            std::io::copy(&mut file, &mut outfile)
-                .map_err(|e| anyhow::anyhow!("Failed to copy file: {}", e))?;
-        }
-    }
-
-    if !found_resource {
-        return Err(anyhow::anyhow!(
-            "No resources folder found in the github repository [4tkbytes/dropbear] :("
-        ));
-    }
+    // let repo_zip_url = "https://github.com/4tkbytes/dropbear/archive/refs/heads/main.zip";
+    // let response = reqwest::blocking::get(repo_zip_url)
+    //     .map_err(|e| anyhow::anyhow!("Failed to download repo zip: {}", e))?
+    //     .bytes()
+    //     .map_err(|e| anyhow::anyhow!("Failed to read zip bytes: {}", e))?;
+    // 
+    // let reader = Cursor::new(response);
+    // let mut zip = zip::ZipArchive::new(reader)
+    //     .map_err(|e| anyhow::anyhow!("Failed to read zip archive: {}", e))?;
+    // 
+    // let app_info = app_dirs2::AppInfo {
+    //     name: "Eucalyptus",
+    //     author: "4tkbytes",
+    // };
+    // let app_data_dir = app_dirs2::app_root(app_dirs2::AppDataType::UserData, &app_info)
+    //     .map_err(|e| anyhow::anyhow!("Could not determine app data directory: {}", e))?;
+    // 
+    // fs::create_dir_all(&app_data_dir)
+    //     .map_err(|e| anyhow::anyhow!("Failed to create app data directory: {}", e))?;
+    // 
+    // let resource_prefix = "dropbear-main/resources/";
+    // let mut found_resource = false;
+    // for i in 0..zip.len() {
+    //     let mut file = zip.by_index(i).unwrap();
+    //     let name = file.name();
+    // 
+    //     if name.starts_with(resource_prefix) && !name.ends_with('/') {
+    //         found_resource = true;
+    //         let rel_path = &name[resource_prefix.len()..];
+    //         let rel_path = rel_path.strip_prefix('/').unwrap_or(rel_path);
+    //         let dest_path = app_data_dir.join(rel_path);
+    // 
+    //         if let Some(parent) = dest_path.parent() {
+    //             fs::create_dir_all(parent)
+    //                 .map_err(|e| anyhow::anyhow!("Failed to create parent directory: {}", e))?;
+    //         }
+    // 
+    //         println!("Copying {} to {:?}", name, dest_path);
+    // 
+    //         let mut outfile = File::create(&dest_path)
+    //             .map_err(|e| anyhow::anyhow!("Failed to create file: {}", e))?;
+    //         std::io::copy(&mut file, &mut outfile)
+    //             .map_err(|e| anyhow::anyhow!("Failed to copy file: {}", e))?;
+    //     }
+    // }
+    // 
+    // if !found_resource {
+    //     return Err(anyhow::anyhow!(
+    //         "No resources folder found in the github repository [4tkbytes/dropbear] :("
+    //     ));
+    // }
 
     // fuck you windows :(
     #[cfg(target_os = "windows")]
diff --git a/eucalyptus-editor/src/editor/component.rs b/eucalyptus-editor/src/editor/component.rs
index d70c299..4589d71 100644
--- a/eucalyptus-editor/src/editor/component.rs
+++ b/eucalyptus-editor/src/editor/component.rs
@@ -594,7 +594,7 @@ impl InspectableComponent for ScriptComponent {
                     ui.horizontal(|ui| {
                         if ui.button("Browse").clicked()
                             && let Some(script_file) = rfd::FileDialog::new()
-                                .add_filter("Typescript", &["ts"])
+                                .add_filter("Kotlin", &["kt"])
                                 .pick_file()
                             {
                                 let script_name = script_file
@@ -610,8 +610,8 @@ impl InspectableComponent for ScriptComponent {
 
                         if ui.button("New").clicked()
                             && let Some(script_path) = rfd::FileDialog::new()
-                                .add_filter("TypeScript", &["ts"])
-                                .set_file_name(format!("{}_script.ts", label))
+                                .add_filter("Kotlin", &["kt"])
+                                .set_file_name(format!("{}_script.kt", label))
                                 .save_file()
                             {
                                 match std::fs::write(&script_path, TEMPLATE_SCRIPT) {
diff --git a/eucalyptus-editor/src/editor/dock.rs b/eucalyptus-editor/src/editor/dock.rs
index cf0fbbe..5a75e9f 100644
--- a/eucalyptus-editor/src/editor/dock.rs
+++ b/eucalyptus-editor/src/editor/dock.rs
@@ -74,6 +74,10 @@ pub struct DraggedAsset {
 pub static TABS_GLOBAL: LazyLock<Mutex<StaticallyKept>> =
     LazyLock::new(|| Mutex::new(StaticallyKept::default()));
 
+// Separate static for REPL to avoid deadlocks
+pub static KOTLIN_REPL: LazyLock<Mutex<Option<crate::editor::repl::KotlinREPL>>> =
+    LazyLock::new(|| Mutex::new(None));
+
 /// Variables kept statically.
 ///
 /// The entire module (including the tab viewer) due to it
@@ -107,6 +111,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
             EditorTab::ModelEntityList => "Model/Entity List".into(),
             EditorTab::AssetViewer => "Asset Viewer".into(),
             EditorTab::ResourceInspector => "Resource Inspector".into(),
+            EditorTab::KotlinREPL => "Kotlin REPL".into(),
         }
     }
 
@@ -1021,6 +1026,17 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                     ui.label("No entity selected, therefore no info to provide. Go on, what are you waiting for? Click an entity!");
                 }
             }
+            EditorTab::KotlinREPL => {
+                // Use separate static to avoid deadlock with TABS_GLOBAL
+                let mut repl_lock = KOTLIN_REPL.lock();
+                if repl_lock.is_none() {
+                    *repl_lock = Some(crate::editor::repl::KotlinREPL::new());
+                }
+                
+                if let Some(repl) = repl_lock.as_mut() {
+                    repl.ui(ui);
+                }
+            }
         }
 
         let mut menu_action: Option<EditorTabMenuAction> = None;
@@ -1071,6 +1087,9 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                                 menu_action = Some(EditorTabMenuAction::ViewportOption);
                             }
                         }
+                        EditorTab::KotlinREPL => {
+                            // No context menu actions for REPL tab yet
+                        }
                     }
                 })
             });
diff --git a/eucalyptus-editor/src/editor/mod.rs b/eucalyptus-editor/src/editor/mod.rs
index c523504..ffa594e 100644
--- a/eucalyptus-editor/src/editor/mod.rs
+++ b/eucalyptus-editor/src/editor/mod.rs
@@ -1,6 +1,7 @@
 pub mod component;
 pub mod dock;
 pub mod input;
+pub mod repl;
 pub mod scene;
 
 pub(crate) use crate::editor::dock::*;
@@ -113,7 +114,7 @@ impl Editor {
             surface.split_right(NodeIndex::root(), 0.25, vec![EditorTab::ModelEntityList]);
         let [_old, _] =
             surface.split_left(NodeIndex::root(), 0.20, vec![EditorTab::ResourceInspector]);
-        let [_old, _] = surface.split_below(right, 0.5, vec![EditorTab::AssetViewer]);
+        let [_old, _] = surface.split_below(right, 0.5, vec![EditorTab::AssetViewer, EditorTab::KotlinREPL]);
 
         // this shit doesn't work :(
         // nvm it works
@@ -583,6 +584,9 @@ impl Editor {
                     if ui_window.button("Open Viewport").clicked() {
                         self.dock_state.push_to_focused_leaf(EditorTab::Viewport);
                     }
+                    if ui_window.button("Open Kotlin REPL").clicked() {
+                        self.dock_state.push_to_focused_leaf(EditorTab::KotlinREPL);
+                    }
                 });
                 {
                     let cfg = PROJECT.read();
diff --git a/eucalyptus-editor/src/editor/repl.rs b/eucalyptus-editor/src/editor/repl.rs
new file mode 100644
index 0000000..0669a3c
--- /dev/null
+++ b/eucalyptus-editor/src/editor/repl.rs
@@ -0,0 +1,221 @@
+use egui::{Color32, FontId, RichText, ScrollArea, TextEdit};
+
+/// A simple Kotlin REPL interface for testing scripts
+pub struct KotlinREPL {
+    input: String,
+    output: Vec<ReplOutputLine>,
+    history: Vec<String>,
+    history_index: Option<usize>,
+}
+
+#[derive(Clone)]
+pub struct ReplOutputLine {
+    pub text: String,
+    pub is_error: bool,
+    pub is_input: bool,
+}
+
+impl Default for KotlinREPL {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl KotlinREPL {
+    pub fn new() -> Self {
+        let mut repl = Self {
+            input: String::new(),
+            output: Vec::new(),
+            history: Vec::new(),
+            history_index: None,
+        };
+        
+        repl.add_output("Kotlin REPL - Ready", false, false);
+        repl.add_output("Type Kotlin expressions to test script functionality", false, false);
+        repl.add_output("Example: Input.isKeyPressed(KeyCode.W)", false, false);
+        repl.add_output("", false, false);
+        
+        repl
+    }
+    
+    fn add_output(&mut self, text: &str, is_error: bool, is_input: bool) {
+        self.output.push(ReplOutputLine {
+            text: text.to_string(),
+            is_error,
+            is_input,
+        });
+    }
+    
+    fn execute(&mut self, code: &str) {
+        // Add input to output
+        self.add_output(&format!("> {}", code), false, true);
+        
+        // Add to history
+        if !code.trim().is_empty() {
+            self.history.push(code.to_string());
+            self.history_index = None;
+        }
+        
+        // Execute the code
+        match self.execute_kotlin_code(code) {
+            Ok(result) => {
+                if !result.is_empty() {
+                    self.add_output(&result, false, false);
+                }
+            }
+            Err(e) => {
+                self.add_output(&format!("Error: {}", e), true, false);
+            }
+        }
+        
+        self.add_output("", false, false); // blank line
+    }
+    
+    fn execute_kotlin_code(
+        &self,
+        code: &str,
+    ) -> anyhow::Result<String> {
+        // For now, we'll provide a simplified evaluation
+        // In the future, this could compile and run actual Kotlin code via JNI
+        
+        // Check for common test commands
+        if code.trim().starts_with("Input.isKeyPressed") {
+            Ok("boolean (check console for actual value)".to_string())
+        } else if code.trim().starts_with("Input.getMouseX") 
+                || code.trim().starts_with("Input.getMouseY") {
+            Ok("double (check console for actual value)".to_string())
+        } else if code.contains("Transform") {
+            Ok("Transform manipulation (check entity in viewport)".to_string())
+        } else if code.trim() == "help" {
+            Ok(r#"Available APIs:
+- Input.isKeyPressed(KeyCode.W) - Check if key is pressed
+- Input.getMouseX() - Get mouse X position
+- Input.getMouseY() - Get mouse Y position
+- engine.getTransform() - Get current entity transform
+- transform.position - Get/set position (Vector3D)
+- transform.rotation - Get/set rotation (Quaternion)
+- transform.scale - Get/set scale (Vector3D)
+
+Note: This is a simplified REPL. Full script execution requires 
+attaching a script to an entity and running the game."#.to_string())
+        } else if code.trim() == "clear" {
+            return Err(anyhow::anyhow!("CLEAR_SCREEN"));
+        } else {
+            Ok(format!("Command received: {}\n(Full Kotlin evaluation not yet implemented)", code))
+        }
+    }
+    
+    pub fn ui(&mut self, ui: &mut egui::Ui) {
+        ui.vertical(|ui| {
+            // Title bar
+            ui.horizontal(|ui| {
+                ui.heading("Kotlin REPL");
+                ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
+                    if ui.button("Clear").clicked() {
+                        self.output.clear();
+                        self.add_output("Kotlin REPL - Ready", false, false);
+                    }
+                    if ui.button("Help").clicked() {
+                        self.execute("help");
+                    }
+                });
+            });
+            
+            ui.separator();
+            
+            // Output area
+            ScrollArea::vertical()
+                .auto_shrink([false, false])
+                .stick_to_bottom(true)
+                .show(ui, |ui| {
+                    ui.set_min_height(ui.available_height() - 80.0);
+                    
+                    for line in &self.output {
+                        let color = if line.is_error {
+                            Color32::from_rgb(255, 100, 100)
+                        } else if line.is_input {
+                            Color32::from_rgb(100, 200, 255)
+                        } else {
+                            Color32::from_rgb(200, 200, 200)
+                        };
+                        
+                        ui.label(
+                            RichText::new(&line.text)
+                                .color(color)
+                                .font(FontId::monospace(14.0))
+                        );
+                    }
+                });
+            
+            ui.separator();
+            
+            // Input area
+            ui.horizontal(|ui| {
+                ui.label(">>>");
+                
+                let response = ui.add(
+                    TextEdit::singleline(&mut self.input)
+                        .desired_width(f32::INFINITY)
+                        .font(FontId::monospace(14.0))
+                );
+                
+                // Handle keyboard input
+                if response.has_focus() {
+                    if ui.input(|i| i.key_pressed(egui::Key::Enter)) {
+                        let code = self.input.clone();
+                        self.input.clear();
+                        
+                        if code.trim() == "clear" {
+                            self.output.clear();
+                            self.add_output("Kotlin REPL - Ready", false, false);
+                        } else {
+                            self.execute(&code);
+                        }
+                    }
+                    
+                    // History navigation
+                    if ui.input(|i| i.key_pressed(egui::Key::ArrowUp)) {
+                        if !self.history.is_empty() {
+                            if let Some(idx) = self.history_index {
+                                if idx > 0 {
+                                    self.history_index = Some(idx - 1);
+                                    self.input = self.history[idx - 1].clone();
+                                }
+                            } else {
+                                self.history_index = Some(self.history.len() - 1);
+                                self.input = self.history[self.history.len() - 1].clone();
+                            }
+                        }
+                    }
+                    
+                    if ui.input(|i| i.key_pressed(egui::Key::ArrowDown)) {
+                        if let Some(idx) = self.history_index {
+                            if idx < self.history.len() - 1 {
+                                self.history_index = Some(idx + 1);
+                                self.input = self.history[idx + 1].clone();
+                            } else {
+                                self.history_index = None;
+                                self.input.clear();
+                            }
+                        }
+                    }
+                }
+                
+                if ui.button("Execute").clicked() && !self.input.is_empty() {
+                    let code = self.input.clone();
+                    self.input.clear();
+                    self.execute(&code);
+                }
+            });
+            
+            // Tips
+            ui.horizontal(|ui| {
+                ui.label(
+                    RichText::new("Tip: Type 'help' for available commands, 'clear' to clear output. Use ↑↓ for history.")
+                        .size(10.0)
+                        .color(Color32::from_rgb(150, 150, 150))
+                );
+            });
+        });
+    }
+}
diff --git a/src/main/kotlin/com/dropbear/DropbearEngine.kt b/src/main/kotlin/com/dropbear/DropbearEngine.kt
index 44722c3..34c932f 100644
--- a/src/main/kotlin/com/dropbear/DropbearEngine.kt
+++ b/src/main/kotlin/com/dropbear/DropbearEngine.kt
@@ -1,11 +1,30 @@
 package com.dropbear
 
 import com.dropbear.ffi.NativeEngine
+import com.dropbear.math.Transform
 
+/**
+ * Main interface to the Dropbear game engine.
+ * 
+ * This class provides high-level access to engine features for Kotlin scripts.
+ * It wraps the low-level NativeEngine JNI bindings with a more ergonomic API.
+ */
 class DropbearEngine {
-    var nativeEngine: NativeEngine? = null
+    /**
+     * Get the transform of the current entity.
+     * 
+     * @return A Transform object that provides live access to position, rotation, and scale
+     * 
+     * @example
+     * ```kotlin
+     * val transform = engine.getTransform()
+     * transform.position.y += 0.1 // Move up
+     * ```
+     */
+    fun getTransform(): Transform {
+        return Transform.fromNative()
+    }
 
-    // figure out how to create a new class
     /**
      * Fetches the currently active entity the script is attached to.
      *
@@ -13,7 +32,8 @@ class DropbearEngine {
      * a `null` in the form of a [Result]
      */
     fun getActiveEntity(): Result<EntityRef> {
-        return Result.failure(Exception("Function not implemented"))
+        // This would need additional native support to get entity ID and label
+        return Result.failure(Exception("Function not fully implemented - requires entity label support"))
     }
 
     /**
@@ -23,6 +43,7 @@ class DropbearEngine {
      * `null` in the form of a [Result]
      */
     fun getEntity(label: String): EntityRef? {
+        // This would need additional native support for entity lookup by label
         return null
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/com/dropbear/EntityRef.kt b/src/main/kotlin/com/dropbear/EntityRef.kt
index 7bccd3c..d01228c 100644
--- a/src/main/kotlin/com/dropbear/EntityRef.kt
+++ b/src/main/kotlin/com/dropbear/EntityRef.kt
@@ -1,27 +1,18 @@
 package com.dropbear
 
+import com.dropbear.math.Transform
 import com.dropbear.math.Vector3D
 
 /**
  * A class to hold a reference to an entity.
- *
- * The dropbear engine interface is made in Rust, which uses a
- * borrow checker paradigm, which heavily utilises immutability
- * unless explicitly provided with the `mut` keyword.
- *
- * Because of this, the primary source of the World (place to store
- * entities) is stored in Rust, and passing an EntityRef (which contains
- * an ID) follows Rust's ideologies of passing references instead of the entire entity,
- * which provides immutability.
- *
- * To edit any values part of the entity, take a look at the functions provided
- * by [EntityRef], which will require a reference to [DropbearEngine] to push the commands.
  */
-class EntityRef {
-    var label: String = ""
-
-    /**
-     * Sets the position of the entity by a Vector
-     */
-    fun setPosition(position: Vector3D, engine: DropbearEngine) {}
-}
\ No newline at end of file
+class EntityRef(val label: String) {
+    fun getTransform(engine: DropbearEngine): Transform {
+        return engine.getTransform()
+    }
+    
+    fun setPosition(position: Vector3D, engine: DropbearEngine) {
+        val transform = engine.getTransform()
+        transform.position = position
+    }
+}
diff --git a/src/main/kotlin/com/dropbear/Example.kt b/src/main/kotlin/com/dropbear/Example.kt
index e1ef013..2efd4e5 100644
--- a/src/main/kotlin/com/dropbear/Example.kt
+++ b/src/main/kotlin/com/dropbear/Example.kt
@@ -1,12 +1,40 @@
 package com.dropbear
 
-class Example(override var engine: DropbearEngine) : RunnableScript {
-    override fun load() {
-        val entity = engine.getActiveEntity()
+import com.dropbear.input.Input
+import com.dropbear.input.KeyCode
+import com.dropbear.math.Vector3D
 
+class Example : RunnableScript {
+    override var engine: DropbearEngine = DropbearEngine()
+    
+    private var speed = 0.1
+    
+    override fun load() {
+        println("Example script loaded!")
     }
-
+    
     override fun update() {
-        TODO("Not yet implemented")
+        val transform = engine.getTransform()
+        engine.getEntity("player")?.getTransform(engine)
+        
+        if (Input.isKeyPressed(KeyCode.W)) {
+            transform.translate(Vector3D(0.0, 0.0, -speed))
+        }
+        if (Input.isKeyPressed(KeyCode.S)) {
+            transform.translate(Vector3D(0.0, 0.0, speed))
+        }
+        if (Input.isKeyPressed(KeyCode.A)) {
+            transform.translate(Vector3D(-speed, 0.0, 0.0))
+        }
+        if (Input.isKeyPressed(KeyCode.D)) {
+            transform.translate(Vector3D(speed, 0.0, 0.0))
+        }
+        
+        if (Input.isKeyPressed(KeyCode.SPACE)) {
+            transform.translate(Vector3D(0.0, speed, 0.0))
+        }
+        if (Input.isKeyPressed(KeyCode.SHIFT)) {
+            transform.translate(Vector3D(0.0, -speed, 0.0))
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/com/dropbear/ffi/NativeEngine.java b/src/main/kotlin/com/dropbear/ffi/NativeEngine.java
index bbedb38..90c45fb 100644
--- a/src/main/kotlin/com/dropbear/ffi/NativeEngine.java
+++ b/src/main/kotlin/com/dropbear/ffi/NativeEngine.java
@@ -1,22 +1,77 @@
 package com.dropbear.ffi;
 
-import com.dropbear.math.Transform;
-
 /**
- * A class for dealing with native functions that is required by the Kotlin scripting
- * and the Rust game engine.
+ * Native interface to the Rust game engine.
+ * This class provides low-level FFI bindings via JNI.
+ * 
+ * All methods are static and call into native Rust code.
  */
 public class NativeEngine {
+    static {
+        // Load the native library
+        // In production, this should load from the JAR or a known location
+        try {
+            System.loadLibrary("eucalyptus_core");
+        } catch (UnsatisfiedLinkError e) {
+            System.err.println("Failed to load native library: " + e.getMessage());
+        }
+    }
+
+    // =============================================================================
+    // Context Management
+    // =============================================================================
+    
     /**
-     * Fetches the transform of the entity by its label in the editor
-     * @param label The label of the entity
-     * @return The {@link Transform} component if the entity exists, null if not.
+     * Set the current scripting context (internal use only)
      */
-    public native Transform getTransformOfEntity(String label);
+    public static native void setContext(long entityId, long worldPtr);
+
+    // =============================================================================
+    // Transform - Position
+    // =============================================================================
+    
+    public static native double getPositionX();
+    public static native double getPositionY();
+    public static native double getPositionZ();
+    public static native void setPosition(double x, double y, double z);
+
+    // =============================================================================
+    // Transform - Rotation (Quaternion)
+    // =============================================================================
+    
+    public static native double getRotationX();
+    public static native double getRotationY();
+    public static native double getRotationZ();
+    public static native double getRotationW();
+    public static native void setRotation(double x, double y, double z, double w);
+
+    // =============================================================================
+    // Transform - Scale
+    // =============================================================================
+    
+    public static native double getScaleX();
+    public static native double getScaleY();
+    public static native double getScaleZ();
+    public static native void setScale(double x, double y, double z);
 
+    // =============================================================================
+    // Input System
+    // =============================================================================
+    
+    /**
+     * Check if a key is currently pressed
+     * @param keycode The keycode to check (use KeyCode constants)
+     * @return true if the key is pressed, false otherwise
+     */
+    public static native boolean isKeyPressed(long keycode);
+    
+    /**
+     * Get the current mouse X position
+     */
+    public static native double getMouseX();
+    
     /**
-     * Fetches the label of the entity this script is currently attached to.
-     * @return The label of the attached entity or null if its not attached
+     * Get the current mouse Y position
      */
-    public native String getLabelOfAttachedEntity();
+    public static native double getMouseY();
 }
diff --git a/src/main/kotlin/com/dropbear/input/Input.kt b/src/main/kotlin/com/dropbear/input/Input.kt
new file mode 100644
index 0000000..46a6e24
--- /dev/null
+++ b/src/main/kotlin/com/dropbear/input/Input.kt
@@ -0,0 +1,47 @@
+package com.dropbear.input
+
+import com.dropbear.ffi.NativeEngine
+
+/**
+ * High-level input management wrapper for accessing input state
+ * from Kotlin scripts.
+ */
+object Input {
+    /**
+     * Check if a specific key is currently pressed
+     * 
+     * @param keyCode The key to check (use KeyCode constants)
+     * @return true if the key is pressed, false otherwise
+     * 
+     * @example
+     * ```kotlin
+     * if (Input.isKeyPressed(KeyCode.W)) {
+     *     // Move forward
+     * }
+     * ```
+     */
+    fun isKeyPressed(keyCode: Long): Boolean {
+        return NativeEngine.isKeyPressed(keyCode)
+    }
+    
+    /**
+     * Get the current mouse X position
+     */
+    fun getMouseX(): Double {
+        return NativeEngine.getMouseX()
+    }
+    
+    /**
+     * Get the current mouse Y position
+     */
+    fun getMouseY(): Double {
+        return NativeEngine.getMouseY()
+    }
+    
+    /**
+     * Get the mouse position as a pair
+     */
+    fun getMousePosition(): Pair<Double, Double> {
+        return Pair(getMouseX(), getMouseY())
+    }
+}
diff --git a/src/main/kotlin/com/dropbear/input/KeyCode.kt b/src/main/kotlin/com/dropbear/input/KeyCode.kt
new file mode 100644
index 0000000..ca8f9f4
--- /dev/null
+++ b/src/main/kotlin/com/dropbear/input/KeyCode.kt
@@ -0,0 +1,47 @@
+package com.dropbear.input
+
+/**
+ * Key codes that match the Rust KeyCode enum.
+ * These are used with NativeEngine.isKeyPressed()
+ */
+object KeyCode {
+    // Letters
+    const val A = 65L
+    const val B = 66L
+    const val C = 67L
+    const val D = 68L
+    const val E = 69L
+    const val F = 70L
+    const val G = 71L
+    const val H = 72L
+    const val I = 73L
+    const val J = 74L
+    const val K = 75L
+    const val L = 76L
+    const val M = 77L
+    const val N = 78L
+    const val O = 79L
+    const val P = 80L
+    const val Q = 81L
+    const val R = 82L
+    const val S = 83L
+    const val T = 84L
+    const val U = 85L
+    const val V = 86L
+    const val W = 87L
+    const val X = 88L
+    const val Y = 89L
+    const val Z = 90L
+    
+    // Arrow keys
+    const val ARROW_LEFT = 37L
+    const val ARROW_UP = 38L
+    const val ARROW_RIGHT = 39L
+    const val ARROW_DOWN = 40L
+    
+    // Special keys
+    const val SPACE = 32L
+    const val SHIFT = 16L
+    const val CONTROL = 17L
+    const val ESCAPE = 27L
+}
diff --git a/src/main/kotlin/com/dropbear/math/Transform.kt b/src/main/kotlin/com/dropbear/math/Transform.kt
index bfa039f..69cec1c 100644
--- a/src/main/kotlin/com/dropbear/math/Transform.kt
+++ b/src/main/kotlin/com/dropbear/math/Transform.kt
@@ -1,9 +1,116 @@
 package com.dropbear.math
 
+import com.dropbear.ffi.NativeEngine
+
 /**
  * A class that keeps all the values of a Transform, which consists of a
  * position ([Vector3D]), rotation([QuaternionD]) and a scale([Vector3D]).
+ * 
+ * This class provides direct access to transform data from the Rust engine.
  */
-class Transform(var position: Vector3D, var rotation: QuaternionD, var scale: Vector3D) {
-
-}
\ No newline at end of file
+class Transform private constructor(
+    private var _position: Vector3D,
+    private var _rotation: QuaternionD,
+    private var _scale: Vector3D
+) {
+    /**
+     * Get the position from the native engine
+     */
+    var position: Vector3D
+        get() {
+            _position = Vector3D(
+                NativeEngine.getPositionX(),
+                NativeEngine.getPositionY(),
+                NativeEngine.getPositionZ()
+            )
+            return _position
+        }
+        set(value) {
+            _position = value
+            NativeEngine.setPosition(value.x, value.y, value.z)
+        }
+    
+    /**
+     * Get the rotation from the native engine
+     */
+    var rotation: QuaternionD
+        get() {
+            _rotation = Quaternion(
+                NativeEngine.getRotationX(),
+                NativeEngine.getRotationY(),
+                NativeEngine.getRotationZ(),
+                NativeEngine.getRotationW()
+            )
+            return _rotation
+        }
+        set(value) {
+            _rotation = value
+            NativeEngine.setRotation(value.x, value.y, value.z, value.w)
+        }
+    
+    /**
+     * Get the scale from the native engine
+     */
+    var scale: Vector3D
+        get() {
+            _scale = Vector3D(
+                NativeEngine.getScaleX(),
+                NativeEngine.getScaleY(),
+                NativeEngine.getScaleZ()
+            )
+            return _scale
+        }
+        set(value) {
+            _scale = value
+            NativeEngine.setScale(value.x, value.y, value.z)
+        }
+    
+    companion object {
+        /**
+         * Creates a new Transform with everything to its default values.
+         * This creates a live Transform that reads from the native engine.
+         */
+        fun default(): Transform {
+            return Transform(
+                Vector3D.zero(),
+                Quaternion.identity(),
+                Vector3D(1.0, 1.0, 1.0)
+            )
+        }
+        
+        /**
+         * Creates a Transform from the current native engine state
+         */
+        fun fromNative(): Transform {
+            val pos = Vector3D(
+                NativeEngine.getPositionX(),
+                NativeEngine.getPositionY(),
+                NativeEngine.getPositionZ()
+            )
+            val rot = Quaternion(
+                NativeEngine.getRotationX(),
+                NativeEngine.getRotationY(),
+                NativeEngine.getRotationZ(),
+                NativeEngine.getRotationW()
+            )
+            val scale = Vector3D(
+                NativeEngine.getScaleX(),
+                NativeEngine.getScaleY(),
+                NativeEngine.getScaleZ()
+            )
+            return Transform(pos, rot, scale)
+        }
+    }
+    
+    /**
+     * Translate (move) the transform by a delta
+     */
+    fun translate(delta: Vector3D) {
+        val current = position
+        position = Vector3D(
+            current.x + delta.x,
+            current.y + delta.y,
+            current.z + delta.z
+        )
+    }
+}