kitgit

tirbofish/dropbear · commit

3bc8da8ab3540c673e408f1f2b219036ae9d2486

ok so i have gotten typescript support pretty nicely done :)

Unverified

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

view full diff

diff --git a/Cargo.toml b/Cargo.toml
index c73c479..3d89336 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -40,8 +40,8 @@ open = "5"
 parking_lot = {version = "0.12", features = ["deadlock_detection"] }
 pollster = "0.4"
 rfd = "0.15.4"
-ron = "0.10.1"
-russimp-ng = { version = "3.2.4", features = ["static-link"] }
+ron = "0.11"
+russimp-ng = { version = "3.2" }
 rustyscript = { version = "0.12" }
 serde = { version = "1.0.219", features = ["derive"] }
 spin_sleep = "1.3"
@@ -49,7 +49,7 @@ tokio = { version = "1", features = ["full"] }
 transform-gizmo-egui = { git = "https://github.com/4tkbytes/transform-gizmo"}
 wgpu = "26"
 winit = { version = "0.30", features = [] }
-zip = "4.3"
+zip = "4.6"
 walkdir = "2.3"
 
 [workspace.dependencies.image]
diff --git a/README.md b/README.md
index abd85fb..f59595b 100644
--- a/README.md
+++ b/README.md
@@ -19,10 +19,7 @@ With Unix systems (macOS not tested), you will have to download a couple depende
 
 ```bash
 # ubuntu, adapt to your own OS
-sudo apt install libudev-dev pkg-config libssl-dev clang cmake meson
-
-# if on arm devices where russimp cannot compile
-sudo apt install assimp-utils
+sudo apt install libudev-dev pkg-config libssl-dev clang cmake meson assimp-utils
 ```
 
 After downloading the requirements, you are free to build it using cargo.
@@ -48,9 +45,13 @@ If you do not want to build it locally, you are able to download the latest acti
 ~~Depsite it looking like a dependency for `eucalyptus`, it can serve as a framework too. Looking through the `docs.rs` will you find related documentation onhow to use it and for rendering your own projects.~~
 
 dropbear cannot be used as a framework (yet), but is best compatible with the eucalyptus editor when making games. For 
-scripting, eucalyptus uses `rhai`, a new language that works with rust. 
+scripting, eucalyptus uses `typescript`. The main philosophy of scripting is to be able to have **type safety**, which can aid developers with their code, which is the reason as to why I didn't use `javascript` for scripting instead.
+
+The typescript used in the scripting is ran with the deno runtime. 
+
+TODO: Create better documentation
 
-The rhai reference for the eucalyptus editor is under the /docs folder of this repository, so take a look there. 
+The typescript reference for the eucalyptus editor is under the /docs folder of this repository, so take a look there. 
 [Here is the entrance](https://github.com/4tkbytes/dropbear/blob/main/docs/README.md)
 
 ## Compability
@@ -62,10 +63,7 @@ The rhai reference for the eucalyptus editor is under the /docs folder of this r
 
 <sup>1</sup> Will never be implemented; not intended for that platform.
 
-<sup>2</sup>  Made some progress on implementing.
-
-
-To be fair, I do not plan on supporting web, android or iOS yet (as it isnt even completed with the basic idea). Maybe I will...?
+<sup>2</sup>  Made some progress on implementing, but currently a WIP. 
 
 ## Contributions
 
@@ -75,5 +73,5 @@ Yeah yeah, go ahead and contribute. Make sure it works, and its not spam, and an
 In the case someone actually makes something with my engine and distributes it, it (meaning **dropbear-engine**, 
 **eucalyptus** and **redback-runtime**) must abide by the license in [LICENSE.md](LICENSE.md). 
 
-The gleek package is licensed under the [MIT License](https://mit-license.org/), which allows for anyone to use my 
-library without _much_ restrictions. 
\ No newline at end of file
+<!-- The gleek package is licensed under the [MIT License](https://mit-license.org/), which allows for anyone to use my 
+library without _much_ restrictions.  -->
\ No newline at end of file
diff --git a/dropbear-engine/src/panic.rs b/dropbear-engine/src/panic.rs
index 95f8b91..b962155 100644
--- a/dropbear-engine/src/panic.rs
+++ b/dropbear-engine/src/panic.rs
@@ -23,7 +23,7 @@ pub fn set_hook() {
 
         let full_text = format!(
             "The application has encountered a fatal error and must close.\n\n\
-             Location: {}\nError: {}\n\nPlease report this error to the developers.",
+            Location: {}\nError: {}\n\nPlease report this error to the developers.",
             location, msg
         );
 
@@ -41,5 +41,7 @@ pub fn set_hook() {
                 .set_level(MessageLevel::Error)
                 .show();
         }
+
+        std::process::abort();
     }));
 }
diff --git a/eucalyptus/build.rs b/eucalyptus/build.rs
index 4b31369..f27a6d2 100644
--- a/eucalyptus/build.rs
+++ b/eucalyptus/build.rs
@@ -55,7 +55,12 @@ fn main() -> anyhow::Result<()> {
         ));
     }
 
-    println!("cargo:rustc-link-arg=/NODEFAULTLIB:libcmt.lib");
+    #[cfg(target_os = "windows")]
+    {
+        println!("cargo:rustc-link-arg=/FORCE:MULTIPLE");
+        println!("cargo:rustc-link-arg=/NODEFAULTLIB:libcmt.lib");
+    }
+
     println!("cargo:rerun-if-changed=build.rs");
     Ok(())
 }
diff --git a/eucalyptus/src/dropbear.ts b/eucalyptus/src/dropbear.ts
index f8e1168..b382d9a 100644
--- a/eucalyptus/src/dropbear.ts
+++ b/eucalyptus/src/dropbear.ts
@@ -1,257 +1,331 @@
 // Modules for the dropbear-engine scripting component
 // Made by 4tkbytes
-// EDIT THIS IF YOU WISH, MOST LIKELY 
+// EDIT THIS IF YOU WISH, RECOMMENDED TO NOT TOUCH IT
 
-type TransformT = any;
-type Vec3T = any;
-type QuatT = any;
 type InputStateT = any;
 
-/**
- * A placeholder function that returns the value of the host function
- */
-function hostFn(name: string) {
-    const fn = (globalThis as any)[name];
-    if (typeof fn !== "function") {
-        throw new Error(`Host function ${name}() is not available. Make sure the runtime exposes it.`);
+export class Transform {
+    position: Vector3;
+    rotation: Quaternion;
+    scale: Vector3;
+
+    public constructor(position?: Vector3, rotation?: Quaternion, scale?: Vector3) {
+        this.position = position || Vector3.zero();
+        this.rotation = rotation || Quaternion.identity();
+        this.scale = scale || Vector3.one();
     }
-    return fn;
-}
 
-const Transform = {
-    create: (): TransformT => hostFn("createTransform")(),
-    translate: (transform: TransformT, translation: [number, number, number] | number[]): TransformT =>
-        hostFn("transformTranslate")(transform, translation),
-    rotateX: (transform: TransformT, angle: number): TransformT =>
-        hostFn("transformRotateX")(transform, angle),
-    rotateY: (transform: TransformT, angle: number): TransformT =>
-        hostFn("transformRotateY")(transform, angle),
-    rotateZ: (transform: TransformT, angle: number): TransformT =>
-        hostFn("transformRotateZ")(transform, angle),
-    scale: (transform: TransformT, scale: number | [number, number, number]): TransformT =>
-        hostFn("transformScale")(transform, scale),
-    matrix: (transform: TransformT): TransformT => hostFn("transformMatrix")(transform),
-};
+    public translate(movement: Vector3) {
+        // Simple direct math - no host functions needed
+        this.position.x += movement.x;
+        this.position.y += movement.y;
+        this.position.z += movement.z;
+    }
+
+    public rotateX(angle: number) {
+        // Create rotation quaternion and multiply
+        const rotQuat = Quaternion.fromAxisAngle(new Vector3(1, 0, 0), angle);
+        this.rotation = Quaternion.multiply(this.rotation, rotQuat);
+    }
+
+    public rotateY(angle: number) {
+        const rotQuat = Quaternion.fromAxisAngle(new Vector3(0, 1, 0), angle);
+        this.rotation = Quaternion.multiply(this.rotation, rotQuat);
+    }
+
+    public rotateZ(angle: number) {
+        const rotQuat = Quaternion.fromAxisAngle(new Vector3(0, 0, 1), angle);
+        this.rotation = Quaternion.multiply(this.rotation, rotQuat);
+    }
+
+    public scaleUniform(scale: number) {
+        this.scale.x *= scale;
+        this.scale.y *= scale;
+        this.scale.z *= scale;
+    }
+
+    public scaleIndividual(scale: [number, number, number]) {
+        this.scale.x *= scale[0];
+        this.scale.y *= scale[1];
+        this.scale.z *= scale[2];
+    }
+}
 
 /**
  * A Vector of 3 components: an X, Y and Z. 
  */
-const Vec3 = {
-    new: (x = 0, y = 0, z = 0): Vec3T => hostFn("createVec3")(x, y, z),
-    zero: (): Vec3T => hostFn("createVec3")(0, 0, 0),
-    one: (): Vec3T => hostFn("createVec3")(1, 1, 1),
-};
+export class Vector3 {
+    public x: number;
+    public y: number;
+    public z: number;
+
+    public constructor(x: number, y: number, z: number) {
+        this.x = x;
+        this.y = y;
+        this.z = z;
+    }
+
+    public as_array(): [number, number, number] {
+        return [this.x, this.y, this.z];
+    }
+
+    public static zero(): Vector3 {
+        return new Vector3(0.0, 0.0, 0.0);
+    }
+
+    public static one(): Vector3 {
+        return new Vector3(1.0, 1.0, 1.0);
+    }
+
+    public static fromArray(arr: [number, number, number]): Vector3 {
+        return new Vector3(arr[0], arr[1], arr[2]);
+    }
+
+    // Useful vector operations
+    public add(other: Vector3): Vector3 {
+        return new Vector3(this.x + other.x, this.y + other.y, this.z + other.z);
+    }
+
+    public subtract(other: Vector3): Vector3 {
+        return new Vector3(this.x - other.x, this.y - other.y, this.z - other.z);
+    }
+
+    public multiply(scalar: number): Vector3 {
+        return new Vector3(this.x * scalar, this.y * scalar, this.z * scalar);
+    }
+
+    public length(): number {
+        return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
+    }
+
+    public normalize(): Vector3 {
+        const len = this.length();
+        if (len === 0) return Vector3.zero();
+        return new Vector3(this.x / len, this.y / len, this.z / len);
+    }
+}
+
+export class Quaternion {
+    public x: number;
+    public y: number;
+    public z: number;
+    public w: number;
+
+    public constructor(x: number = 0, y: number = 0, z: number = 0, w: number = 1) {
+        this.x = x;
+        this.y = y;
+        this.z = z;
+        this.w = w;
+    }
+
+    public static fromEuler(x: number, y: number, z: number): Quaternion {
+        // Convert euler angles to quaternion
+        const cx = Math.cos(x * 0.5);
+        const sx = Math.sin(x * 0.5);
+        const cy = Math.cos(y * 0.5);
+        const sy = Math.sin(y * 0.5);
+        const cz = Math.cos(z * 0.5);
+        const sz = Math.sin(z * 0.5);
+
+        return new Quaternion(
+            sx * cy * cz - cx * sy * sz,
+            cx * sy * cz + sx * cy * sz,
+            cx * cy * sz - sx * sy * cz,
+            cx * cy * cz + sx * sy * sz
+        );
+    }
+
+    public static fromAxisAngle(axis: Vector3, angle: number): Quaternion {
+        const halfAngle = angle * 0.5;
+        const sin = Math.sin(halfAngle);
+        const normalizedAxis = axis.normalize();
+        
+        return new Quaternion(
+            normalizedAxis.x * sin,
+            normalizedAxis.y * sin,
+            normalizedAxis.z * sin,
+            Math.cos(halfAngle)
+        );
+    }
+
+    public static multiply(a: Quaternion, b: Quaternion): Quaternion {
+        return new Quaternion(
+            a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
+            a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
+            a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,
+            a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z
+        );
+    }
+
+    public static fromArray(arr: [number, number, number, number]): Quaternion {
+        return new Quaternion(arr[0], arr[1], arr[2], arr[3]);
+    }
+
+    public static identity(): Quaternion {
+        return new Quaternion(0.0, 0.0, 0.0, 1.0);
+    }
 
-const Quaternion = {
-    identity: (): QuatT => hostFn("createQuatIdentity")(),
-    fromEuler: (x: number, y: number, z: number): QuatT => hostFn("createQuatFromEuler")(x, y, z),
-};
+    public as_array(): [number, number, number, number] {
+        return [this.x, this.y, this.z, this.w];
+    }
+}
 
 export const Keys = {
-    // Letters
     KeyA: "KeyA", KeyB: "KeyB", KeyC: "KeyC", KeyD: "KeyD", KeyE: "KeyE", KeyF: "KeyF", 
     KeyG: "KeyG", KeyH: "KeyH", KeyI: "KeyI", KeyJ: "KeyJ", KeyK: "KeyK", KeyL: "KeyL", 
     KeyM: "KeyM", KeyN: "KeyN", KeyO: "KeyO", KeyP: "KeyP", KeyQ: "KeyQ", KeyR: "KeyR", 
     KeyS: "KeyS", KeyT: "KeyT", KeyU: "KeyU", KeyV: "KeyV", KeyW: "KeyW", KeyX: "KeyX", 
     KeyY: "KeyY", KeyZ: "KeyZ",
-    
-    // Numbers
     Digit0: "Digit0", Digit1: "Digit1", Digit2: "Digit2", Digit3: "Digit3", Digit4: "Digit4",
     Digit5: "Digit5", Digit6: "Digit6", Digit7: "Digit7", Digit8: "Digit8", Digit9: "Digit9",
-    
-    // Special keys
     Space: "Space",
-    ShiftLeft: "ShiftLeft",
-    ShiftRight: "ShiftRight",
-    ControlLeft: "ControlLeft",
-    ControlRight: "ControlRight",
-    AltLeft: "AltLeft",
-    AltRight: "AltRight",
-    Escape: "Escape",
-    Enter: "Enter",
-    Tab: "Tab",
-    ArrowUp: "ArrowUp",
-    ArrowDown: "ArrowDown",
-    ArrowLeft: "ArrowLeft",
-    ArrowRight: "ArrowRight",
-    
-    // Function keys
+    ShiftLeft: "ShiftLeft", ShiftRight: "ShiftRight",
+    ControlLeft: "ControlLeft", ControlRight: "ControlRight",
+    AltLeft: "AltLeft", AltRight: "AltRight",
+    Escape: "Escape", Enter: "Enter", Tab: "Tab",
+    ArrowUp: "ArrowUp", ArrowDown: "ArrowDown", ArrowLeft: "ArrowLeft", ArrowRight: "ArrowRight",
     F1: "F1", F2: "F2", F3: "F3", F4: "F4", F5: "F5", F6: "F6",
     F7: "F7", F8: "F8", F9: "F9", F10: "F10", F11: "F11", F12: "F12",
 } as const;
 
 export type KeyCode = typeof Keys[keyof typeof Keys];
 
-export const Input = {
-    /**
-     * Check if a key is currently pressed
-     * @param inputState The current input state
-     * @param key The key to check
-     * @returns true if the key is pressed, false otherwise
-     */
-    isKeyPressed: (inputState: InputStateT, key: KeyCode): boolean => 
-        hostFn("isKeyPressed")(inputState, key),
-
-    /**
-     * Get the current mouse X position
-     * @param inputState The current input state
-     * @returns The mouse X coordinate
-     */
-    getMouseX: (inputState: InputStateT): number => 
-        hostFn("getMouseX")(inputState),
-
-    /**
-     * Get the current mouse Y position
-     * @param inputState The current input state
-     * @returns The mouse Y coordinate
-     */
-    getMouseY: (inputState: InputStateT): number => 
-        hostFn("getMouseY")(inputState),
-
-    /**
-     * Get the mouse delta X (movement since last frame)
-     * @param inputState The current input state
-     * @returns The mouse delta X
-     */
-    getMouseDeltaX: (inputState: InputStateT): number => 
-        hostFn("getMouseDeltaX")(inputState),
-
-    /**
-     * Get the mouse delta Y (movement since last frame)
-     * @param inputState The current input state
-     * @returns The mouse delta Y
-     */
-    getMouseDeltaY: (inputState: InputStateT): number => 
-        hostFn("getMouseDeltaY")(inputState),
-
-    /**
-     * Lock or unlock the cursor
-     * @param locked Whether to lock the cursor
-     */
-    lockCursor: (locked: boolean): void => 
-        hostFn("lockCursor")(locked),
-};
-
-export const EntityProperties = {
-    /**
-     * Get a property value (generic)
-     * @param properties The entity properties object
-     * @param key The property key
-     * @returns The property value or null if not found
-     */
-    getProperty: (properties: any, key: string): any =>
-        hostFn("getProperty")(properties, key),
-
-    /**
-     * Set a string property
-     * @param properties The entity properties object
-     * @param key The property key
-     * @param value The string value
-     * @returns Updated properties object
-     */
-    setPropertyString: (properties: any, key: string, value: string): any =>
-        hostFn("setPropertyString")(properties, key, value),
-
-    /**
-     * Set an integer property
-     * @param properties The entity properties object
-     * @param key The property key
-     * @param value The integer value
-     * @returns Updated properties object
-     */
-    setPropertyInt: (properties: any, key: string, value: number): any =>
-        hostFn("setPropertyInt")(properties, key, value),
-
-    /**
-     * Set a float property
-     * @param properties The entity properties object
-     * @param key The property key
-     * @param value The float value
-     * @returns Updated properties object
-     */
-    setPropertyFloat: (properties: any, key: string, value: number): any =>
-        hostFn("setPropertyFloat")(properties, key, value),
-
-    /**
-     * Set a boolean property
-     * @param properties The entity properties object
-     * @param key The property key
-     * @param value The boolean value
-     * @returns Updated properties object
-     */
-    setPropertyBool: (properties: any, key: string, value: boolean): any =>
-        hostFn("setPropertyBool")(properties, key, value),
-
-    /**
-     * Set a Vec3 property
-     * @param properties The entity properties object
-     * @param key The property key
-     * @param value The Vec3 value as [x, y, z] array
-     * @returns Updated properties object
-     */
-    setPropertyVec3: (properties: any, key: string, value: [number, number, number]): any =>
-        hostFn("setPropertyVec3")(properties, key, value),
-
-    /**
-     * Get a string property
-     * @param properties The entity properties object
-     * @param key The property key
-     * @returns The string value or empty string if not found
-     */
-    getString: (properties: any, key: string): string =>
-        hostFn("getString")(properties, key),
-
-    /**
-     * Get an integer property
-     * @param properties The entity properties object
-     * @param key The property key
-     * @returns The integer value or 0 if not found
-     */
-    getInt: (properties: any, key: string): number =>
-        hostFn("getInt")(properties, key),
-
-    /**
-     * Get a float property
-     * @param properties The entity properties object
-     * @param key The property key
-     * @returns The float value or 0.0 if not found
-     */
-    getFloat: (properties: any, key: string): number =>
-        hostFn("getFloat")(properties, key),
-
-    /**
-     * Get a boolean property
-     * @param properties The entity properties object
-     * @param key The property key
-     * @returns The boolean value or false if not found
-     */
-    getBool: (properties: any, key: string): boolean =>
-        hostFn("getBool")(properties, key),
-
-    /**
-     * Get a Vec3 property
-     * @param properties The entity properties object
-     * @param key The property key
-     * @returns The Vec3 value as [x, y, z] array or [0, 0, 0] if not found
-     */
-    getVec3: (properties: any, key: string): [number, number, number] =>
-        hostFn("getVec3")(properties, key),
-
-    /**
-     * Check if a property exists
-     * @param properties The entity properties object
-     * @param key The property key
-     * @returns True if the property exists, false otherwise
-     */
-    hasProperty: (properties: any, key: string): boolean =>
-        hostFn("hasProperty")(properties, key),
-};
-
-export { Transform, Vec3, Quaternion };
+export class EntityProperties {
+    private data: Record<string, any>;
+
+    public constructor(data?: Record<string, any>) {
+        this.data = data || {};
+    }
+
+    public getRawProperties(): Record<string, any> {
+        return this.data;
+    }
+
+    public setString(key: string, value: string): void {
+        this.data[key] = value;
+    }
+
+    public setNumber(key: string, value: number): void {
+        this.data[key] = value;
+    }
+
+    public setBool(key: string, value: boolean): void {
+        this.data[key] = value;
+    }
+
+    public getString(key: string): string {
+        return this.data[key] || "";
+    }
+
+    public getNumber(key: string): number {
+        return this.data[key] || 0;
+    }
+
+    public getBool(key: string): boolean {
+        return this.data[key] || false;
+    }
+
+    public hasProperty(key: string): boolean {
+        return key in this.data;
+    }
+}
+
+export class Entity {
+    public transform: Transform;
+    public properties: EntityProperties;
+    private inputData: InputData | null = null;
+
+    constructor(entityData?: ScriptEntityData) {
+        if (entityData) {
+            this.transform = createTransformFromData(entityData.transform);
+            this.properties = new EntityProperties(entityData.entity.custom_properties);
+            this.inputData = entityData.input;
+        } else {
+            this.transform = new Transform();
+            this.properties = new EntityProperties({});
+        }
+    }
+
+    // Input helpers
+    isKeyPressed(key: KeyCode): boolean {
+        if (!this.inputData) return false;
+        return this.inputData.pressed_keys.includes(key);
+    }
+
+    getMousePosition(): [number, number] {
+        return this.inputData?.mouse_pos || [0, 0];
+    }
+
+    getMouseDelta(): [number, number] | null {
+        return this.inputData?.mouse_delta || null;
+    }
+
+    // Movement helpers
+    moveForward(distance: number): void {
+        const movement = new Vector3(0, 0, -distance);
+        this.transform.translate(movement);
+    }
+
+    moveRight(distance: number): void {
+        const movement = new Vector3(distance, 0, 0);
+        this.transform.translate(movement);
+    }
+
+    moveUp(distance: number): void {
+        const movement = new Vector3(0, distance, 0);
+        this.transform.translate(movement);
+    }
+
+    // Convert back to data format for Rust
+    toEntityData(): Partial<ScriptEntityData> {
+        return {
+            transform: {
+                position: this.transform.position.as_array(),
+                rotation: this.transform.rotation.as_array(),
+                scale: this.transform.scale.as_array()
+            },
+            entity: {
+                custom_properties: this.properties.getRawProperties()
+            }
+        };
+    }
+}
+
+// Type definitions
+export interface TransformData {
+    position: [number, number, number];
+    rotation: [number, number, number, number]; // quaternion [x, y, z, w]
+    scale: [number, number, number];
+}
+
+export interface EntityData {
+    custom_properties: Record<string, any>;
+}
+
+export interface InputData {
+    mouse_pos: [number, number];
+    pressed_keys: string[];
+    mouse_delta: [number, number] | null;
+    is_cursor_locked: boolean;
+}
+
+export interface ScriptEntityData {
+    transform: TransformData;
+    entity: EntityData;
+    input: InputData;
+}
+
+export function createTransformFromData(data: TransformData): Transform {
+    const position = Vector3.fromArray(data.position);
+    const rotation = Quaternion.fromArray(data.rotation);
+    const scale = Vector3.fromArray(data.scale);
+    return new Transform(position, rotation, scale);
+}
 
+// Global exports
 globalThis.Transform = Transform;
-globalThis.Vec3 = Vec3;
+globalThis.Vector3 = Vector3;
 globalThis.Quaternion = Quaternion;
-globalThis.Input = Input;
 globalThis.Keys = Keys;
-globalThis.EntityProperties = EntityProperties;
\ No newline at end of file
+globalThis.EntityProperties = EntityProperties;
+globalThis.Entity = Entity;
\ No newline at end of file
diff --git a/eucalyptus/src/editor/component.rs b/eucalyptus/src/editor/component.rs
index 4c5aa30..760311d 100644
--- a/eucalyptus/src/editor/component.rs
+++ b/eucalyptus/src/editor/component.rs
@@ -441,6 +441,17 @@ impl Component for ScriptComponent {
                                 .set_file_name(format!("{}_script.ts", label))
                                 .save_file()
                             {
+                                // check if dropbear module exists
+                                // todo: change this to an %APPDATA% file instead of to memory. 
+                                let lib_path = &script_path.clone().parent().unwrap().join("dropbear.ts");
+                                if let Err(_) = std::fs::read(lib_path) {
+                                    log::warn!("dropbear.ts library does not exist in project source directory, copying...");
+                                    if let Err(e) = std::fs::write(lib_path, include_str!("../dropbear.ts")) {
+                                        log::error!("Non-fatal error: Creating library file failed: {}", e);
+                                    } else {
+                                        log::info!("Wrote dropbear.ts library file!");
+                                    }
+                                };
                                 match std::fs::write(&script_path, TEMPLATE_SCRIPT) {
                                     Ok(_) => {
                                         let script_name = script_path
diff --git a/eucalyptus/src/editor/dock.rs b/eucalyptus/src/editor/dock.rs
index 1c0c4df..aad4542 100644
--- a/eucalyptus/src/editor/dock.rs
+++ b/eucalyptus/src/editor/dock.rs
@@ -2,7 +2,7 @@ use super::*;
 use std::{collections::HashSet, sync::LazyLock};
 
 use dropbear_engine::{entity::Transform, lighting::{Light, LightComponent}};
-use egui;
+use egui::{self, CollapsingHeader};
 use egui_dock_fork::TabViewer;
 use egui_extras;
 use log;
@@ -649,11 +649,13 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                     if let Ok((e, transform, _props, script)) = self
                     .world
                     .query_one_mut::<(&mut AdoptedEntity, Option<&mut Transform>, Option<&ModelProperties>, Option<&mut ScriptComponent>)>(*entity) {
-                        // let label = e.label().clone();
+                        let label = e.label().clone();
 
                         e.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, &mut String::new());
+                        let mut trans = Transform::new();
                         if let Some(t) = transform {
                             t.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, e.label_mut());
+                            trans = t.clone();
                         }
 
                         // if let Some(props) = _props {
@@ -664,114 +666,116 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                             script.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, e.label_mut());
                         }
 
+                        // ==============================================================
                         // todo: convert camera into component
-                        // let entity_id_copy = *entity;
-                        // let entity_label = label.clone();
-                        // let entity_position = transform.position;
-                        // let camera_manager = &self.camera_manager;
-                        // let signal = &mut *self.signal;
-                        // let get_player_camera_target =
-                        //     camera_manager.get_player_camera_target();
-                        // let get_player_camera_offset =
-                        //     camera_manager.get_player_camera_offset();
-                        // let get_active_type = camera_manager.get_active_type();
-                        // let get_active_eye = camera_manager.get_active().unwrap().eye;
-                        //
-                        // let followed_entity_label = if let Some(target_entity) =
-                        //     get_player_camera_target
-                        // {
-                        //     if target_entity != entity_id_copy {
-                        //         if let Ok((followed_entity, _, _)) = self.world
-                        //             .query_one_mut::<(&AdoptedEntity, &Transform, &ModelProperties)>(target_entity)
-                        //         {
-                        //             Some(followed_entity.label().to_string())
-                        //         } else {
-                        //             None
-                        //         }
-                        //     } else {
-                        //         None
-                        //     }
-                        // } else {
-                        //     None
-                        // };
-
-                        // ui.group(|ui| {
-                        //     CollapsingHeader::new("Camera")
-                        //     .default_open(true)
-                        //     .show(ui, |ui| {
-                        //         ui.horizontal(|ui| {
-                        //             if ui.button("Capture Camera Position relative to Entity").clicked() {
-                        //                 let current_camera_pos = get_active_eye;
-                        //                 let calculated_offset = current_camera_pos - entity_position;
-                        //                 log::debug!("Capturing camera offset: entity at {:?}, camera at {:?}, offset: {:?}",
-                        //                     entity_position, current_camera_pos, calculated_offset);
-                        //                 *signal = Signal::CameraAction(CameraAction::SetPlayerTarget {
-                        //                     entity: entity_id_copy,
-                        //                     offset: calculated_offset,
-                        //                 });
-                        //                 crate::success_without_console!("Camera successfully attached to {}", entity_label);
-                        //             }
-                        //         });
-                        //         ui.separator();
-                        //         ui.horizontal(|ui| {
-                        //             ui.label("Status:");
-                        //             let status_text = match get_active_type {
-                        //                 crate::camera::CameraType::Debug => {
-                        //                     egui::RichText::new("Debug Camera (Free)")
-                        //                         .color(egui::Color32::LIGHT_BLUE)
-                        //                 },
-                        //                 crate::camera::CameraType::Player => {
-                        //                     if let Some(target_entity) = get_player_camera_target {
-                        //                         if target_entity == entity_id_copy {
-                        //                             egui::RichText::new("Following THIS Entity")
-                        //                                 .color(egui::Color32::LIGHT_GREEN)
-                        //                                 .strong()
-                        //                         } else {
-                        //                             if let Some(followed_label) = &followed_entity_label {
-                        //                                 egui::RichText::new(format!("Following: {}", followed_label))
-                        //                                     .color(egui::Color32::YELLOW)
-                        //                             } else {
-                        //                                 egui::RichText::new("Following: Unknown Entity")
-                        //                                     .color(egui::Color32::RED)
-                        //                             }
-                        //                         }
-                        //                     } else {
-                        //                         egui::RichText::new("Player Camera (Free)")
-                        //                             .color(egui::Color32::LIGHT_GRAY)
-                        //                     }
-                        //                 }
-                        //             };
-                        //             ui.label(status_text);
-                        //         });
-                        //
-                        //         ui.separator();
-                        //
-                        //         if let Some(target_entity) = get_player_camera_target {
-                        //             if target_entity == entity_id_copy {
-                        //                 ui.horizontal(|ui| {
-                        //                     ui.label("Camera Offset:");
-                        //                     if let Some(offset) = get_player_camera_offset {
-                        //                         ui.label(format!("({:.2}, {:.2}, {:.2})", offset.x, offset.y, offset.z));
-                        //                     } else {
-                        //                         ui.label("Unknown");
-                        //                     }
-                        //                 });
-                        //                 ui.horizontal(|ui| {
-                        //                     ui.label("Distance:");
-                        //                     let camera_pos = get_active_eye;
-                        //                     let distance = (camera_pos - entity_position).length();
-                        //                     ui.label(format!("{:.2} units", distance));
-                        //                 });
-                        //             }
-                        //         }
-                        //
-                        //         ui.horizontal(|ui| {
-                        //             if ui.button("Clear Camera Target").clicked() {
-                        //                 *signal = Signal::CameraAction(CameraAction::ClearPlayerTarget);
-                        //             }
-                        //         });
-                        //     });
-                        // });
+                        let entity_id_copy = *entity;
+                        let entity_label = label.clone();
+                        let entity_position = trans.position;
+                        let camera_manager = &self.camera_manager;
+                        let signal = &mut *self.signal;
+                        let get_player_camera_target =
+                            camera_manager.get_player_camera_target();
+                        let get_player_camera_offset =
+                            camera_manager.get_player_camera_offset();
+                        let get_active_type = camera_manager.get_active_type();
+                        let get_active_eye = camera_manager.get_active().unwrap().eye;
+                        
+                        let followed_entity_label = if let Some(target_entity) =
+                            get_player_camera_target
+                        {
+                            if target_entity != entity_id_copy {
+                                if let Ok((followed_entity, _, _)) = self.world
+                                    .query_one_mut::<(&AdoptedEntity, &Transform, &ModelProperties)>(target_entity)
+                                {
+                                    Some(followed_entity.label().to_string())
+                                } else {
+                                    None
+                                }
+                            } else {
+                                None
+                            }
+                        } else {
+                            None
+                        };
+
+                        ui.group(|ui| {
+                            CollapsingHeader::new("Camera")
+                            .default_open(true)
+                            .show(ui, |ui| {
+                                ui.horizontal(|ui| {
+                                    if ui.button("Capture Camera Position relative to Entity").clicked() {
+                                        let current_camera_pos = get_active_eye;
+                                        let calculated_offset = current_camera_pos - entity_position;
+                                        log::debug!("Capturing camera offset: entity at {:?}, camera at {:?}, offset: {:?}",
+                                            entity_position, current_camera_pos, calculated_offset);
+                                        *signal = Signal::CameraAction(CameraAction::SetPlayerTarget {
+                                            entity: entity_id_copy,
+                                            offset: calculated_offset,
+                                        });
+                                        crate::success_without_console!("Camera successfully attached to {}", entity_label);
+                                    }
+                                });
+                                ui.separator();
+                                ui.horizontal(|ui| {
+                                    ui.label("Status:");
+                                    let status_text = match get_active_type {
+                                        crate::camera::CameraType::Debug => {
+                                            egui::RichText::new("Debug Camera (Free)")
+                                                .color(egui::Color32::LIGHT_BLUE)
+                                        },
+                                        crate::camera::CameraType::Player => {
+                                            if let Some(target_entity) = get_player_camera_target {
+                                                if target_entity == entity_id_copy {
+                                                    egui::RichText::new("Following THIS Entity")
+                                                        .color(egui::Color32::LIGHT_GREEN)
+                                                        .strong()
+                                                } else {
+                                                    if let Some(followed_label) = &followed_entity_label {
+                                                        egui::RichText::new(format!("Following: {}", followed_label))
+                                                            .color(egui::Color32::YELLOW)
+                                                    } else {
+                                                        egui::RichText::new("Following: Unknown Entity")
+                                                            .color(egui::Color32::RED)
+                                                    }
+                                                }
+                                            } else {
+                                                egui::RichText::new("Player Camera (Free)")
+                                                    .color(egui::Color32::LIGHT_GRAY)
+                                            }
+                                        }
+                                    };
+                                    ui.label(status_text);
+                                });
+                        
+                                ui.separator();
+                        
+                                if let Some(target_entity) = get_player_camera_target {
+                                    if target_entity == entity_id_copy {
+                                        ui.horizontal(|ui| {
+                                            ui.label("Camera Offset:");
+                                            if let Some(offset) = get_player_camera_offset {
+                                                ui.label(format!("({:.2}, {:.2}, {:.2})", offset.x, offset.y, offset.z));
+                                            } else {
+                                                ui.label("Unknown");
+                                            }
+                                        });
+                                        ui.horizontal(|ui| {
+                                            ui.label("Distance:");
+                                            let camera_pos = get_active_eye;
+                                            let distance = (camera_pos - entity_position).length();
+                                            ui.label(format!("{:.2} units", distance));
+                                        });
+                                    }
+                                }
+                        
+                                ui.horizontal(|ui| {
+                                    if ui.button("Clear Camera Target").clicked() {
+                                        *signal = Signal::CameraAction(CameraAction::ClearPlayerTarget);
+                                    }
+                                });
+                            });
+                        });
+                        // ==============================================================
 
                         if let Some(t) = cfg.label_last_edit {
                             if t.elapsed() >= Duration::from_millis(500) {
diff --git a/eucalyptus/src/editor/scene.rs b/eucalyptus/src/editor/scene.rs
index 847a228..c174d1d 100644
--- a/eucalyptus/src/editor/scene.rs
+++ b/eucalyptus/src/editor/scene.rs
@@ -97,17 +97,19 @@ impl Scene for Editor {
             }
 
             let mut script_entities = Vec::new();
-            for (entity_id, script) in self.world.query::<&ScriptComponent>().iter() {
+            for (entity_id, script) in self.world.query::<&mut ScriptComponent>().iter() {
+                log_once::debug_once!("Script Entity -> id: {:?}, component: {:?}", entity_id, script);
+                script.name = script.path.file_name().unwrap().to_str().unwrap().to_string();
                 script_entities.push((entity_id, script.name.clone()));
             }
 
             if script_entities.is_empty() {
-                log::warn!("Script entities is empty");
+                log_once::warn_once!("Script entities is empty");
             }
 
             for (entity_id, script_name) in script_entities {
                 if let Err(e) = self.script_manager.update_entity_script(entity_id, &script_name, &mut self.world, &self.input_state, dt) {
-                    log::warn!("Failed to update script '{}' for entity {:?}: {}", script_name, entity_id, e);
+                    log_once::warn_once!("Failed to update script '{}' for entity {:?}: {}", script_name, entity_id, e);
                 }
             }
         }
@@ -380,19 +382,24 @@ impl Scene for Editor {
                     }
 
                     for (entity_id, script) in script_entities {
+                        log::debug!("Initialising entity script [{}] from path: {}", script.name, script.path.display());
                         match self.script_manager.load_script(&script.path) {
                             Ok(script_name) => {
                                 if let Err(e) = self.script_manager.init_entity_script(entity_id, &script_name, &mut self.world, &self.input_state) {
                                     log::warn!("Failed to initialise script '{}' for entity {:?}: {}", script.name, entity_id, e);
+                                    self.signal = Signal::StopPlaying;
+                                } else {
+                                    crate::success_without_console!("You are in play mode now! Press Escape to exit");
+                                    log::info!("You are in play mode now! Press Escape to exit");
                                 }
                             }
                             Err(e) => {
-                                log::warn!("Failed to load script '{}': {}", script.name, e);
+                                // todo: proper error menu
+                                crate::fatal!("Failed to load script '{}': {}", script.name, e);
+                                self.signal = Signal::StopPlaying;
                             }
                         }
                     }
-                    crate::success_without_console!("You are in play mode now! Press Escape to exit");
-                    log::info!("You are in play mode now. Press Escape to exit");
                 } else {
                     crate::fatal!("Unable to build: Player camera not attached to an entity");
                 }
diff --git a/eucalyptus/src/main.rs b/eucalyptus/src/main.rs
index 5464df8..1881547 100644
--- a/eucalyptus/src/main.rs
+++ b/eucalyptus/src/main.rs
@@ -9,9 +9,10 @@ use clap::{Arg, Command};
 use dropbear_engine::{WindowConfiguration, scene};
 use eucalyptus::APP_INFO;
 
-#[tokio::main]
+// #[tokio::main]
 #[cfg(feature = "editor")]
-async fn main() -> anyhow::Result<()> {
+// async 
+fn main() -> anyhow::Result<()> {
     #[cfg(target_os = "android")]
     compile_error!("The `editor` feature is not supported on Android. If you are attempting\
  to use the Eucalyptus editor on Android, please don't. Instead, use the `data-only` feature\
diff --git a/eucalyptus/src/scripting/entity.rs b/eucalyptus/src/scripting/entity.rs
index da73792..73ab529 100644
--- a/eucalyptus/src/scripting/entity.rs
+++ b/eucalyptus/src/scripting/entity.rs
@@ -2,7 +2,6 @@ use crate::states::{ModelProperties, PropertyValue};
 use rustyscript::{serde_json, Runtime};
 use serde::{Deserialize, Serialize};
 
-// Create a serializable version of ModelProperties for script communication
 #[derive(Clone, Serialize, Deserialize)]
 pub struct SerializableModelProperties {
     pub properties: std::collections::HashMap<String, serde_json::Value>,
@@ -12,7 +11,6 @@ impl From<&ModelProperties> for SerializableModelProperties {
     fn from(props: &ModelProperties) -> Self {
         let mut properties = std::collections::HashMap::new();
         
-        // Convert each property to JSON value
         for (key, value) in props.custom_properties.iter() {
             let json_value = match value {
                 PropertyValue::String(s) => serde_json::Value::String(s.clone()),
diff --git a/eucalyptus/src/scripting/input.rs b/eucalyptus/src/scripting/input.rs
index f60b1bb..0f4de63 100644
--- a/eucalyptus/src/scripting/input.rs
+++ b/eucalyptus/src/scripting/input.rs
@@ -17,11 +17,10 @@ pub struct InputState {
     pub is_cursor_locked: bool,
 }
 
-// Create a serializable version for script communication
 #[derive(Clone, Serialize, Deserialize)]
 pub struct SerializableInputState {
     pub mouse_pos: (f64, f64),
-    pub pressed_keys: Vec<String>, // Convert KeyCode to strings
+    pub pressed_keys: Vec<String>,
     pub mouse_delta: Option<(f64, f64)>,
     pub is_cursor_locked: bool,
 }
@@ -58,14 +57,11 @@ impl InputState {
         }
     }
 
-    /// Locks the cursor. If its true, it locks the cursor, hiding the mouse and setting the value
-    /// or is_cursor_locked true, which can be used for mouse movement in the scene. 
     pub fn lock_cursor(&mut self, toggle: bool) {
         self.is_cursor_locked = toggle;
     }
 
     pub fn register_input_modules(runtime: &mut Runtime) -> anyhow::Result<()> {
-        // Register input functions that take SerializableInputState as parameter
         runtime.register_function("isKeyPressed", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
             if args.len() != 2 {
                 return Err(rustyscript::Error::Runtime("isKeyPressed requires 2 arguments (inputState, keyCode)".to_string()));
@@ -132,7 +128,7 @@ impl InputState {
                 return Err(rustyscript::Error::Runtime("lockCursor requires 1 argument (locked)".to_string()));
             }
             
-            let locked = args[0].as_bool()
+            let _locked = args[0].as_bool()
                 .ok_or_else(|| rustyscript::Error::Runtime("Locked parameter must be a boolean".to_string()))?;
             
             // This function would need to communicate back to the engine to actually lock the cursor
@@ -145,7 +141,8 @@ impl InputState {
     }
 }
 
-// Helper function to convert string to KeyCode
+/// Helper function to convert string to KeyCode
+#[allow(dead_code)]
 fn string_to_keycode(key_str: &str) -> Option<KeyCode> {
     match key_str {
         "KeyA" => Some(KeyCode::KeyA),
@@ -214,7 +211,7 @@ fn string_to_keycode(key_str: &str) -> Option<KeyCode> {
     }
 }
 
-// Helper function to convert KeyCode to string
+/// Helper function to convert KeyCode to string
 fn keycode_to_string(key_code: KeyCode) -> Option<String> {
     match key_code {
         KeyCode::KeyA => Some("KeyA".to_string()),
diff --git a/eucalyptus/src/scripting/math.rs b/eucalyptus/src/scripting/math.rs
index 150477e..83f5731 100644
--- a/eucalyptus/src/scripting/math.rs
+++ b/eucalyptus/src/scripting/math.rs
@@ -4,7 +4,6 @@ use rustyscript::{serde_json, Runtime};
 
 pub fn register_math_functions(runtime: &mut Runtime) -> anyhow::Result<()> {
     
-    // Transform functions
     runtime.register_function("createTransform", |_args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
         let transform = Transform::new();
         serde_json::to_value(transform)
@@ -19,7 +18,6 @@ pub fn register_math_functions(runtime: &mut Runtime) -> anyhow::Result<()> {
         let mut transform: Transform = serde_json::from_value(args[0].clone())
             .map_err(|e| rustyscript::Error::Runtime(format!("Invalid transform: {}", e)))?;
         
-        // Handle both array and individual values for translation
         let translation = if let Some(array) = args[1].as_array() {
             if array.len() != 3 {
                 return Err(rustyscript::Error::Runtime("Translation array must have 3 elements".to_string()));
@@ -114,6 +112,7 @@ pub fn register_math_functions(runtime: &mut Runtime) -> anyhow::Result<()> {
             .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Transform: {}", e)))
     })?;
 
+    // this shouldn't be here as there is no need for a matrix...
     runtime.register_function("transformMatrix", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
         if args.len() != 1 {
             return Err(rustyscript::Error::Runtime("transformMatrix requires 1 argument".to_string()));
@@ -122,14 +121,11 @@ pub fn register_math_functions(runtime: &mut Runtime) -> anyhow::Result<()> {
         let transform: Transform = serde_json::from_value(args[0].clone())
             .map_err(|e| rustyscript::Error::Runtime(format!("Invalid transform: {}", e)))?;
         
-        // Assuming Transform has a matrix() method that returns a 4x4 matrix
-        // You may need to adjust this based on your actual Transform implementation
         let matrix = transform.matrix();
         serde_json::to_value(matrix)
             .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize matrix: {}", e)))
     })?;
 
-    // Vec3 functions
     runtime.register_function("createVec3", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
         let x = args.get(0).and_then(|v| v.as_f64()).unwrap_or(0.0);
         let y = args.get(1).and_then(|v| v.as_f64()).unwrap_or(0.0);
@@ -140,7 +136,6 @@ pub fn register_math_functions(runtime: &mut Runtime) -> anyhow::Result<()> {
             .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Vec3: {}", e)))
     })?;
 
-    // Quaternion functions
     runtime.register_function("createQuatIdentity", |_args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> {
         let quat = DQuat::IDENTITY;
         serde_json::to_value(quat)
diff --git a/eucalyptus/src/scripting/mod.rs b/eucalyptus/src/scripting/mod.rs
index 9a5a8f5..0ee10c9 100644
--- a/eucalyptus/src/scripting/mod.rs
+++ b/eucalyptus/src/scripting/mod.rs
@@ -165,6 +165,11 @@ impl ScriptManager {
     pub fn new() -> anyhow::Result<Self> {
         let mut runtime = Runtime::new(RuntimeOptions::default())?;
 
+        let dropbear_content = include_str!("../dropbear.ts");
+        let dropbear_module = Module::new("./dropbear.ts", dropbear_content);
+        runtime.load_module(&dropbear_module)?;
+        log::debug!("Loaded dropbear module");
+
         // Register modules
         math::register_math_functions(&mut runtime)?;
         input::InputState::register_input_modules(&mut runtime)?;
@@ -186,7 +191,7 @@ impl ScriptManager {
                 .as_secs_f64();
             Ok(serde_json::Value::Number(serde_json::Number::from_f64(time).unwrap()))
         })?;
-
+        log::debug!("Initialised ScriptManager");
         Ok(Self {
             runtime,
             compiled_scripts: HashMap::new(),
@@ -215,18 +220,18 @@ impl ScriptManager {
             log_once::debug_once!("init_entity_script: '{}' for entity {:?}", script_name, entity_id);
         }
 
-        if let Some(module) = self.compiled_scripts.get(script_name) {
-            // Prepare script data
+        log::debug!("Init Entity Script module name: {}", script_name);
+        if let Some(module) = self.compiled_scripts.get(script_name).cloned() {
             let mut script_data = serde_json::Map::new();
 
-            // Add transform data
+            // transform
             if let Ok(mut transform_query) = world.query_one::<&Transform>(entity_id) {
                 if let Some(transform) = transform_query.get() {
                     script_data.insert("transform".to_string(), serde_json::to_value(transform)?);
                 }
             }
 
-            // Add entity properties
+            // entity props
             if let Ok(mut properties_query) = world.query_one::<&ModelProperties>(entity_id) {
                 if let Some(properties) = properties_query.get() {
                     script_data.insert("entity".to_string(), serde_json::to_value(properties)?);
@@ -239,19 +244,33 @@ impl ScriptManager {
                 script_data.insert("entity".to_string(), serde_json::to_value(&default_props)?);
             }
 
-            // Add input state
+            // input state
             let serializable_input = input::SerializableInputState::from(input_state);
             script_data.insert("input".to_string(), serde_json::to_value(&serializable_input)?);
 
-            // Call init function if it exists - specify return type
-            let args: Vec<serde_json::Value> = Vec::new();
-            if let Ok(_result) = self.runtime.call_function::<serde_json::Value>(Some(module), "load", &args) {
-                log::debug!("Called init for entity {:?}", entity_id);
+            // call onLoad
+            let script_data_value = serde_json::Value::Object(script_data);
+            match self.runtime.call_function::<serde_json::Value>(Some(&module), "onLoad", &vec![script_data_value.clone()]) {
+                Ok(result) => {
+                    log::debug!("Called onLoad for entity {:?}", entity_id);
+                    
+                    let updated_data = if result.is_object() {
+                        result
+                    } else {
+                        // JIC script isn't returning updated entity
+                        script_data_value
+                    };
+                    
+                    self.apply_script_data_to_world(entity_id, &updated_data, world)?;
+                    
+                    self.entity_script_data.insert(entity_id, updated_data);
+                }
+                Err(e) => {
+                    log::warn!("onLoad function not found or failed for entity {:?}: {}", entity_id, e);
+                    self.entity_script_data.insert(entity_id, script_data_value);
+                }
             }
 
-            // Store script data for this entity
-            self.entity_script_data.insert(entity_id, serde_json::Value::Object(script_data));
-
             Ok(())
         } else {
             Err(anyhow::anyhow!("Script '{}' not found", script_name))
@@ -266,50 +285,114 @@ impl ScriptManager {
         input_state: &input::InputState,
         dt: f32,
     ) -> anyhow::Result<()> {
-        if let Some(module) = self.compiled_scripts.get(script_name) {
-            // Update script data
+        log_once::debug_once!("Update entity script name: {}", script_name);
+        if let Some(module) = self.compiled_scripts.get(script_name).cloned() {
             let mut script_data = serde_json::Map::new();
 
-            // Update transform data
+            // transform
             if let Ok(mut transform_query) = world.query_one::<&Transform>(entity_id) {
                 if let Some(transform) = transform_query.get() {
                     script_data.insert("transform".to_string(), serde_json::to_value(transform)?);
                 }
             }
 
-            // Update entity properties
+            // entity props
             if let Ok(mut properties_query) = world.query_one::<&ModelProperties>(entity_id) {
                 if let Some(properties) = properties_query.get() {
                     script_data.insert("entity".to_string(), serde_json::to_value(properties)?);
+                } else {
+                    let default_props = ModelProperties::default();
+                    script_data.insert("entity".to_string(), serde_json::to_value(&default_props)?);
                 }
+            } else {
+                let default_props = ModelProperties::default();
+                script_data.insert("entity".to_string(), serde_json::to_value(&default_props)?);
             }
 
-            // Update input state
+            // input state
             let serializable_input = input::SerializableInputState::from(input_state);
             script_data.insert("input".to_string(), serde_json::to_value(&serializable_input)?);
 
-            // Call update function if it exists - specify return type and fix parameter passing
+            let script_data_value = serde_json::Value::Object(script_data);
             let dt_value = serde_json::Value::Number(serde_json::Number::from_f64(dt as f64).unwrap());
-            match self.runtime.call_function::<serde_json::Value>(Some(module), "update", &vec![dt_value]) {
-                Ok(_result) => {
+            
+            match self.runtime.call_function::<serde_json::Value>(Some(&module), "onUpdate", &vec![script_data_value.clone(), dt_value]) {
+                Ok(result) => {
                     log::trace!("Called update for entity {:?}", entity_id);
-                    // Here you would need to extract any modified data from the script
-                    // and update the world accordingly. This depends on how rustyscript
-                    // handles data exchange between Rust and JS.
+                    
+                    if let Some(result_obj) = result.as_object() {
+                        if let Some(transform_value) = result_obj.get("transform") {
+                            if let Ok(new_transform) = serde_json::from_value::<Transform>(transform_value.clone()) {
+                                if let Ok(mut transform_query) = world.query_one::<&mut Transform>(entity_id) {
+                                    if let Some(transform) = transform_query.get() {
+                                        *transform = new_transform;
+                                        log::trace!("Updated transform for entity {:?}", entity_id);
+                                    }
+                                }
+                            }
+                        }
+
+                        if let Some(entity_value) = result_obj.get("entity") {
+                            if let Ok(new_properties) = serde_json::from_value::<ModelProperties>(entity_value.clone()) {
+                                if let Ok(mut properties_query) = world.query_one::<&mut ModelProperties>(entity_id) {
+                                    if let Some(properties) = properties_query.get() {
+                                        *properties = new_properties;
+                                        log::trace!("Updated properties for entity {:?}", entity_id);
+                                    }
+                                }
+                            }
+                        }
+                    }
                 }
                 Err(e) => {
                     log_once::error_once!("Script execution error for entity {:?}: {}", entity_id, e);
                 }
             }
-
-            // Update stored script data
-            self.entity_script_data.insert(entity_id, serde_json::Value::Object(script_data));
         } else {
             log_once::error_once!("Unable to fetch compiled scripts for entity {:?}. Script Name: {}", entity_id, script_name);
         }
         Ok(())
     }
 
+    fn apply_script_data_to_world(
+        &self,
+        entity_id: hecs::Entity,
+        script_data: &serde_json::Value,
+        world: &mut World,
+    ) -> anyhow::Result<()> {
+        if let Some(data_obj) = script_data.as_object() {
+            // Update transform if it exists in the script data
+            if let Some(transform_value) = data_obj.get("transform") {
+                if let Ok(updated_transform) = serde_json::from_value::<Transform>(transform_value.clone()) {
+                    if let Ok(mut transform_query) = world.query_one::<&mut Transform>(entity_id) {
+                        if let Some(transform) = transform_query.get() {
+                            *transform = updated_transform;
+                            log::trace!("Updated transform for entity {:?}", entity_id);
+                        }
+                    }
+                }
+            }
+
+            if let Some(entity_value) = data_obj.get("entity") {
+                if let Ok(updated_properties) = serde_json::from_value::<ModelProperties>(entity_value.clone()) {
+                    if let Ok(mut properties_query) = world.query_one::<&mut ModelProperties>(entity_id) {
+                        if let Some(properties) = properties_query.get() {
+                            *properties = updated_properties;
+                            log::trace!("Updated properties for entity {:?}", entity_id);
+                        }
+                    } else {
+                        if let Err(e) = world.insert_one(entity_id, updated_properties) {
+                            log::warn!("Failed to insert updated properties for entity {:?}: {}", entity_id, e);
+                        }
+                    }
+                }
+            }
+            // input state doesn't get updated, it is only read. 
+        }
+
+        Ok(())
+    }
+
     pub fn load_script_from_source(&mut self, script_name: &String, script_content: &String) -> anyhow::Result<String> {
         let module = Module::new(&script_name, script_content);
         
@@ -327,15 +410,20 @@ impl ScriptManager {
     }
 
     pub fn load_script(&mut self, script_path: &PathBuf) -> anyhow::Result<String> {
+        log::debug!("Reading script content");
         let script_content = fs::read_to_string(script_path)?;
+        log::debug!("Fetching script name");
         let script_name = script_path
-            .file_stem()
+            .file_name()
             .unwrap_or_default()
             .to_string_lossy()
             .to_string();
+        log::debug!("Script name: {}", script_name);
 
+        log::debug!("Creating module for typescript runtime");
         let module = Module::new(&script_name, &script_content);
         
+        log::debug!("Loading module");
         match self.runtime.load_module(&module) {
             Ok(module) => {
                 self.compiled_scripts.insert(script_name.clone(), module);
diff --git a/eucalyptus/src/states.rs b/eucalyptus/src/states.rs
index 07c39f4..431085f 100644
--- a/eucalyptus/src/states.rs
+++ b/eucalyptus/src/states.rs
@@ -614,12 +614,12 @@ pub struct SceneEntity {
     pub entity_id: Option<hecs::Entity>,
 }
 
-#[derive(Debug, Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
 pub struct ModelProperties {
     pub custom_properties: HashMap<String, PropertyValue>,
 }
 
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 pub enum PropertyValue {
     String(String),
     Int(i64),
@@ -739,7 +739,7 @@ impl SceneConfig {
         );
         world.clear();
 
-        let project_config = if !cfg!(feature = "data-only") {
+        let _project_config = if !cfg!(feature = "data-only") {
             if let Ok(cfg) = PROJECT.read() {
                 cfg.project_path.clone()
             } else {
@@ -940,26 +940,64 @@ impl SceneConfig {
                         if adopted_entity.label() == target_label {
                             Some(entity_id)
                         } else {
-                            let stem_match = if cfg!(feature = "data-only") {
-                                  adopted_entity.model().path.to_executable_path().unwrap()
-                            } else {
+                            // Try to match by file stem if label doesn't match
+                            #[cfg(not(feature = "data-only"))]
+                            {
                                 let project_path = if let Ok(cfg) = PROJECT.read() {
                                     cfg.project_path.clone()
                                 } else {
                                     panic!("Unable to get project path to use with camera manager");
                                 };
-                                adopted_entity.model().path.to_project_path(project_path).unwrap()
-                            };
-
-                            let stem_match = stem_match.file_stem()
-                                .and_then(|s| s.to_str())
-                                .map(|s| s == target_label)
-                                .unwrap_or(false);
-
-                            if stem_match {
-                                Some(entity_id)
-                            } else {
-                                None
+                                
+                                match &adopted_entity.model().path.ref_type {
+                                    ResourceReferenceType::File(_reference) => {
+                                        if let Some(path) = adopted_entity.model().path.to_project_path(project_path) {
+                                            let stem_match = path.file_stem()
+                                                .and_then(|s| s.to_str())
+                                                .map(|s| s == target_label)
+                                                .unwrap_or(false);
+                                            
+                                            if stem_match {
+                                                Some(entity_id)
+                                            } else {
+                                                None
+                                            }
+                                        } else {
+                                            None
+                                        }
+                                    },
+                                    ResourceReferenceType::Bytes(_bytes) => {
+                                        // For bytes, we can only match by label, which already failed
+                                        None
+                                    }
+                                    _ => None
+                                }
+                            }
+                            #[cfg(feature = "data-only")]
+                            {
+                                match &adopted_entity.model().path.ref_type {
+                                    ResourceReferenceType::File(_reference) => {
+                                        if let Ok(path) = adopted_entity.model().path.to_executable_path() {
+                                            let stem_match = path.file_stem()
+                                                .and_then(|s| s.to_str())
+                                                .map(|s| s == target_label)
+                                                .unwrap_or(false);
+                                            
+                                            if stem_match {
+                                                Some(entity_id)
+                                            } else {
+                                                None
+                                            }
+                                        } else {
+                                            None
+                                        }
+                                    },
+                                    ResourceReferenceType::Bytes(_bytes) => {
+                                        // For bytes, we can only match by label, which already failed
+                                        None
+                                    }
+                                    _ => None
+                                }
                             }
                         }
                     });
diff --git a/eucalyptus/src/template.ts b/eucalyptus/src/template.ts
index 4670e21..65f8308 100644
--- a/eucalyptus/src/template.ts
+++ b/eucalyptus/src/template.ts
@@ -1,10 +1,25 @@
 // dropbear-engine script template for eucalyptus
-import * as Dropbear from "./dropbear";
+import * as dropbear from "./dropbear.ts";
 
-export function onLoad() {
+export function onLoad(e) {
+    const entity = new dropbear.Entity(e);
+    // ------- Your own code here -------
+    console.log("I have been awoken");
 
+
+    // ----------------------------------
+    // Do not remove anything outside unless
+    // you know what you are doing.
+    return entity.toEntityData();
 }
 
-export function onUpdate(dt: Number) {
-    
+export function onUpdate(e, dt: number) {
+    const entity = new dropbear.Entity(e);
+    // ------- Your own code here -------
+    console.log("I'm being updated!");
+
+
+    // ----------------------------------
+    // Same thing over here!
+    return entity.toEntityData();
 }