kitgit

tirbofish/dropbear · commit

a22b80279c050ddb8034cc23847bacea06fd38a8

Merge pull request #50 from 4tkbytes/sockets pr: more work on ffi

Unverified

tk <4tkbytes@pm.me> · 2025-10-09 12:20

view full diff

diff --git a/.env-example b/.env-example
new file mode 100644
index 0000000..147c983
--- /dev/null
+++ b/.env-example
@@ -0,0 +1,2 @@
+GITHUB_USERNAME=your-github-username
+GITHUB_TOKEN=your-personal-access-token
\ No newline at end of file
diff --git a/.github/workflows/dokka_docs.yaml b/.github/workflows/dokka_docs.yaml
new file mode 100644
index 0000000..797d759
--- /dev/null
+++ b/.github/workflows/dokka_docs.yaml
@@ -0,0 +1,39 @@
+name: Deploy Documentation
+
+on:
+  push:
+    branches:
+      - main
+  workflow_dispatch:
+
+permissions:
+  contents: write
+
+jobs:
+  deploy-docs:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Set up JDK 21
+        uses: actions/setup-java@v4
+        with:
+          java-version: '21'
+          distribution: 'temurin'
+          cache: 'gradle'
+
+      - name: Grant execute permission for gradlew
+        run: chmod +x gradlew
+
+      - name: Generate documentation
+        run: ./gradlew dokkaHtml
+
+      - name: Deploy to GitHub Pages
+        uses: peaceiris/actions-gh-pages@v3
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          publish_dir: ./build/dokka/html
+          publish_branch: gh-pages
+          commit_message: 'Deploy documentation from ${{ github.sha }}'
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 1f67367..ffc1a5a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,4 +51,8 @@ Cargo.lock
 **/*.rs.bk
 *.pdb
 
-.idea/
\ No newline at end of file
+.idea/
+
+libs/
+
+.env
\ No newline at end of file
diff --git a/.run/Build eucalyptus-editor.run.xml b/.run/Build eucalyptus-editor.run.xml
new file mode 100644
index 0000000..72b14db
--- /dev/null
+++ b/.run/Build eucalyptus-editor.run.xml
@@ -0,0 +1,20 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Build eucalyptus-editor" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
+    <option name="buildProfileId" value="dev" />
+    <option name="command" value="build" />
+    <option name="workingDirectory" value="file://$PROJECT_DIR$" />
+    <envs />
+    <option name="emulateTerminal" value="true" />
+    <option name="channel" value="DEFAULT" />
+    <option name="requiredFeatures" value="true" />
+    <option name="allFeatures" value="false" />
+    <option name="withSudo" value="false" />
+    <option name="buildTarget" value="REMOTE" />
+    <option name="backtrace" value="SHORT" />
+    <option name="isRedirectInput" value="false" />
+    <option name="redirectInputPath" value="" />
+    <method v="2">
+      <option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
+    </method>
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/.run/Build gradle.run.xml b/.run/Build gradle.run.xml
new file mode 100644
index 0000000..87c40f0
--- /dev/null
+++ b/.run/Build gradle.run.xml
@@ -0,0 +1,24 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Build gradle" type="GradleRunConfiguration" factoryName="Gradle">
+    <ExternalSystemSettings>
+      <option name="executionName" />
+      <option name="externalProjectPath" value="$PROJECT_DIR$" />
+      <option name="externalSystemIdString" value="GRADLE" />
+      <option name="scriptParameters" value="" />
+      <option name="taskDescriptions">
+        <list />
+      </option>
+      <option name="taskNames">
+        <list>
+          <option value="build" />
+        </list>
+      </option>
+      <option name="vmOptions" />
+    </ExternalSystemSettings>
+    <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
+    <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
+    <DebugAllEnabled>false</DebugAllEnabled>
+    <RunAsTest>false</RunAsTest>
+    <method v="2" />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/.run/Build literally everything.run.xml b/.run/Build literally everything.run.xml
new file mode 100644
index 0000000..bf24eb2
--- /dev/null
+++ b/.run/Build literally everything.run.xml
@@ -0,0 +1,26 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Build literally everything" type="GradleRunConfiguration" factoryName="Gradle">
+    <ExternalSystemSettings>
+      <option name="executionName" />
+      <option name="externalProjectPath" value="$PROJECT_DIR$" />
+      <option name="externalSystemIdString" value="GRADLE" />
+      <option name="scriptParameters" value="" />
+      <option name="taskDescriptions">
+        <list />
+      </option>
+      <option name="taskNames">
+        <list>
+          <option value="build" />
+        </list>
+      </option>
+      <option name="vmOptions" />
+    </ExternalSystemSettings>
+    <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
+    <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
+    <DebugAllEnabled>false</DebugAllEnabled>
+    <RunAsTest>false</RunAsTest>
+    <method v="2">
+      <option name="RunConfigurationTask" enabled="true" run_configuration_name="Build eucalyptus-editor" run_configuration_type="CargoCommandRunConfiguration" />
+    </method>
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/.run/Clean literally everything.run.xml b/.run/Clean literally everything.run.xml
new file mode 100644
index 0000000..aa395a9
--- /dev/null
+++ b/.run/Clean literally everything.run.xml
@@ -0,0 +1,20 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Clean literally everything" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
+    <option name="command" value="clean" />
+    <option name="workingDirectory" value="file://$PROJECT_DIR$" />
+    <envs />
+    <option name="emulateTerminal" value="true" />
+    <option name="channel" value="DEFAULT" />
+    <option name="requiredFeatures" value="true" />
+    <option name="allFeatures" value="false" />
+    <option name="withSudo" value="false" />
+    <option name="buildTarget" value="REMOTE" />
+    <option name="backtrace" value="SHORT" />
+    <option name="isRedirectInput" value="false" />
+    <option name="redirectInputPath" value="" />
+    <method v="2">
+      <option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
+      <option name="Gradle.BeforeRunTask" enabled="true" tasks="clean" externalProjectPath="$PROJECT_DIR$" vmOptions="" scriptParameters="" />
+    </method>
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/.run/Publish to GitHub Packages.run.xml b/.run/Publish to GitHub Packages.run.xml
new file mode 100644
index 0000000..3d068c8
--- /dev/null
+++ b/.run/Publish to GitHub Packages.run.xml
@@ -0,0 +1,24 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Publish to GitHub Packages" type="GradleRunConfiguration" factoryName="Gradle">
+    <ExternalSystemSettings>
+      <option name="executionName" />
+      <option name="externalProjectPath" value="$PROJECT_DIR$" />
+      <option name="externalSystemIdString" value="GRADLE" />
+      <option name="scriptParameters" value="" />
+      <option name="taskDescriptions">
+        <list />
+      </option>
+      <option name="taskNames">
+        <list>
+          <option value="publish" />
+        </list>
+      </option>
+      <option name="vmOptions" />
+    </ExternalSystemSettings>
+    <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
+    <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
+    <DebugAllEnabled>false</DebugAllEnabled>
+    <RunAsTest>false</RunAsTest>
+    <method v="2" />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/.run/Publish to Maven Local.run.xml b/.run/Publish to Maven Local.run.xml
new file mode 100644
index 0000000..c6aec2a
--- /dev/null
+++ b/.run/Publish to Maven Local.run.xml
@@ -0,0 +1,24 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Publish to Maven Local" type="GradleRunConfiguration" factoryName="Gradle">
+    <ExternalSystemSettings>
+      <option name="executionName" />
+      <option name="externalProjectPath" value="$PROJECT_DIR$" />
+      <option name="externalSystemIdString" value="GRADLE" />
+      <option name="scriptParameters" value="" />
+      <option name="taskDescriptions">
+        <list />
+      </option>
+      <option name="taskNames">
+        <list>
+          <option value="publishToMavenLocal" />
+        </list>
+      </option>
+      <option name="vmOptions" />
+    </ExternalSystemSettings>
+    <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
+    <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
+    <DebugAllEnabled>false</DebugAllEnabled>
+    <RunAsTest>false</RunAsTest>
+    <method v="2" />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/.run/Run eucalyptus-editor.run.xml b/.run/Run eucalyptus-editor.run.xml
new file mode 100644
index 0000000..7cd481d
--- /dev/null
+++ b/.run/Run eucalyptus-editor.run.xml
@@ -0,0 +1,20 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Run eucalyptus-editor" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
+    <option name="buildProfileId" value="dev" />
+    <option name="command" value="run" />
+    <option name="workingDirectory" value="file://$PROJECT_DIR$" />
+    <envs />
+    <option name="emulateTerminal" value="true" />
+    <option name="channel" value="DEFAULT" />
+    <option name="requiredFeatures" value="true" />
+    <option name="allFeatures" value="false" />
+    <option name="withSudo" value="false" />
+    <option name="buildTarget" value="REMOTE" />
+    <option name="backtrace" value="SHORT" />
+    <option name="isRedirectInput" value="false" />
+    <option name="redirectInputPath" value="" />
+    <method v="2">
+      <option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
+    </method>
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/.run/build.run.xml b/.run/build.run.xml
deleted file mode 100644
index 87e14e1..0000000
--- a/.run/build.run.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<component name="ProjectRunConfigurationManager">
-  <configuration default="false" name="build" type="GradleRunConfiguration" factoryName="Gradle">
-    <ExternalSystemSettings>
-      <option name="executionName" />
-      <option name="externalProjectPath" value="$PROJECT_DIR$" />
-      <option name="externalSystemIdString" value="GRADLE" />
-      <option name="scriptParameters" value="" />
-      <option name="taskDescriptions">
-        <list />
-      </option>
-      <option name="taskNames">
-        <list>
-          <option value="build" />
-        </list>
-      </option>
-      <option name="vmOptions" />
-    </ExternalSystemSettings>
-    <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
-    <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
-    <DebugAllEnabled>false</DebugAllEnabled>
-    <RunAsTest>false</RunAsTest>
-    <method v="2" />
-  </configuration>
-</component>
\ No newline at end of file
diff --git a/.run/eucalyptus-editor.run.xml b/.run/eucalyptus-editor.run.xml
deleted file mode 100644
index 6cec2ad..0000000
--- a/.run/eucalyptus-editor.run.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<component name="ProjectRunConfigurationManager">
-  <configuration default="false" name="eucalyptus-editor" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
-    <option name="buildProfileId" value="dev" />
-    <option name="command" value="run" />
-    <option name="workingDirectory" value="file://$PROJECT_DIR$" />
-    <envs />
-    <option name="emulateTerminal" value="true" />
-    <option name="channel" value="DEFAULT" />
-    <option name="requiredFeatures" value="true" />
-    <option name="allFeatures" value="false" />
-    <option name="withSudo" value="false" />
-    <option name="buildTarget" value="REMOTE" />
-    <option name="backtrace" value="SHORT" />
-    <option name="isRedirectInput" value="false" />
-    <option name="redirectInputPath" value="" />
-    <method v="2">
-      <option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
-    </method>
-  </configuration>
-</component>
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
index 3a523da..0ac2739 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,7 +6,7 @@ package.repository = "https://github.com/4tkbytes/dropbear"
 package.readme = "README.md"
 
 resolver = "3"
-members = [ "dropbear-engine", "dropbear_future-queue", "eucalyptus-core", "eucalyptus-editor"]
+members = [ "dropbear-engine", "dropbear_future-queue", "eucalyptus-core", "eucalyptus-editor", "magna-carta"]
 # members = ["dropbear-engine", "eucalyptus-core", "eucalyptus-editor", "redback-runtime"]
 
 
@@ -61,6 +61,8 @@ gltf = "1"
 os_info = "3.12"
 rustc_version_runtime = "0.3"
 jni = { version = "0.21", features = ["invocation"] }
+tree-sitter = "0.22"
+tree-sitter-kotlin = "0.3.8"
 
 [workspace.dependencies.image]
 version = "0.25"
diff --git a/README.md b/README.md
index b5d3dd4..dc06b84 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,7 @@ If you might have not realised, all the crates/projects names are after Australi
 
 ### Related Projects
 
+- [magna-carta](https://github.com/4tkbytes/dropbear/tree/main/magna-carta) is a rust library used to generate compile-time Kotlin/Native and Kotlin/JVM metadata for searching. 
 - [dropbear_future-queue](https://github.com/4tkbytes/dropbear/tree/main/dropbear_future-queue) is a handy library for dealing with async in a sync context
 - [model_to_image](https://github.com/4tkbytes/model_to_image) is a library used to generate thumbnails and images from a 3D model with the help of `russimp-ng` and a custom made rasteriser. _(very crude but usable)_
 
@@ -26,7 +27,6 @@ With Unix systems (macOS not tested), you will have to download a couple of depe
 
 <!-- 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.  -->
 
-### Dependencies
 
 ```bash
 # ubuntu
@@ -37,7 +37,7 @@ sudo pacman -Syu base-devel systemd pkgconf openssl clang cmake meson assimp jdk
 
 ```
 
-### Engine Build
+Then run this to build the project
 
 ```bash
 git clone git@github.com:4tkbytes/dropbear
@@ -45,6 +45,8 @@ cd dropbear
 
 # this will build all the projects in the workspace
 cargo build
+# ensure that rust is built before gradlew as rust produces a cdylib which gradlew needs to link to
+./gradlew build
 ```
 
 [//]: # (# ensure submodules are checked-out)
@@ -70,7 +72,14 @@ If you do not want to build it locally, you are able to download the latest acti
 Despite the dropbear-engine (and other components) being made in Rust, the editor has chosen the scripting language of choice to be `Kotlin`
 because of previous experience and that Kotlin is more multiplatform than Swift. 
 
-Java is possible (as the JVM runs the script), however it is not officially supported and Kotlin is recommended. . 
+The dropbear engine uses Kotlin Multiplatform, which allows the cooked up product to be compatible with all platforms 
+KMP can support, which includes mobile, WASM and desktop. Because the editor is only available on desktop, the JVM is 
+used to evaluate the scripts as it allows for hot-reloading (not made yet). 
+
+It is recommended to use IntelliJ IDEA with the Rust plugin to help contribute to the engine. If you are a normal joe, 
+then just use the standard IntelliJ IDEA. 
+
+## Documentation
 
 API documentation and articles are available at (todo)
 
@@ -83,11 +92,11 @@ API documentation and articles are available at (todo)
 
 <sup>1</sup> Will never be implemented; not intended for that platform.
 
-<sup>2</sup>  Made some progress on implementing, but currently a WIP.
+<sup>2</sup> Made some progress on implementing, but currently a WIP.
 
 ## Contributions
 
-Yeah yeah, go ahead and contribute. Make sure it works, and its not spam, and any tests pass.
+Yeah, yeah, go ahead and contribute. Make sure it works, and its not spam, and any tests pass.
 
 # Licensing
 
diff --git a/build.gradle.kts b/build.gradle.kts
index 42b7b28..46e7c09 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,6 +1,8 @@
 plugins {
-    kotlin("jvm") version "2.1.21"
-//    kotlin("multiplatform") version "2.1.21"
+    alias(libs.plugins.kotlinMultiplatform)
+    alias(libs.plugins.kotlinxSerialization)
+    `maven-publish`
+    id("org.jetbrains.dokka") version "2.0.0"
 }
 
 group = "com.dropbear"
@@ -10,21 +12,95 @@ repositories {
     mavenCentral()
 }
 
-dependencies {
-    testImplementation(kotlin("test"))
-    implementation(kotlin("test"))
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath("io.github.cdimascio:dotenv-kotlin:6.4.1")
+    }
 }
 
-tasks.test {
-    useJUnitPlatform()
-}
 kotlin {
-    jvmToolchain(21)
-}
+    jvm()
+
+    val hostOs = System.getProperty("os.name")
+    val isArm64 = System.getProperty("os.arch") == "aarch64"
+    val isMingwX64 = hostOs.startsWith("Windows")
+    val nativeTarget = when {
+        hostOs == "Mac OS X" && isArm64 -> macosArm64("nativeLib")
+        hostOs == "Mac OS X" && !isArm64 -> macosX64("nativeLib")
+        hostOs == "Linux" && isArm64 -> linuxArm64("nativeLib")
+        hostOs == "Linux" && !isArm64 -> linuxX64("nativeLib")
+        isMingwX64 -> mingwX64("nativeLib")
+        else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
+    }
+
+    val libName = when {
+        hostOs == "Mac OS X" -> "libeucalyptus_core.dylib"
+        hostOs == "Linux" -> "libeucalyptus_core.so"
+        isMingwX64 -> "eucalyptus_core.dll"
+        else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
+    }
+
+    if (file("${project.rootDir}/target/debug/$libName").exists()) {
+        println("Debug library exists")
+//        "${project.rootDir}/target/debug/$libName"
+    } else if (file("${project.rootDir}/target/release/$libName").exists()) {
+        println("Release library exists")
+//        "${project.rootDir}/target/debug/$libName"
+    } else if (file("${project.rootDir}/libs/$libName").exists()) {
+        println("Local library exists")
+//        "${project.rootDir}/libs/$libName"
+    } else {
+        throw GradleException("libeucalyptus_core.so does not exist. This is a local build, so most likely you haven't built the rust library yet. \n" +
+                "Try running cargo build")
+    }
+
+    nativeTarget.apply {
+        compilations.getByName("main") {
+            cinterops {
+                val dropbear by creating {
+                    defFile(project.file("src/dropbear.def"))
+                    includeDirs.headerFilterOnly(project.file("headers"))
+                }
+            }
+        }
+        binaries {
+            sharedLib {
+                baseName = "dropbear"
+            }
+        }
+    }
+
+    sourceSets {
+        commonMain {
+            dependencies {
+                api("co.touchlab:kermit:2.0.4")
+            }
+        }
+        nativeMain {
+            dependencies {
+                implementation(libs.kotlinxSerializationJson)
+            }
+        }
+
+        jvmMain {
+            kotlin.srcDirs("src/jvmMain/kotlin", "src/jvmMain/java")
+            dependencies {
+                
+            }
+        }
+    }
 
-sourceSets {
-    main {
-        java.srcDirs("src/main/kotlin", "src/main/java")
+    targets.all {
+        compilations.all {
+            compileTaskProvider.configure {
+                compilerOptions {
+                    freeCompilerArgs.add("-Xexpect-actual-classes")
+                }
+            }
+        }
     }
 }
 
@@ -32,14 +108,40 @@ tasks.register<JavaCompile>("generateJniHeaders") {
     val outputDir = layout.buildDirectory.dir("generated/jni-headers")
     options.headerOutputDirectory.set(outputDir.get().asFile)
 
+    destinationDirectory.set(layout.buildDirectory.dir("classes/java/jni"))
+
     classpath = files(
-        tasks.named("compileKotlin"),
-        tasks.named("compileJava")
+        tasks.named("compileKotlinJvm"),
     )
 
-    source = fileTree("src/main/java") {
+    source = fileTree("src/jvmMain/java") {
         include("**/*.java")
     }
 
-    dependsOn("compileJava", "compileKotlin")
+    dependsOn("compileKotlinJvm")
 }
+
+val dotenv = io.github.cdimascio.dotenv.dotenv()
+
+publishing {
+    repositories {
+        maven {
+            name = "GitHubPackages"
+            url = uri("https://maven.pkg.github.com/4tkbytes/dropbear")
+            credentials {
+                username = dotenv["GITHUB_USERNAME"]
+                password = dotenv["GITHUB_TOKEN"]
+            }
+        }
+    }
+
+    publications {
+        create<MavenPublication>("release") {
+            groupId = group as String?
+            artifactId = rootProject.name
+            version = version
+
+            from(components["kotlin"])
+        }
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/Cargo.toml b/eucalyptus-core/Cargo.toml
index 70c6ad2..9df9909 100644
--- a/eucalyptus-core/Cargo.toml
+++ b/eucalyptus-core/Cargo.toml
@@ -7,7 +7,7 @@ repository.workspace = true
 readme = "README.md"
 
 [lib]
-crate-type = ["cdylib", "rlib"]
+crate-type = ["rlib", "cdylib"]
 
 [dependencies]
 anyhow.workspace = true
@@ -29,11 +29,14 @@ winit.workspace = true
 tokio.workspace = true
 rayon.workspace = true
 jni.workspace = true
-lazy_static = "1.5.0"
 crossbeam-channel = "0.5.15"
+interprocess = "2.2"
+serde_json = "1.0.143"
+libloading = "0.8.9"
 
 [features]
-editor = []
+editor = ["jvm"]
+jvm = []
 
 # [target.'cfg(not(target_os = "android"))'.dependencies]
 # rfd.workspace = true
diff --git a/eucalyptus-core/README.md b/eucalyptus-core/README.md
index 735d9b7..e00bcec 100644
--- a/eucalyptus-core/README.md
+++ b/eucalyptus-core/README.md
@@ -2,4 +2,6 @@
 
 The core libraries of the eucalyptus editor. Great for embedding into `redback-runtime` and `eucalyptus-editor` as one big change instead of a bunch of features.
 
-This is a library, so if tools are wished to be made, this is the perfect library for you.
\ No newline at end of file
+This is a library, so if tools are wished to be made, this is the perfect library for you.
+
+it also produces a shared library for Kotlin/Native and the JVM :)
\ No newline at end of file
diff --git a/eucalyptus-core/build.rs b/eucalyptus-core/build.rs
index 714eb7a..ec0208f 100644
--- a/eucalyptus-core/build.rs
+++ b/eucalyptus-core/build.rs
@@ -1,60 +1,56 @@
-use std::fs::{self, File};
-use std::io::Cursor;
-use std::process::Command;
-
 fn main() -> anyhow::Result<()> {
-    // todo: move this into the "setup" process
+    // // 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] :("
     //     ));
-    // }    
+    // }
 
     // fuck you windows :(
     #[cfg(target_os = "windows")]
diff --git a/eucalyptus-core/src/lib.rs b/eucalyptus-core/src/lib.rs
index 87e2061..c4907b1 100644
--- a/eucalyptus-core/src/lib.rs
+++ b/eucalyptus-core/src/lib.rs
@@ -5,4 +5,5 @@ pub mod logging;
 pub mod scripting;
 pub mod states;
 pub mod utils;
-pub mod spawn;
\ No newline at end of file
+pub mod spawn;
+mod ptr;
diff --git a/eucalyptus-core/src/ptr.rs b/eucalyptus-core/src/ptr.rs
new file mode 100644
index 0000000..c55c5c4
--- /dev/null
+++ b/eucalyptus-core/src/ptr.rs
@@ -0,0 +1,29 @@
+#![allow(dead_code)] // needed because some fields arent accessed
+use std::marker::PhantomData;
+
+/// A clonable Send/Sync pointer. Typically unsafe, but fuck it we ball.
+/// Anything to not deal with Mutex and RwLock amirite???
+#[derive(Clone)]
+pub struct SafePointer<T> {
+    ptr: *const T,
+    _marker: PhantomData<T>
+}
+
+unsafe impl<T> Send for SafePointer<T> where T: Send {}
+
+unsafe impl<T> Sync for SafePointer<T> where T: Sync {}
+
+impl<T> SafePointer<T> {
+    /// Creates a new safe pointer from an unsafe pointer
+    pub fn new(ptr: *const T) -> Self {
+        SafePointer {
+            ptr,
+            _marker: PhantomData,
+        }
+    }
+
+    /// Accesses the [`SafePointer`] as an unsafe pointer
+    pub unsafe fn get(&self) -> *const T {
+        self.ptr
+    }
+}
diff --git a/eucalyptus-core/src/scripting.rs b/eucalyptus-core/src/scripting.rs
index 532f7f4..635b6f5 100644
--- a/eucalyptus-core/src/scripting.rs
+++ b/eucalyptus-core/src/scripting.rs
@@ -1,179 +1,98 @@
-mod java;
+pub mod kmp;
+pub mod jni;
 
 use crate::input::InputState;
 use crate::states::{EntityNode, PROJECT, SOURCE, ScriptComponent, Value};
 use dropbear_engine::entity::{AdoptedEntity, Transform};
 use hecs::{Entity, World};
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
 use std::{collections::HashMap, fs};
-use crate::scripting::java::JavaContext;
+use std::ffi::OsString;
+use std::sync::{Arc, LazyLock};
+use libloading::Library;
+use crate::scripting::jni::JavaContext;
 
 pub const TEMPLATE_SCRIPT: &str = include_str!("../../resources/scripting/kotlin/Template.kt");
 
-#[derive(Clone)]
-pub struct DropbearScriptingAPIContext {
-    pub current_entity: Option<Entity>,
-    // fyi: im pretty sure this is safe because I can just null with [`Option::None`]
-    current_world: Option<*const World>,
-    pub current_input: Option<InputState>,
-    pub persistent_data: HashMap<String, Value>,
-    pub frame_data: HashMap<String, Value>,
-}
-
-impl Default for DropbearScriptingAPIContext {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-impl DropbearScriptingAPIContext {
-    pub fn new() -> Self {
-        Self {
-            current_entity: None,
-            current_world: None,
-            current_input: None,
-            persistent_data: HashMap::new(),
-            frame_data: HashMap::new(),
-        }
-    }
-
-    pub fn set_context(&mut self, entity: Entity, world: &mut World, input: &InputState) {
-        self.current_entity = Some(entity);
-        self.current_world = Some(world as *mut World);
-        self.current_input = Some(input.clone());
-    }
-
-    pub fn clear_context(&mut self) {
-        self.current_entity = None;
-        self.current_world = None;
-        self.current_input = None;
-        self.frame_data.clear();
-    }
-
-    pub fn get_current_entity(&self) -> Option<Entity> {
-        self.current_entity
-    }
-
-    pub fn get_input(&self) -> Option<&InputState> {
-        self.current_input.as_ref()
-    }
-
-    pub fn set_persistent_data(&mut self, key: String, value: Value) {
-        self.persistent_data.insert(key, value);
-    }
-
-    pub fn get_persistent_data(&self, key: &str) -> Option<&Value> {
-        self.persistent_data.get(key)
-    }
-
-    pub fn set_frame_data(&mut self, key: String, value: Value) {
-        self.frame_data.insert(key, value);
-    }
-
-    pub fn get_frame_data(&self, key: &str) -> Option<&Value> {
-        self.frame_data.get(key)
-    }
-
-    pub fn cleanup_entity_data(&mut self, entity: Entity) {
-        let entity_prefix = format!("entity_{:?}_", entity);
-        self.persistent_data
-            .retain(|k, _| !k.starts_with(&entity_prefix));
-    }
-}
-
-/// 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
+#[derive(Default)]
+pub enum ScriptTarget {
+    #[default]
+    None,
+    JVM { library_path: PathBuf },
+    Native { library_path: PathBuf },
 }
 
 pub struct ScriptManager {
-    #[allow(dead_code)]
-    script_context: DropbearScriptingAPIContext,
-    java: JavaContext,
+    jvm: Option<JavaContext>,
+    library: Option<Library>,
+    script_target: ScriptTarget,
+    entity_tag_database: HashMap<String, Vec<Entity>>,
 }
 
 impl ScriptManager {
     pub fn new() -> anyhow::Result<Self> {
-        // let lib_path: PathBuf = Self::look_for_potential_library()?;
-        // let library = unsafe { Library::new(lib_path.clone())? };
-
-        let result = Self {
-            java: JavaContext::new()?,
-            script_context: DropbearScriptingAPIContext::new(),
-        };
-
-        log::debug!("Initialised ScriptManager");
-        Ok(result)
+        Ok(Self {
+            jvm: None,
+            library: None,
+            script_target: Default::default(),
+            entity_tag_database: HashMap::new(),
+        })
     }
 
-    pub fn load_script(
+    pub fn init_script(
         &mut self,
-        script_name: &String,
-        _script_content: String,
-    ) -> anyhow::Result<String> {
-        
-        log::debug!("Loaded library [{}]", script_name);
-        Ok(script_name.clone())
+        entity_tag_database: HashMap<String, Vec<Entity>>,
+        target: ScriptTarget,
+    ) -> anyhow::Result<()> {
+        self.entity_tag_database = entity_tag_database;
+
+        match target {
+            ScriptTarget::JVM { library_path } => {
+                let jvm = JavaContext::new(library_path)?;
+                self.jvm = Some(jvm);
+            }
+            ScriptTarget::Native { library_path } => {
+                let library = unsafe {
+                    Library::new(library_path)?
+                };
+
+                self.library = Some(library);
+            }
+            _ => {
+                anyhow::bail!("Invalid script target, must be either JVM or Native");
+            }
+        }
+
+        Ok(())
     }
 
-    pub fn init_entity_script(
+    pub fn load_script(
         &mut self,
         entity_id: hecs::Entity,
-        script_name: &str,
+        tag: String,
         world: &mut World,
         input_state: &InputState,
     ) -> 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);
+        #[cfg(feature = "jvm")]
+        {
+            if let Some(jvm) = &mut self.jvm {
+                jvm.init(world)?;
+            }
         }
 
-        Ok(())
+        Err(anyhow::anyhow!("it aint ready yet bozo"))
     }
 
-    pub fn update_entity_script(
+    pub fn update_script(
         &mut self,
         entity_id: hecs::Entity,
-        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(())
+        Err(anyhow::anyhow!("it aint ready yet bozo"))
     }
 }
 
@@ -265,8 +184,7 @@ pub fn convert_entity_to_group(
 
             let script_node = if let Ok(script) = world.get::<&ScriptComponent>(entity_id) {
                 Some(EntityNode::Script {
-                    name: script.name.clone(),
-                    path: script.path.clone(),
+                    tags: script.tags.clone(),
                 })
             } else {
                 None
@@ -309,15 +227,15 @@ pub fn attach_script_to_entity(
     Ok(())
 }
 
-pub enum ScriptAction {
-    AttachScript {
-        script_path: PathBuf,
-        script_name: String,
-    },
-    CreateAndAttachScript {
-        script_path: PathBuf,
-        script_name: String,
-    },
-    RemoveScript,
-    EditScript,
-}
+// pub enum ScriptAction {
+//     AttachScript {
+//         script_path: PathBuf,
+//         script_name: String,
+//     },
+//     CreateAndAttachScript {
+//         script_path: PathBuf,
+//         script_name: String,
+//     },
+//     RemoveScript,
+//     EditScript,
+// }
diff --git a/eucalyptus-core/src/scripting/java.rs b/eucalyptus-core/src/scripting/java.rs
deleted file mode 100644
index 38a8a7f..0000000
--- a/eucalyptus-core/src/scripting/java.rs
+++ /dev/null
@@ -1,302 +0,0 @@
-#![allow(non_snake_case)]
-use jni::{InitArgsBuilder, JNIEnv, JNIVersion, JavaVM};
-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 {
-    jvm: JavaVM,
-}
-
-impl JavaContext {
-    /// Creates a new [`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)?;
-        log::info!("Initialised JVM");
-        Ok(Self {
-            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
-}
-
-/// Get mouse X position
-#[unsafe(no_mangle)]
-pub extern "system" fn Java_com_dropbear_ffi_NativeEngine_getMouseX(
-    _env: JNIEnv,
-    _class: JClass,
-) -> jdouble {
-    CURRENT_MOUSE.read().0
-}
-
-/// 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
-}
-
-/// 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,
-    }
-}
-
diff --git a/eucalyptus-core/src/scripting/jni.rs b/eucalyptus-core/src/scripting/jni.rs
new file mode 100644
index 0000000..7fe40e9
--- /dev/null
+++ b/eucalyptus-core/src/scripting/jni.rs
@@ -0,0 +1,89 @@
+#![allow(non_snake_case)]
+//! Deals with the Java Native Interface (JNI) with the help of the [`jni`] crate
+
+use std::path::Path;
+use std::sync::Arc;
+use hecs::World;
+use jni::{InitArgsBuilder, JNIEnv, JNIVersion, JavaVM};
+use jni::objects::{GlobalRef, JClass, JString, JValue};
+use jni::sys::jobject;
+use jni::sys::jlong;
+use dropbear_engine::entity::AdoptedEntity;
+
+pub struct JavaContext {
+    jvm: Arc<JavaVM>,
+    dropbear_engine_class: Option<GlobalRef>,
+}
+
+impl JavaContext {
+    pub fn new(jar_path: impl AsRef<Path>) -> anyhow::Result<Self> {
+        let jvm_args = InitArgsBuilder::new()
+            .version(JNIVersion::V8)
+            .option(format!("-Djava.class.path={}", jar_path.as_ref().display()))
+            .build()?;
+
+        let jvm = Arc::new(JavaVM::new(jvm_args)?);
+
+        log::info!("Created JVM instance");
+
+        Ok(Self {
+            jvm,
+            dropbear_engine_class: None,
+        })
+    }
+
+    pub fn init(&mut self, world: &World) -> anyhow::Result<()> {
+            let mut env = self.jvm.attach_current_thread()?;
+
+            // create native engine first
+            let native_engine_class: JClass = env.find_class("com/dropbear/ffi/NativeEngine")?;
+            let native_engine_obj = env.new_object(native_engine_class, "()V", &[])?;
+
+            let world_ptr = world as *const World;
+
+            let world_handle = world_ptr as jlong;
+            env.call_method(
+                &native_engine_obj,
+                "init",
+                "(J)V",
+                &[JValue::Long(world_handle)],
+            )?;
+
+            // create dropbear engine after
+            let dropbear_class: JClass = env.find_class("com/dropbear/DropbearEngine")?;
+            let dropbear_obj = env.new_object(
+                dropbear_class,
+                "(Lcom/dropbear/ffi/NativeEngine;)V",
+                &[JValue::Object(&native_engine_obj)],
+            )?;
+
+            let global_ref = env.new_global_ref(dropbear_obj)?;
+
+        self.dropbear_engine_class = Some(global_ref);
+
+        if let Some(global_ref) = &self.dropbear_engine_class {
+            env.call_method(&global_ref, "init", "()V", &[])?;
+        }
+
+        Ok(())
+    }
+}
+
+#[unsafe(no_mangle)]
+// JNIEXPORT jlong JNICALL Java_com_dropbear_ffi_JNINative_getEntity
+// (JNIEnv *, jobject, jlong, jstring);
+pub fn Java_com_dropbear_ffi_JNINative_getEntity(env: &mut JNIEnv, _obj: jobject, world_handle: jlong, label: JString) -> jlong {
+    let label = env.get_string(&label).unwrap();
+    let world = world_handle as *mut World;
+
+    let world = unsafe { &mut *world };
+
+    let rust_label = label.to_str().unwrap().to_string();
+
+    for (id, entity) in world.query::<&AdoptedEntity>().iter() {
+        if entity.model.label == rust_label {
+            return jlong::from(id.id())
+        }
+    }
+    jlong::from(-1)
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting/kmp.rs b/eucalyptus-core/src/scripting/kmp.rs
new file mode 100644
index 0000000..0cdd567
--- /dev/null
+++ b/eucalyptus-core/src/scripting/kmp.rs
@@ -0,0 +1,48 @@
+//! Deals with Kotlin/Native library loading for different platforms.
+
+use std::ffi::{c_char, CStr};
+use dropbear_engine::entity::AdoptedEntity;
+
+/// Looks up an entity by its string label in the given world and writes the result to `out_entity`.
+///
+/// # Safety
+///
+/// - `label` must be a valid null-terminated C string.
+/// - `world_ptr` must be a non-null pointer to a valid, initialized `hecs::World`
+///   that is not being mutably aliased (i.e., no concurrent mutable access).
+/// - `out_entity` must be a non-null pointer to a `uint64_t` (`u64`) that the caller owns.
+/// - The `hecs::World` must outlive the duration of this call.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn dropbear_get_entity(
+    label: *const c_char,
+    world_ptr: *const hecs::World,
+    out_entity: *mut u64,
+) -> i32 {
+    if label.is_null() || world_ptr.is_null() || out_entity.is_null() {
+        log::warn!("dropbear_get_entity: received null pointer");
+        return -1;
+    }
+
+    let world = unsafe { &*world_ptr };
+
+    let label_str = unsafe {
+        match CStr::from_ptr(label).to_str() {
+            Ok(s) => s,
+            Err(_) => {
+                log::warn!("dropbear_get_entity: invalid UTF-8 in label");
+                return -1;
+            }
+        }
+    };
+
+    for (id, entity) in world.query::<&AdoptedEntity>().iter() {
+        if entity.model.label == label_str {
+            *out_entity = id.id() as u64;
+            log::debug!("Found entity with label: {:?}", label_str);
+            return 0;
+        }
+    }
+
+    log::warn!("Entity with label '{:?}' not found", label_str);
+    -1
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/states.rs b/eucalyptus-core/src/states.rs
index 7962834..396cf4a 100644
--- a/eucalyptus-core/src/states.rs
+++ b/eucalyptus-core/src/states.rs
@@ -242,12 +242,27 @@ pub enum Node {
 }
 
 #[derive(Default, Debug, Serialize, Deserialize, Clone)]
-pub struct File {
-    pub name: String,
-    pub path: PathBuf,
-    pub resource_type: Option<ResourceType>,
+pub enum File {
+    #[default]
+    Unknown,
+    ResourceFile {
+        name: String,
+        path: PathBuf,
+        resource_type: ResourceType,
+    },
+    SourceFile {
+        name: String,
+        path: PathBuf,
+    },
 }
 
+// #[derive(Default, Debug, Serialize, Deserialize, Clone)]
+// pub struct File {
+//     pub name: String,
+//     pub path: PathBuf,
+//     pub resource_type: Option<ResourceType>,
+// }
+
 #[derive(Default, Debug, Serialize, Deserialize, Clone)]
 pub struct Folder {
     pub name: String,
@@ -259,6 +274,8 @@ pub struct Folder {
 #[derive(Debug, Serialize, Deserialize, Clone, Hash)]
 pub enum ResourceType {
     Unknown,
+    Config,
+    Script,
     Model,
     Thumbnail,
     Texture,
@@ -273,6 +290,8 @@ impl Display for ResourceType {
             ResourceType::Texture => "texture",
             ResourceType::Shader => "shader",
             ResourceType::Thumbnail => "thumbnail",
+            ResourceType::Script => "script",
+            ResourceType::Config => "eucalyptus project config"
         };
         write!(f, "{}", str)
     }
@@ -406,13 +425,17 @@ fn collect_nodes(dir: impl AsRef<Path>, project_path: impl AsRef<Path>, exclude_
                     .unwrap_or_default();
 
                 let resource_type = if parent_folder.contains("model") {
-                    Some(ResourceType::Model)
+                    ResourceType::Model
                 } else if parent_folder.contains("texture") {
-                    Some(ResourceType::Texture)
+                    ResourceType::Texture
                 } else if parent_folder.contains("shader") {
-                    Some(ResourceType::Shader)
+                    ResourceType::Shader
+                } else if entry_path.extension().map(|e| e.to_string_lossy().to_lowercase()) == Some("kt".to_string()) {
+                    ResourceType::Script
+                } else if entry_path.extension().map(|e| e.to_string_lossy().to_lowercase().contains("eu")).unwrap_or_default() {
+                    ResourceType::Config
                 } else {
-                    Some(ResourceType::Unknown)
+                    ResourceType::Unknown
                 };
 
                 // Store relative path from the project root instead of absolute path
@@ -421,7 +444,7 @@ fn collect_nodes(dir: impl AsRef<Path>, project_path: impl AsRef<Path>, exclude_
                     .unwrap_or(&entry_path)
                     .to_path_buf();
 
-                nodes.push(Node::File(File {
+                nodes.push(Node::File(File::ResourceFile {
                     name,
                     path: relative_path,
                     resource_type,
@@ -439,8 +462,7 @@ pub enum EntityNode {
         name: String,
     },
     Script {
-        name: String,
-        path: PathBuf,
+        tags: Vec<String>
     },
     Light {
         id: hecs::Entity,
@@ -460,8 +482,7 @@ pub enum EntityNode {
 
 #[derive(Default, Debug, Serialize, Deserialize, Clone)]
 pub struct ScriptComponent {
-    pub name: String,
-    pub path: PathBuf,
+    pub tags: Vec<String>
 }
 
 impl EntityNode {
@@ -484,8 +505,7 @@ impl EntityNode {
                     name: name.clone(),
                 },
                 EntityNode::Script {
-                    name: script.name.clone(),
-                    path: script.path.clone(),
+                    tags: script.tags.clone(),
                 },
             ];
 
@@ -892,8 +912,7 @@ impl SceneConfig {
 
                         if let Some(script_config) = &entity_config.script {
                             let script = ScriptComponent {
-                                name: script_config.name.clone(),
-                                path: script_config.path.clone(),
+                                tags: script_config.tags.clone(),
                             };
                             // if let (Some(target_label), Some(offset)) = (
                             //     &camera_config.follow_target_entity_label,
@@ -924,8 +943,7 @@ impl SceneConfig {
                     } else {
                         if let Some(script_config) = &entity_config.script {
                             let script = ScriptComponent {
-                                name: script_config.name.clone(),
-                                path: script_config.path.clone(),
+                                tags: script_config.tags.clone(),
                             };
                             world.spawn((adopted, transform, script, entity_config.properties.clone()))
                         } else {
@@ -977,20 +995,8 @@ impl SceneConfig {
 
                         if let Some(script_config) = &entity_config.script {
                             let script = ScriptComponent {
-                                name: script_config.name.clone(),
-                                path: script_config.path.clone(),
+                                tags: script_config.tags.clone(),
                             };
-                            // if let (Some(target_label), Some(offset)) = (
-                            //     &camera_config.follow_target_entity_label,
-                            //     &camera_config.follow_offset,
-                            // ) {
-                            //     let follow_target = CameraFollowTarget {
-                            //         follow_target: target_label.clone(),
-                            //         offset: DVec3::from_array(*offset),
-                            //     };
-                            //     world.spawn((adopted, transform, script, entity_config.properties.clone(), camera, camera_component, follow_target))
-                            // } else {
-                            // }
                             world.spawn((adopted, transform, script, entity_config.properties.clone(), camera, camera_component))
                         } else {
                             world.spawn((adopted, transform, entity_config.properties.clone(), camera, camera_component))
@@ -999,8 +1005,7 @@ impl SceneConfig {
                         // Entity without camera components
                         if let Some(script_config) = &entity_config.script {
                             let script = ScriptComponent {
-                                name: script_config.name.clone(),
-                                path: script_config.path.clone(),
+                                tags: script_config.tags.clone(),
                             };
                             world.spawn((adopted, transform, script, entity_config.properties.clone()))
                         } else {
@@ -1090,8 +1095,7 @@ impl SceneConfig {
 
                         if let Some(script_config) = &entity_config.script {
                             let script = ScriptComponent {
-                                name: script_config.name.clone(),
-                                path: script_config.path.clone(),
+                                tags: script_config.tags.clone(),
                             };
                             // if let (Some(target_label), Some(offset)) = (
                             //     &camera_config.follow_target_entity_label,
@@ -1123,8 +1127,7 @@ impl SceneConfig {
                         // Entity without camera components
                         if let Some(script_config) = &entity_config.script {
                             let script = ScriptComponent {
-                                name: script_config.name.clone(),
-                                path: script_config.path.clone(),
+                                tags: script_config.tags.clone(),
                             };
                             world.spawn((plane, transform, script, entity_config.properties.clone()))
                         } else {
diff --git a/eucalyptus-editor/build.rs b/eucalyptus-editor/build.rs
index 5a899d4..5d3a697 100644
--- a/eucalyptus-editor/build.rs
+++ b/eucalyptus-editor/build.rs
@@ -1,5 +1,3 @@
-use std::fs::{self, File};
-use std::io::Cursor;
 use std::process::Command;
 
 fn main() -> anyhow::Result<()> {
@@ -16,59 +14,6 @@ fn main() -> anyhow::Result<()> {
     println!("cargo:rerun-if-changed=.git/HEAD");
     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] :("
-    //     ));
-    // }
-
     // fuck you windows :(
     #[cfg(target_os = "windows")]
     {
diff --git a/eucalyptus-editor/src/debug.rs b/eucalyptus-editor/src/debug.rs
index 3b3dc4d..d33fe39 100644
--- a/eucalyptus-editor/src/debug.rs
+++ b/eucalyptus-editor/src/debug.rs
@@ -17,5 +17,7 @@ pub(crate) fn show_menu_bar(
             log::info!("Show Entities Loaded under Debug Menu is clicked");
             *signal = Signal::LogEntities;
         }
+
+        
     });
 }
diff --git a/eucalyptus-editor/src/editor/component.rs b/eucalyptus-editor/src/editor/component.rs
index 4589d71..a3cfd2a 100644
--- a/eucalyptus-editor/src/editor/component.rs
+++ b/eucalyptus-editor/src/editor/component.rs
@@ -6,7 +6,6 @@ use dropbear_engine::attenuation::ATTENUATION_PRESETS;
 use dropbear_engine::entity::{AdoptedEntity, Transform};
 use dropbear_engine::lighting::{Light, LightComponent, LightType};
 use egui::{CollapsingHeader, ComboBox, DragValue, Grid, RichText, TextEdit, Ui};
-use eucalyptus_core::scripting::{ScriptAction, TEMPLATE_SCRIPT};
 use eucalyptus_core::states::{ModelProperties, ScriptComponent, Value};
 use eucalyptus_core::warn;
 use glam::Vec3;
@@ -585,70 +584,30 @@ impl InspectableComponent for ScriptComponent {
         signal: &mut Signal,
         label: &mut String,
     ) {
-        let script_loc = self.path.to_str().unwrap_or("").to_string();
-
         ui.vertical(|ui| {
             CollapsingHeader::new("Scripting")
                 .default_open(true)
                 .show(ui, |ui| {
-                    ui.horizontal(|ui| {
-                        if ui.button("Browse").clicked()
-                            && let Some(script_file) = rfd::FileDialog::new()
-                                .add_filter("Kotlin", &["kt"])
-                                .pick_file()
-                            {
-                                let script_name = script_file
-                                    .file_stem()
-                                    .unwrap_or_default()
-                                    .to_string_lossy()
-                                    .to_string();
-                                *signal = Signal::ScriptAction(ScriptAction::AttachScript {
-                                    script_path: script_file,
-                                    script_name,
+                    CollapsingHeader::new("Tags")
+                        .default_open(true)
+                        .show(ui, |ui| {
+                            let mut local_del: Option<usize> = None;
+                            for (i, tag) in self.tags.iter_mut().enumerate() {
+                                let current_width = ui.available_width();
+                                ui.horizontal(|ui| {
+                                    ui.add_sized([current_width*70.0/100.0, 20.0], TextEdit::singleline(tag));
+                                    if ui.button("🗑️").clicked() {
+                                        local_del = Some(i);
+                                    }
                                 });
                             }
-
-                        if ui.button("New").clicked()
-                            && let Some(script_path) = rfd::FileDialog::new()
-                                .add_filter("Kotlin", &["kt"])
-                                .set_file_name(format!("{}_script.kt", label))
-                                .save_file()
-                            {
-                                match std::fs::write(&script_path, TEMPLATE_SCRIPT) {
-                                    Ok(_) => {
-                                        let script_name = script_path
-                                            .file_stem()
-                                            .unwrap_or_default()
-                                            .to_string_lossy()
-                                            .to_string();
-                                        *signal = Signal::ScriptAction(ScriptAction::CreateAndAttachScript {
-                                            script_path,
-                                            script_name,
-                                        });
-                                    },
-                                    Err(e) => {
-                                        warn!("Failed to create script file: {}", e);
-                                    },
-                                }
+                            if let Some(i) = local_del {
+                                self.tags.remove(i);
                             }
-                    });
-
-                    ui.separator();
-
-                    ui.horizontal_wrapped(|ui| {
-                        ui.label("Script Location:");
-                        ui.label(script_loc);
-                    });
-
-                    if ui.button("Remove").clicked() {
-                        *signal = Signal::ScriptAction(ScriptAction::RemoveScript);
-                    }
-                    ui.separator();
-                    ui.horizontal(|ui| {
-                        if ui.button("Edit Script").clicked() {
-                            *signal = Signal::ScriptAction(ScriptAction::EditScript);
-                        }
-                    });
+                            if ui.button("➕ Add").clicked() {
+                                self.tags.push(String::new())
+                            }
+                        });
                 });
         });
     }
diff --git a/eucalyptus-editor/src/editor/dock.rs b/eucalyptus-editor/src/editor/dock.rs
index 5a75e9f..5c42783 100644
--- a/eucalyptus-editor/src/editor/dock.rs
+++ b/eucalyptus-editor/src/editor/dock.rs
@@ -12,7 +12,7 @@ use egui;
 use egui::{CollapsingHeader};
 use egui_dock_fork::TabViewer;
 use egui_extras;
-use eucalyptus_core::states::{Node, RESOURCES, ResourceType};
+use eucalyptus_core::states::{Node, RESOURCES, ResourceType, File};
 use log;
 use parking_lot::Mutex;
 use transform_gizmo_egui::{EnumSet, Gizmo, GizmoConfig, GizmoExt, GizmoMode, math::DVec3};
@@ -453,91 +453,94 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                         for node in res {
                             match node {
                                 Node::File(file) => {
-                                    if !logged.contains(&file.name) {
-                                        logged.insert(file.name.clone());
-                                        log::debug!(
-                                            "Adding image for {} of type {}",
-                                            file.name,
-                                            file.resource_type.as_ref().unwrap()
-                                        );
-                                    }
-                                    if let Some(ref res_type) = file.resource_type {
-                                        match res_type {
-                                            ResourceType::Model => {
-                                                let ad_dir = app_dirs2::get_app_root(
-                                                    app_dirs2::AppDataType::UserData,
-                                                    &APP_INFO,
-                                                )
-                                                .unwrap();
-
-                                                let model_thumbnail =
-                                                    ad_dir.join(format!("{}.png", file.name));
-
-                                                if !model_thumbnail.exists() {
-                                                    // gen image
-                                                    log::debug!(
-                                                        "Model thumbnail [{}] does not exist, generating one now",
-                                                        file.name
+                                    match file {
+                                        File::Unknown => {}
+                                        File::ResourceFile { name, path, resource_type } => {
+                                            if !logged.contains(name) {
+                                                logged.insert(name.clone());
+                                                log::debug!(
+                                                    "Adding image for {} of type {}",
+                                                    name,
+                                                    resource_type
+                                                );
+                                            }
+                                            match resource_type {
+                                                ResourceType::Model => {
+                                                    let ad_dir = app_dirs2::get_app_root(
+                                                        app_dirs2::AppDataType::UserData,
+                                                        &APP_INFO,
+                                                    )
+                                                        .unwrap();
+
+                                                    let model_thumbnail =
+                                                        ad_dir.join(format!("{}.png", name));
+
+                                                    if !model_thumbnail.exists() {
+                                                        // gen image
+                                                        log::debug!(
+                                                    "Model thumbnail [{}] does not exist, generating one now",
+                                                    name
+                                                );
+                                                        let mut model = match model_to_image::ModelToImageBuilder::new(&path)
+                                                            .with_size((600, 600))
+                                                            .build() {
+                                                            Ok(v) => v,
+                                                            Err(e) => panic!("Error occurred while loading file from path: {}", e),
+                                                        };
+                                                        if let Err(e) =
+                                                            model.render().unwrap().write_to(Some(
+                                                                &ad_dir
+                                                                    .join(format!("{}.png", name)),
+                                                            ))
+                                                        {
+                                                            log::error!(
+                                                        "Failed to write model thumbnail for {}: {}",
+                                                        name,
+                                                        e
                                                     );
-                                                    let mut model = match model_to_image::ModelToImageBuilder::new(&file.path)
-                                                    .with_size((600, 600))
-                                                    .build() {
-                                                        Ok(v) => v,
-                                                        Err(e) => panic!("Error occurred while loading file from path: {}", e),
-                                                    };
-                                                    if let Err(e) =
-                                                        model.render().unwrap().write_to(Some(
-                                                            &ad_dir
-                                                                .join(format!("{}.png", file.name)),
-                                                        ))
-                                                    {
-                                                        log::error!(
-                                                            "Failed to write model thumbnail for {}: {}",
-                                                            file.name,
-                                                            e
-                                                        );
+                                                        }
                                                     }
-                                                }
 
-                                                let image_uri =
-                                                    model_thumbnail.to_string_lossy().to_string();
+                                                    let image_uri =
+                                                        model_thumbnail.to_string_lossy().to_string();
 
-                                                assets.push((
-                                                    format!("file://{}", image_uri),
-                                                    file.name.clone(),
-                                                    file.path.clone(),
-                                                    res_type.clone(),
-                                                ))
-                                            }
-                                            ResourceType::Texture => assets.push((
-                                                format!(
-                                                    "file://{}",
-                                                    file.path.to_string_lossy()
-                                                ),
-                                                file.name.clone(),
-                                                file.path.clone(),
-                                                res_type.clone(),
-                                            )),
-                                            _ => {
-                                                if file
-                                                    .path
-                                                    .clone()
-                                                    .extension()
-                                                    .unwrap()
-                                                    .to_str()
-                                                    .unwrap()
-                                                    .contains("euc")
-                                                {
-                                                    continue;
+                                                    assets.push((
+                                                        format!("file://{}", image_uri),
+                                                        name.clone(),
+                                                        path.clone(),
+                                                        resource_type.clone(),
+                                                    ))
+                                                }
+                                                ResourceType::Texture => assets.push((
+                                                    format!(
+                                                        "file://{}",
+                                                        path.to_string_lossy()
+                                                    ),
+                                                    name.clone(),
+                                                    path.clone(),
+                                                    resource_type.clone(),
+                                                )),
+                                                _ => {
+                                                    if path
+                                                        .clone()
+                                                        .extension()
+                                                        .unwrap()
+                                                        .to_str()
+                                                        .unwrap()
+                                                        .contains("euc")
+                                                    {
+                                                        continue;
+                                                    }
+                                                    assets.push((
+                                                        "NO_TEXTURE".into(),
+                                                        name.clone(),
+                                                        path.clone(),
+                                                        resource_type.clone(),
+                                                    ))
                                                 }
-                                                assets.push((
-                                                    "NO_TEXTURE".into(),
-                                                    file.name.clone(),
-                                                    file.path.clone(),
-                                                    res_type.clone(),
-                                                ))
                                             }
                                         }
+                                        File::SourceFile { .. } => {}
                                     }
                                 }
                                 Node::Folder(folder) => {
diff --git a/eucalyptus-editor/src/editor/mod.rs b/eucalyptus-editor/src/editor/mod.rs
index ffa594e..f02e236 100644
--- a/eucalyptus-editor/src/editor/mod.rs
+++ b/eucalyptus-editor/src/editor/mod.rs
@@ -29,7 +29,7 @@ use eucalyptus_core::{camera::{
     CameraAction, CameraComponent, CameraType, DebugCamera,
 }, states::WorldLoadingStatus};
 use eucalyptus_core::input::InputState;
-use eucalyptus_core::scripting::{ScriptAction, ScriptManager};
+use eucalyptus_core::scripting::{ScriptManager};
 use eucalyptus_core::states::{
     CameraConfig, EditorTab, EntityNode, LightConfig, ModelProperties, PROJECT, SCENES,
     SceneEntity, ScriptComponent,
@@ -49,7 +49,7 @@ use dropbear_engine::model::{ModelId};
 
 pub struct Editor {
     scene_command: SceneCommand,
-    pub world: World,
+    pub world: Box<World>,
     dock_state: DockState<EditorTab>,
     texture_id: Option<egui::TextureId>,
     size: Extent3d,
@@ -150,7 +150,7 @@ impl Editor {
             is_viewport_focused: false,
             // is_cursor_locked: false,
             window: None,
-            world: World::new(),
+            world: Box::new(World::new()),
             show_new_project: false,
             project_name: String::new(),
             project_path: Arc::new(Mutex::new(None)),
@@ -903,12 +903,12 @@ fn show_entity_tree(
                     }
                 });
             }
-            EntityNode::Script { name, path: _ } => {
+            EntityNode::Script { tags } => {
                 ui.horizontal(|ui| {
                     handle.ui(ui, |ui| {
                         ui.label("📜");
                     });
-                    ui.label(name.to_string());
+                    ui.label("Script");
                 });
             }
             EntityNode::Group {
@@ -1172,10 +1172,9 @@ pub enum Signal {
     Paste(SceneEntity),
     Delete,
     Undo,
-    ScriptAction(ScriptAction),
-    #[allow(dead_code)]
+    // ScriptAction(ScriptAction),
     // not actions required because follow target is set through scripting. 
-    CameraAction(CameraAction),
+    // CameraAction(CameraAction),
     Play,
     StopPlaying,
     AddComponent(hecs::Entity, EntityType),
diff --git a/eucalyptus-editor/src/editor/scene.rs b/eucalyptus-editor/src/editor/scene.rs
index 6c899c5..2eb5306 100644
--- a/eucalyptus-editor/src/editor/scene.rs
+++ b/eucalyptus-editor/src/editor/scene.rs
@@ -52,7 +52,7 @@ impl Scene for Editor {
         if let Some(mut receiver) = self.world_receiver.take() {
             self.show_project_loading_window(&graphics.shared.get_egui_context());
             if let Ok(loaded_world) = receiver.try_recv() {
-                self.world = loaded_world;
+                self.world = Box::new(loaded_world);
                 self.is_world_loaded.mark_project_loaded();
                 
                 if let Some(dock_state_shared) = &self.dock_state_shared &&
@@ -106,18 +106,11 @@ impl Scene for Editor {
                     .iter()
                 {
                     log_once::debug_once!(
-                        "Script Entity -> id: {:?}, component: {:?}",
+                        "Script Entity -> id: {:?}, tags: {:?}",
                         entity_id,
-                        script
+                        script.tags
                     );
-                    script.name = script
-                        .path
-                        .file_name()
-                        .unwrap()
-                        .to_str()
-                        .unwrap()
-                        .to_string();
-                    script_entities.push((entity_id, script.name.clone()));
+                    script_entities.push((entity_id, script.tags.clone()));
                 }
             }
 
@@ -126,16 +119,14 @@ impl Scene for Editor {
             }
 
             for (entity_id, script_name) in script_entities {
-                if let Err(e) = self.script_manager.update_entity_script(
+                if let Err(e) = self.script_manager.update_script(
                     entity_id,
-                    &script_name,
                     &mut self.world,
                     &self.input_state,
                     dt,
                 ) {
                     log_once::warn_once!(
-                        "Failed to update script '{}' for entity {:?}: {}",
-                        script_name,
+                        "Failed to update script for entity {:?}: {}",
                         entity_id,
                         e
                     );
@@ -183,46 +174,6 @@ impl Scene for Editor {
             }
         }
 
-        // let camera_follow_data: Vec<(Entity, String, glam::Vec3)> = {
-        //     self.world
-        //         .query::<(&Camera, &CameraComponent)>()
-        //         .iter()
-        //         .filter_map(|(entity_id, (_, _))| {
-        //             follow_target.map(|target| {
-        //                 (
-        //                     entity_id,
-        //                     target.follow_target.clone(),
-        //                     target.offset.as_vec3()
-        //                 )
-        //             })
-        //         })
-        //         .collect()
-        // };
-
-
-        // for (camera_entity, target_label, offset) in camera_follow_data {
-        //     let target_position = {
-        //         self.world
-        //             .query::<(&AdoptedEntity, &Transform)>()
-        //             .iter()
-        //             .find_map(|(_, (adopted, transform))| {
-        //                 if adopted.model.label == target_label {
-        //                     Some(transform.position)
-        //                 } else {
-        //                     None
-        //                 }
-        //             })
-        //     };
-        // 
-        // 
-        //     if let Some(pos) = target_position 
-        //         && let Ok(mut query) = self.world.query_one::<&mut Camera>(camera_entity)
-        //         && let Some(camera) = query.get() {
-        //             camera.eye = pos + offset.as_dvec3();
-        //             camera.target = pos;
-        //         }
-        // }
-
         {
             for (_entity_id, (camera, component)) in self.world
                 .query::<(&mut Camera, &mut CameraComponent)>()
diff --git a/eucalyptus-editor/src/signal.rs b/eucalyptus-editor/src/signal.rs
index 135363b..d6fdff4 100644
--- a/eucalyptus-editor/src/signal.rs
+++ b/eucalyptus-editor/src/signal.rs
@@ -1,5 +1,7 @@
+use std::collections::HashMap;
 use std::sync::Arc;
 use egui::{Align2, Image};
+use hecs::Entity;
 use dropbear_engine::camera::Camera;
 use dropbear_engine::entity::{AdoptedEntity, Transform};
 use dropbear_engine::graphics::SharedGraphicsContext;
@@ -8,7 +10,7 @@ use dropbear_engine::utils::{ResourceReference, ResourceReferenceType};
 use eucalyptus_core::states::{ModelProperties, ScriptComponent, Value};
 use eucalyptus_core::{fatal, info, scripting, success, success_without_console, warn, warn_without_console};
 use eucalyptus_core::camera::{CameraAction, CameraComponent, CameraType};
-use eucalyptus_core::scripting::ScriptAction;
+use eucalyptus_core::scripting::ScriptTarget;
 use eucalyptus_core::spawn::{push_pending_spawn, PendingSpawn};
 use crate::editor::{ComponentType, Editor, EditorState, EntityType, PendingSpawn2, Signal, UndoableAction};
 
@@ -25,6 +27,7 @@ impl SignalController for Editor {
 
         match &self.signal {
             Signal::None => {
+                // returns absolutely nothing because no signal is set. 
                 Ok::<(), anyhow::Error>(())
             }
             Signal::Copy(_) => {Ok(())}
@@ -93,210 +96,6 @@ impl SignalController for Editor {
                 self.signal = Signal::None;
                 Ok(())
             }
-            Signal::ScriptAction(action) => match action {
-                ScriptAction::AttachScript {
-                    script_path,
-                    script_name,
-                } => {
-                    if let Some(selected_entity) = self.selected_entity {
-                        match scripting::move_script_to_src(script_path) {
-                            Ok(moved_path) => {
-                                let new_script = ScriptComponent {
-                                    name: script_name.clone(),
-                                    path: moved_path.clone(),
-                                };
-
-                                let replaced = {
-                                    if let Ok(mut sc) = self.world.get::<&mut ScriptComponent>(selected_entity) {
-                                        sc.name = new_script.name.clone();
-                                        sc.path = new_script.path.clone();
-                                        true
-                                    } else {
-                                        false
-                                    }
-                                };
-
-                                if !replaced {
-                                    match scripting::attach_script_to_entity(
-                                        &mut self.world,
-                                        selected_entity,
-                                        new_script.clone(),
-                                    ) {
-                                        Ok(_) => {
-                                        }
-                                        Err(e) => {
-                                            self.signal = Signal::None;
-                                            fatal!("Failed to attach script to entity {:?}: {}",
-                                                selected_entity,
-                                                e);
-                                            return Err(anyhow::anyhow!(e));
-                                        }
-                                    }
-                                }
-
-                                {
-                                    if let Err(e) = scripting::convert_entity_to_group(
-                                        &mut self.world,
-                                        selected_entity,
-                                    ) {
-                                        log::warn!("convert_entity_to_group failed (non-fatal): {}", e);
-                                    }
-                                }
-
-                                success!(
-                                    "{} script '{}' at {} to entity {:?}",
-                                    if replaced { "Reattached" } else { "Attached" },
-                                    script_name,
-                                    moved_path.display(),
-                                    selected_entity
-                                );
-                            }
-                            Err(e) => {
-                                fatal!("Move failed: {}", e);
-                            }
-                        }
-                    } else {
-                        fatal!("AttachScript requested but no entity is selected");
-                    }
-
-                    self.signal = Signal::None;
-                    Ok(())
-                }
-                ScriptAction::CreateAndAttachScript {
-                    script_path,
-                    script_name,
-                } => {
-                    if let Some(selected_entity) = self.selected_entity {
-                        let new_script = ScriptComponent {
-                            name: script_name.clone(),
-                            path: script_path.clone(),
-                        };
-
-                        let replaced = {
-                            if let Ok(mut sc) = self.world.get::<&mut ScriptComponent>(selected_entity) {
-                                sc.name = new_script.name.clone();
-                                sc.path = new_script.path.clone();
-                                true
-                            } else {
-                                false
-                            }
-                        };
-
-                        if !replaced {
-                            match scripting::attach_script_to_entity(
-                                &mut self.world,
-                                selected_entity,
-                                new_script.clone(),
-                            ) {
-                                Ok(_) => {
-                                }
-                                Err(e) => {
-                                    self.signal = Signal::None;
-                                    fatal!("Failed to attach new script: {}", e);
-                                    return Err(anyhow::anyhow!(e));
-                                }
-                            }
-                        }
-
-                        {
-                            if let Err(e) = scripting::convert_entity_to_group(
-                                &mut self.world,
-                                selected_entity,
-                            ) {
-                                log::warn!("convert_entity_to_group failed (non-fatal): {}", e);
-                            }
-                        }
-
-                        success!(
-                            "{} new script '{}' at {} to entity {:?}",
-                            if replaced { "Replaced" } else { "Attached" },
-                            script_name,
-                            script_path.display(),
-                            selected_entity
-                        );
-                    } else {
-                        warn_without_console!("No selected entity to attach new script");
-                        log::warn!("CreateAndAttachScript requested but no entity is selected");
-                    }
-                    self.signal = Signal::None;
-                    Ok(())
-                }
-                ScriptAction::RemoveScript => {
-                    if let Some(selected_entity) = self.selected_entity {
-                        let mut success = false;
-                        let mut comp = ScriptComponent::default();
-                        {
-                            if let Ok(script) = self.world
-                                .remove_one::<ScriptComponent>(selected_entity)
-                            {
-                                success!("Removed script from entity {:?}", selected_entity);
-                                success = true;
-                                comp = script.clone();
-                            } else {
-                                warn!("No script component found on entity {:?}", selected_entity);
-                            }
-                            match self.world.insert_one(selected_entity, ScriptComponent::default()) {
-                                Ok(_) => {
-                                    log::debug!("Inserted default script component");
-                                }
-                                Err(e) => {
-                                    log::warn!("No such entity is available. Additional info: {}", e);
-                                }
-                            }
-                        }
-
-                        if success {
-                            if let Err(e) = scripting::convert_entity_to_group(
-                                &mut self.world,
-                                selected_entity,
-                            ) {
-                                log::warn!("convert_entity_to_group failed (non-fatal): {}", e);
-                            }
-                            log::debug!("Pushing remove component to undo stack");
-                            UndoableAction::push_to_undo(
-                                &mut self.undo_stack,
-                                UndoableAction::RemoveComponent(
-                                    selected_entity,
-                                    Box::new(ComponentType::Script(comp)),
-                                ),
-                            );
-                        }
-                    } else {
-                        warn!("No entity selected to remove script from");
-                    }
-
-                    self.signal = Signal::None;
-                    Ok(())
-                }
-                ScriptAction::EditScript => {
-                    if let Some(selected_entity) = self.selected_entity {
-                        let script_opt = {
-                            if let Ok(mut q) = self.world.query_one::<&ScriptComponent>(selected_entity) {
-                                q.get().cloned()
-                            } else {
-                                None
-                            }
-                        };
-
-                        if let Some(script) = script_opt {
-                            match open::that(script.path.clone()) {
-                                Ok(()) => {
-                                    success!("Opened {}", script.name)
-                                }
-                                Err(e) => {
-                                    warn!("Error while opening {}: {}", script.name, e);
-                                }
-                            }
-                        } else {
-                            warn!("No script component found on entity {:?}", selected_entity);
-                        }
-                    } else {
-                        warn!("No entity selected to edit script");
-                    }
-                    self.signal = Signal::None;
-                    Ok(())
-                }
-            },
             Signal::Play => {
                 if matches!(self.editor_state, EditorState::Playing) {
                     fatal!("Unable to play: already in playing mode");
@@ -329,60 +128,43 @@ impl SignalController for Editor {
                         }
                     }
 
+                    let mut etag: HashMap<String, Vec<Entity>> = HashMap::new();
                     for (entity_id, script) in script_entities {
+                        for tag in script.tags {
+                            if etag.contains_key(&tag) {
+                                etag.get_mut(&tag).unwrap().push(entity_id);
+                            } else {
+                                etag.insert(tag.clone(), vec![entity_id]);
+                            }
+                        }
+                    }
+
+                    let etag_clone = etag.clone();
+
+                    // todo: get the library name working
+                    if let Err(e) = self.script_manager.init_script(etag_clone, ScriptTarget::None) {
+                        fatal!("Failed to ready script manager interface because {}", e);
+                        self.signal = Signal::StopPlaying;
+                        return Err(anyhow::anyhow!(e));
+                    }
+
+                    for (tag, entities) in &etag {
                         log::debug!(
-                            "Initialising entity script [{}] from path: {}",
-                            script.name,
-                            script.path.display()
+                            "Initialising script for tag {:?} with entities: {:?}",
+                            tag,
+                            entities
                         );
 
-                        let bytes = match std::fs::read_to_string(&script.path) {
-                            Ok(val) => val,
-                            Err(e) => {
-                                self.signal = Signal::None;
-                                fatal!(
-                                    "Unable to read script {} to bytes because {}",
-                                    &script.path.display(),
-                                    e
-                                );
+                        for e in entities {
+                            if let Err(e) = self.script_manager.load_script(*e, tag.clone(), &mut self.world, &self.input_state) {
+                                fatal!("Failed to initialise script for tag {:?} because {}", tag, e);
+                                self.signal = Signal::StopPlaying;
                                 return Err(anyhow::anyhow!(e));
-                            }
-                        };
-
-                        match self.script_manager.load_script(
-                            &script
-                                .path
-                                .file_name()
-                                .unwrap()
-                                .to_string_lossy()
-                                .to_string(),
-                            bytes,
-                        ) {
-                            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 {
-                                    success_without_console!(
+                            } else {
+                                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) => {
-                                // todo: proper error menu
-                                self.signal = Signal::StopPlaying;
-                                fatal!("Failed to load script '{}': {}", script.name, e);
+                                log::info!("You are in play mode now! Press Escape to exit");
                             }
                         }
                     }
@@ -415,28 +197,6 @@ impl SignalController for Editor {
                 self.signal = Signal::None;
                 Ok(())
             }
-            Signal::CameraAction(action) => match action {
-                CameraAction::SetPlayerTarget { .. } => {
-                    log::warn!("Deprecated: CameraAction::SetPlayerTarget");
-                    self.signal = Signal::None;
-                    Ok(())
-                }
-                CameraAction::ClearPlayerTarget => {
-                    log::warn!("Deprecated: CameraAction::ClearPlayerTarget");
-                    self.signal = Signal::None;
-                    Ok(())
-                }
-                CameraAction::SetCurrentPositionAsOffset(_) => {
-                    // if let Ok((camera, target)) = self.world.query_one_mut::<(&Camera, &mut CameraFollowTarget)>(*entity) {
-                    //     target.offset = camera.target
-                    // } else {
-                    //     warn!("Unable to query camera to set current camera position to offset");
-                    // }
-                    log::warn!("Deprecated: CameraAction::SetCurrentPositionAsOffset");
-                    self.signal = Signal::None;
-                    Ok(())
-                }
-            },
             Signal::AddComponent(entity, e_type) => {
                 match e_type {
                     EntityType::Entity => {
@@ -702,18 +462,18 @@ impl SignalController for Editor {
             Signal::LogEntities => {
                 log::debug!("====================");
                 let mut counter = 0;
-                for entity in self.world.iter() {
-                    if let Some(entity) = entity.get::<&AdoptedEntity>() {
-                        log::info!("Model: {:?}", entity.model.label);
+                for e in self.world.iter() {
+                    if let Some(entity) = e.get::<&AdoptedEntity>() {
+                        log::info!("Model: {:?} with u32 id: {:?}", entity.model.label, e.entity().id());
                         log::info!("  |-> Using model: {:?}", entity.model.id);
                     }
 
-                    if let Some(entity) = entity.get::<&Light>() {
+                    if let Some(entity) = e.get::<&Light>() {
                         log::info!("Light: {:?}", entity.cube_model.label);
                         log::info!("  |-> Using model: {:?}", entity.cube_model.id);
                     }
 
-                    if entity.get::<&Camera>().is_some() {
+                    if e.get::<&Camera>().is_some() {
                         log::info!("Camera");
                     }
                     counter += 1;
diff --git a/gradle.properties b/gradle.properties
index 7fc6f1f..9b79a16 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1 +1,3 @@
 kotlin.code.style=official
+org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
+org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..3eb05ee
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,10 @@
+[versions]
+kotlin = "2.1.0"
+kotlinxSerialization = "1.8.0"
+
+[libraries]
+kotlinxSerializationJson = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
+
+[plugins]
+kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
+kotlinxSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
\ No newline at end of file
diff --git a/gradlew b/gradlew
old mode 100644
new mode 100755
diff --git a/headers/dropbear.h b/headers/dropbear.h
new file mode 100644
index 0000000..75565a4
--- /dev/null
+++ b/headers/dropbear.h
@@ -0,0 +1,23 @@
+#ifndef DROPBEAR_H
+#define DROPBEAR_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+typedef struct World World; // opaque pointer
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+// ===========================================
+
+// returns 0 on success, non-zero on failure
+int dropbear_get_entity(const char* label, const World* world_ptr, uint64_t* out_entity);
+
+// ===========================================
+
+#ifdef __cplusplus
+} // extern "C"
+#endif // __cplusplus
+#endif // DROPBEAR_H
\ No newline at end of file
diff --git a/magna-carta/Cargo.toml b/magna-carta/Cargo.toml
new file mode 100644
index 0000000..9674d71
--- /dev/null
+++ b/magna-carta/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "magna-carta"
+version.workspace = true
+edition.workspace = true
+license = "MIT"
+repository.workspace = true
+readme = "README.md"
+
+[dependencies]
+anyhow.workspace = true
+tree-sitter.workspace = true
+tree-sitter-kotlin.workspace = true
diff --git a/magna-carta/README.md b/magna-carta/README.md
new file mode 100644
index 0000000..9187754
--- /dev/null
+++ b/magna-carta/README.md
@@ -0,0 +1,24 @@
+# magna-carta
+
+Creates a manifest for kotlin files to allow for compile-time annotation processing. 
+
+This crate was created because Kotlin Symbol Processor (KSP) does not support Kotlin Multiplatform (KMP) Native 
+targets.
+
+## Behaviour
+
+### Common
+
+magna-carta will look for all files in the `src/commonMain/kotlin` directory and generate a manifest file in 
+`build/magna-carta/manifest.json`. This can be used for either [Native](#native) or [JVM](#jvm) targets, which
+each have their own behavior. 
+
+### Native
+
+In the case the Native build is requested, it will generate a manifest Kotlin file in `src/nativeMain/kotlin`, which
+includes exported C ABI entry points for dropbear to call into. 
+
+### JVM
+
+The JVM will also generate a manifest Kotlin file in `src/jvmMain/kotlin`, however because of its integration
+with the `jni` crate, it will not generate any C ABI entry points, instead allowing for reflection to be used. 
\ No newline at end of file
diff --git a/magna-carta/src/generator/jvm.rs b/magna-carta/src/generator/jvm.rs
new file mode 100644
index 0000000..8c8fa23
--- /dev/null
+++ b/magna-carta/src/generator/jvm.rs
@@ -0,0 +1,48 @@
+use std::fmt::Write;
+use crate::generator::Generator;
+use crate::ScriptManifest;
+
+pub struct KotlinJVMGenerator;
+
+impl Generator for KotlinJVMGenerator {
+    fn generate(&self, manifest: &ScriptManifest) -> anyhow::Result<String> {
+        let mut output = String::new();
+
+        writeln!(output, "// Auto-generated by dropbear-engine with the magna-carta parser")?;
+        writeln!(output)?;
+
+        // Import all discovered classes
+        let mut imported_packages = std::collections::HashSet::new();
+        for item in manifest.items() {
+            if let Some(last_dot) = item.fqcn().rfind('.') {
+                let package = &item.fqcn()[..last_dot];
+                imported_packages.insert(package.to_string());
+            }
+        }
+
+        for package in imported_packages.iter() {
+            writeln!(output, "import {}.*", package)?;
+        }
+        writeln!(output)?;
+
+        // Simple registry class for JVM
+        writeln!(output, "object RunnableRegistry {{")?;
+        writeln!(output, "    val SCRIPTS = listOf(")?;
+
+        for (i, item) in manifest.items().iter().enumerate() {
+            if i > 0 {
+                writeln!(output, ",")?;
+            }
+            writeln!(output, "        {}::class", item.simple_name())?;
+        }
+
+        if !manifest.items().is_empty() {
+            writeln!(output)?;
+        }
+        writeln!(output, "    )")?;
+        writeln!(output, "}}")?;
+
+        Ok(output)
+    }
+}
+
diff --git a/magna-carta/src/generator/mod.rs b/magna-carta/src/generator/mod.rs
new file mode 100644
index 0000000..6ac5980
--- /dev/null
+++ b/magna-carta/src/generator/mod.rs
@@ -0,0 +1,80 @@
+use std::path::Path;
+use crate::ScriptManifest;
+
+pub mod native;
+pub mod jvm;
+
+#[allow(dead_code)]
+pub trait Generator {
+    fn generate(&self, manifest: &ScriptManifest) -> anyhow::Result<String>;
+
+    fn write_to_file(&self, manifest: &ScriptManifest, path: impl AsRef<Path>) -> anyhow::Result<()> {
+        let content = self.generate(manifest)?;
+        std::fs::write(path, content)?;
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::path::PathBuf;
+    use crate::generator::jvm::KotlinJVMGenerator;
+    use crate::generator::native::KotlinNativeGenerator;
+    use crate::ManifestItem;
+
+    #[test]
+    fn test_native_generator() {
+        let mut manifest = ScriptManifest::new();
+        manifest.add_item(ManifestItem::new(
+            "com.game.Player".to_string(),
+            "Player".to_string(),
+            vec!["player".to_string(), "movement".to_string()],
+            PathBuf::from("src/Player.kt"),
+        ));
+
+        manifest.add_item(ManifestItem::new(
+            "com.game.GlobalLogger".to_string(),
+            "GlobalLogger".to_string(),
+            vec![],
+            PathBuf::from("src/GlobalLogger.kt"),
+        ));
+
+        let generator = KotlinNativeGenerator;
+        let output = generator.generate(&manifest).unwrap();
+
+        // Verify imports
+        assert!(output.contains("import com.game.Player"));
+        assert!(output.contains("import com.game.GlobalLogger"));
+
+        // Verify tags
+        assert!(output.contains("tags = listOf(\"player\", \"movement\")"));
+        assert!(output.contains("tags = listOf()"));
+
+        // Verify class instantiations
+        assert!(output.contains("script = Player()"));
+        assert!(output.contains("script = GlobalLogger()"));
+
+        // Verify CName exports
+        assert!(output.contains("@CName(\"dropbear_load\")"));
+        assert!(output.contains("@CName(\"dropbear_update\")"));
+        assert!(output.contains("@CName(\"dropbear_destroy\")"));
+    }
+
+    #[test]
+    fn test_jvm_generator() {
+        let mut manifest = ScriptManifest::new();
+        manifest.add_item(ManifestItem::new(
+            "com.game.Player".to_string(),
+            "Player".to_string(),
+            vec!["player".to_string()],
+            PathBuf::from("src/Player.kt"),
+        ));
+
+        let generator = KotlinJVMGenerator;
+        let output = generator.generate(&manifest).unwrap();
+
+        assert!(output.contains("import com.game.*"));
+        assert!(output.contains("Player::class"));
+    }
+}
\ No newline at end of file
diff --git a/magna-carta/src/generator/native.rs b/magna-carta/src/generator/native.rs
new file mode 100644
index 0000000..bb81004
--- /dev/null
+++ b/magna-carta/src/generator/native.rs
@@ -0,0 +1,134 @@
+use std::fmt::Write;
+use crate::generator::Generator;
+use crate::ScriptManifest;
+
+pub struct KotlinNativeGenerator;
+
+impl Generator for KotlinNativeGenerator {
+    fn generate(&self, manifest: &ScriptManifest) -> anyhow::Result<String> {
+        let mut output = String::new();
+
+        // Header
+        writeln!(output, "// Auto-generated by dropbear-engine with the magna-carta parser")?;
+        writeln!(output, "@file:OptIn(ExperimentalForeignApi::class, ExperimentalNativeApi::class)")?;
+        writeln!(output)?;
+
+        // Imports
+        writeln!(output, "import com.dropbear.DropbearEngine")?;
+        writeln!(output, "import com.dropbear.EntityId")?;
+        writeln!(output, "import com.dropbear.EntityRef")?;
+        writeln!(output, "import com.dropbear.ProjectScriptingMetadata")?;
+        writeln!(output, "import com.dropbear.ScriptRegistration")?;
+        writeln!(output, "import com.dropbear.ffi.NativeEngine")?;
+        writeln!(output, "import kotlinx.cinterop.COpaquePointer")?;
+        writeln!(output, "import kotlinx.cinterop.ExperimentalForeignApi")?;
+        writeln!(output, "import kotlin.experimental.ExperimentalNativeApi")?;
+        writeln!(output, "import kotlin.native.CName")?;
+        writeln!(output)?;
+
+        // Import all discovered classes
+        let mut imported_classes = std::collections::HashSet::new();
+        for item in manifest.items() {
+            // Extract package from FQCN to create import
+            if let Some(last_dot) = item.fqcn().rfind('.') {
+                let package = &item.fqcn()[..last_dot];
+                let simple_name = &item.fqcn()[last_dot + 1..];
+                writeln!(output, "import {}.{}", package, simple_name)?;
+                imported_classes.insert(simple_name.to_string());
+            } else {
+                // No package, just the class name
+                imported_classes.insert(item.simple_name().to_string());
+            }
+        }
+        writeln!(output)?;
+
+        // Script instances registry
+        writeln!(output, "private val scriptInstances: List<ScriptRegistration> by lazy {{")?;
+        writeln!(output, "    Metadata().getScripts()")?;
+        writeln!(output, "}}")?;
+        writeln!(output)?;
+
+        // Metadata class
+        writeln!(output, "class Metadata: ProjectScriptingMetadata {{")?;
+        writeln!(output, "    override fun getScripts(): List<ScriptRegistration> {{")?;
+        writeln!(output, "        return listOf(")?;
+
+        for (i, item) in manifest.items().iter().enumerate() {
+            if i > 0 {
+                writeln!(output, ",")?;
+            }
+
+            // Format tags
+            let tags_str = if item.tags().is_empty() {
+                "listOf()".to_string()
+            } else {
+                let tag_list: Vec<String> = item.tags()
+                    .iter()
+                    .map(|tag| format!("\"{}\"", tag))
+                    .collect();
+                format!("listOf({})", tag_list.join(", "))
+            };
+
+            writeln!(
+                output,
+                "            ScriptRegistration(\n                tags = {},\n                script = {}()\n            )",
+                tags_str,
+                item.simple_name()
+            )?;
+        }
+
+        if !manifest.items().is_empty() {
+            writeln!(output)?;
+        }
+        writeln!(output, "        )")?;
+        writeln!(output, "    }}")?;
+        writeln!(output, "}}")?;
+        writeln!(output)?;
+
+        // Engine factory function
+        writeln!(output, "fun getDropbearEngine(worldPointer: COpaquePointer?, currentEntity: Long?): DropbearEngine {{")?;
+        writeln!(output, "    val nativeEngine = NativeEngine()")?;
+        writeln!(output, "    nativeEngine.init(worldPointer)")?;
+        writeln!(output, "    val dropbearEngine = DropbearEngine(nativeEngine, if (currentEntity == null) null else EntityRef(EntityId(currentEntity)))")?;
+        writeln!(output, "    return dropbearEngine")?;
+        writeln!(output, "}}")?;
+        writeln!(output)?;
+
+        // Load function
+        writeln!(output, "@CName(\"dropbear_load\")")?;
+        writeln!(output, "fun loadScriptByTag(worldPointer: COpaquePointer?, currentEntity: Long?, tag: String?) {{")?;
+        writeln!(output, "    if (tag == null) return")?;
+        writeln!(output, "    val scripts = scriptInstances.filter {{ it.tags.contains(tag) }}")?;
+        writeln!(output, "    val engine = getDropbearEngine(worldPointer, currentEntity)")?;
+        writeln!(output, "    for (script in scripts) {{")?;
+        writeln!(output, "        script.script.load(engine)")?;
+        writeln!(output, "    }}")?;
+        writeln!(output, "}}")?;
+        writeln!(output)?;
+
+        // Update function
+        writeln!(output, "@CName(\"dropbear_update\")")?;
+        writeln!(output, "fun updateScriptByTag(worldPointer: COpaquePointer?, currentEntity: Long?, tag: String?, deltaTime: Double) {{")?;
+        writeln!(output, "    if (tag == null) return")?;
+        writeln!(output, "    val scripts = scriptInstances.filter {{ it.tags.contains(tag) }}")?;
+        writeln!(output, "    val engine = getDropbearEngine(worldPointer, currentEntity)")?;
+        writeln!(output, "    for (script in scripts) {{")?;
+        writeln!(output, "        script.script.update(engine, deltaTime.toFloat())")?;
+        writeln!(output, "    }}")?;
+        writeln!(output, "}}")?;
+        writeln!(output)?;
+
+        // Destroy function
+        writeln!(output, "@CName(\"dropbear_destroy\")")?;
+        writeln!(output, "fun destroyScriptByTag(worldPointer: COpaquePointer?, currentEntity: Long?, tag: String?) {{")?;
+        writeln!(output, "    if (tag == null) return")?;
+        writeln!(output, "    val scripts = scriptInstances.filter {{ it.tags.contains(tag) }}")?;
+        writeln!(output, "    val engine = getDropbearEngine(worldPointer, currentEntity)")?;
+        writeln!(output, "    for (script in scripts) {{")?;
+        writeln!(output, "        script.script.destroy(engine)")?;
+        writeln!(output, "    }}")?;
+        writeln!(output, "}}")?;
+
+        Ok(output)
+    }
+}
\ No newline at end of file
diff --git a/magna-carta/src/lib.rs b/magna-carta/src/lib.rs
new file mode 100644
index 0000000..50fb032
--- /dev/null
+++ b/magna-carta/src/lib.rs
@@ -0,0 +1,329 @@
+mod generator;
+
+use std::path::PathBuf;
+use tree_sitter::{Parser, Query, QueryCursor};
+
+#[derive(Debug, Clone)]
+pub struct ScriptManifest {
+    items: Vec<ManifestItem>,
+}
+
+impl Default for ScriptManifest {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl ScriptManifest {
+    pub fn new() -> Self {
+        Self {
+            items: Vec::new(),
+        }
+    }
+
+    pub fn add_item(&mut self, item: ManifestItem) {
+        self.items.push(item);
+    }
+
+    pub fn items(&self) -> &[ManifestItem] {
+        &self.items
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct ManifestItem {
+    /// Fully qualified class name
+    ///
+    /// Example: `foo.bar.Enemy`
+    fqcn: String,
+    /// Simple name of the class
+    ///
+    /// Example: `Enemy`
+    simple_name: String,
+    /// Tags to identify the class
+    ///
+    /// Example: `["goomba", "shell"]`
+    tags: Vec<String>,
+    /// Path to the source file in reference to the project root
+    ///
+    /// Example: `src/commonMain/kotlin/foo/bar/Enemy.kt`
+    file_path: PathBuf,
+}
+
+impl ManifestItem {
+    pub fn new(
+        fqcn: String,
+        simple_name: String,
+        tags: Vec<String>,
+        file_path: PathBuf,
+    ) -> Self {
+        Self {
+            fqcn,
+            simple_name,
+            tags,
+            file_path,
+        }
+    }
+
+    pub fn fqcn(&self) -> &str {
+        &self.fqcn
+    }
+
+    pub fn simple_name(&self) -> &str {
+        &self.simple_name
+    }
+
+    pub fn tags(&self) -> &[String] {
+        &self.tags
+    }
+
+    pub fn file_path(&self) -> &PathBuf {
+        &self.file_path
+    }
+}
+
+pub struct KotlinProcessor {
+    parser: Parser,
+}
+
+impl KotlinProcessor {
+    pub fn new() -> anyhow::Result<Self> {
+        let mut parser = Parser::new();
+        parser.set_language(&tree_sitter_kotlin::language())?;
+        Ok(Self { parser })
+    }
+
+    pub fn process_file(
+        &mut self,
+        source_code: &str,
+        file_path: PathBuf,
+    ) -> anyhow::Result<Option<ManifestItem>> {
+        let tree = self
+            .parser
+            .parse(source_code, None)
+            .ok_or_else(|| anyhow::anyhow!("Failed to parse source code"))?;
+
+        let root_node = tree.root_node();
+
+        let package = self.extract_package(root_node, source_code)?;
+
+        if let Some(class_info) = self.extract_class_info(root_node, source_code)? {
+            let (class_name, tags) = class_info;
+
+            let fqcn = if package.is_empty() {
+                class_name.clone()
+            } else {
+                format!("{}.{}", package, class_name)
+            };
+
+            return Ok(Some(ManifestItem::new(
+                fqcn,
+                class_name,
+                tags,
+                file_path,
+            )));
+        }
+
+        Ok(None)
+    }
+
+    fn extract_package(
+        &self,
+        root_node: tree_sitter::Node,
+        source: &str,
+    ) -> anyhow::Result<String> {
+        let query = Query::new(
+            &tree_sitter_kotlin::language(),
+            r#"
+            (package_header
+              (identifier) @package)
+            "#,
+        )?;
+
+        let mut cursor = QueryCursor::new();
+        let mut matches = cursor.matches(&query, root_node, source.as_bytes());
+
+        if let Some(match_) = matches.next() && let Some(capture) = match_.captures.first() {
+            let package_node = capture.node;
+            let package_text = package_node.utf8_text(source.as_bytes())?;
+
+            return Ok(package_text.replace('\n', "").trim().to_string());
+        }
+
+        Ok(String::new())
+    }
+
+    fn extract_class_info(
+        &self,
+        root_node: tree_sitter::Node,
+        source: &str,
+    ) -> anyhow::Result<Option<(String, Vec<String>)>> {
+        let query = Query::new(
+            &tree_sitter_kotlin::language(),
+            r#"
+        ; Case 1: @Runnable (no parentheses)
+        (class_declaration
+          (modifiers
+            (annotation
+              (user_type
+                (type_identifier) @annotation_name)
+              (#eq? @annotation_name "Runnable")))
+          (type_identifier) @class_name)
+
+        ; Case 2: @Runnable(...) (with parentheses)
+        (class_declaration
+          (modifiers
+            (annotation
+              (constructor_invocation
+                (user_type
+                  (type_identifier) @annotation_name2)
+                (value_arguments)? @value_args)
+              (#eq? @annotation_name2 "Runnable")))
+          (type_identifier) @class_name2)
+        "#,
+        )?;
+
+        let mut cursor = QueryCursor::new();
+        let matches = cursor.matches(&query, root_node, source.as_bytes());
+
+        let annotation_name_idx = query.capture_index_for_name("annotation_name");
+        let annotation_name2_idx = query.capture_index_for_name("annotation_name2");
+        let class_name_idx = query.capture_index_for_name("class_name");
+        let class_name2_idx = query.capture_index_for_name("class_name2");
+        let value_args_idx = query.capture_index_for_name("value_args");
+
+        for match_ in matches {
+            let mut class_name = String::new();
+            let mut found_runnable = false;
+            let mut value_args_node = None;
+
+            for capture in match_.captures {
+                let text = capture.node.utf8_text(source.as_bytes())?;
+
+                // case 1 (no brackets)
+                if let Some(idx) = annotation_name_idx && capture.index == idx && text == "Runnable" {
+                    found_runnable = true;
+                }
+
+                // case 2 (with brackets)
+                if let Some(idx) = annotation_name2_idx && capture.index == idx && text == "Runnable" {
+                    found_runnable = true;
+                }
+
+                // class names
+                if let Some(idx) = class_name_idx && capture.index == idx {
+                    class_name = text.to_string();
+                }
+
+                if let Some(idx) = class_name2_idx && capture.index == idx {
+                    class_name = text.to_string();
+                }
+
+
+                // case 2 value args
+                if let Some(idx) = value_args_idx && capture.index == idx {
+                    value_args_node = Some(capture.node);
+                }
+
+            }
+
+            if found_runnable && !class_name.is_empty() {
+                let tags = if let Some(value_args) = value_args_node {
+                    self.extract_tags_from_value_args(value_args, source)?
+                } else {
+                    Vec::new()
+                };
+
+                return Ok(Some((class_name, tags)));
+            }
+        }
+
+        Ok(None)
+    }
+
+    fn extract_tags_from_value_args(
+        &self,
+        value_args_node: tree_sitter::Node,
+        source: &str,
+    ) -> anyhow::Result<Vec<String>> {
+        let mut tags = Vec::new();
+
+        let mut cursor = value_args_node.walk();
+        for value_arg in value_args_node.children(&mut cursor) {
+            if value_arg.kind() == "value_argument" {
+                let mut arg_cursor = value_arg.walk();
+                for child in value_arg.children(&mut arg_cursor) {
+                    // Case 1: Direct string literal (vararg style)
+                    if child.kind() == "string_literal" {
+                        let text = child.utf8_text(source.as_bytes())?;
+                        let clean_tag = text.trim_matches(|c| c == '"' || c == '\'').to_string();
+                        if !clean_tag.is_empty() {
+                            tags.push(clean_tag);
+                        }
+                    }
+                    else if child.kind() == "collection_literal" {
+                        let mut collection_cursor = child.walk();
+                        for collection_item in child.children(&mut collection_cursor) {
+                            if collection_item.kind() == "string_literal" {
+                                let text = collection_item.utf8_text(source.as_bytes())?;
+                                let clean_tag = text.trim_matches(|c| c == '"' || c == '\'').to_string();
+                                if !clean_tag.is_empty() {
+                                    tags.push(clean_tag);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        Ok(tags)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_process_player_class() {
+        let source = r#"
+package com.dropbear
+
+@Runnable(["player", "movement"])
+class Player: System {
+    override fun load(engine: DropbearEngine) {
+        TODO("Not yet implemented")
+    }
+    override fun update(engine: DropbearEngine, deltaTime: Float) {
+
+        TODO("Not yet implemented")
+
+    }
+
+    override fun destroy(engine: DropbearEngine) {
+
+        TODO("Not yet implemented")
+
+    }
+
+}
+"#;
+
+        let mut processor = KotlinProcessor::new().unwrap();
+        let result = processor
+            .process_file(source, PathBuf::from("src/main/kotlin/com/dropbear/Player.kt"))
+            .unwrap();
+
+        assert!(result.is_some());
+        let item = result.unwrap();
+
+        assert_eq!(item.fqcn(), "com.dropbear.Player");
+        assert_eq!(item.simple_name(), "Player");
+        assert_eq!(item.tags(), &["player", "movement"]);
+        assert_eq!(
+            item.file_path(),
+            &PathBuf::from("src/main/kotlin/com/dropbear/Player.kt")
+        );
+    }
+}
\ No newline at end of file
diff --git a/resources/templates/gradle_template.tar.gz b/resources/templates/gradle_template.tar.gz
new file mode 100644
index 0000000..34148e6
Binary files /dev/null and b/resources/templates/gradle_template.tar.gz differ
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 5354fc0..d12f0c0 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,4 +1,8 @@
-plugins {
-    id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
-}
-rootProject.name = "dropbear"
\ No newline at end of file
+rootProject.name = "dropbear"
+
+pluginManagement {
+    repositories {
+        mavenCentral()
+        gradlePluginPortal()
+    }
+}
\ No newline at end of file
diff --git a/src/README.md b/src/README.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/README.md
diff --git a/src/commonMain/kotlin/com/dropbear/DropbearEngine.kt b/src/commonMain/kotlin/com/dropbear/DropbearEngine.kt
new file mode 100644
index 0000000..e599e40
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/DropbearEngine.kt
@@ -0,0 +1,22 @@
+package com.dropbear
+
+import com.dropbear.ffi.NativeEngine
+
+class DropbearEngine(val native: NativeEngine, val currentEntity: EntityRef? = null) {
+    private val globalScripts = mutableListOf<System>()
+
+    fun init() {
+    }
+
+    fun getEntity(label: String): Result<EntityRef> {
+        val entityId = native.getEntity(label)
+        return (when (entityId) {
+            null -> {
+                Result.failure(Exception("JNI returned null"))
+            }
+            else -> {
+                Result.success(EntityRef(EntityId(entityId)))
+            }
+        })
+    }
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/EntityId.kt b/src/commonMain/kotlin/com/dropbear/EntityId.kt
new file mode 100644
index 0000000..b043b58
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/EntityId.kt
@@ -0,0 +1,3 @@
+package com.dropbear
+
+data class EntityId(val id: ULong)
diff --git a/src/commonMain/kotlin/com/dropbear/EntityRef.kt b/src/commonMain/kotlin/com/dropbear/EntityRef.kt
new file mode 100644
index 0000000..d4a17ee
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/EntityRef.kt
@@ -0,0 +1,22 @@
+package com.dropbear
+
+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(val id: EntityId = EntityId(0u)) {
+    fun setPosition(position: Vector3D, engine: DropbearEngine) {}
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/Example.kt b/src/commonMain/kotlin/com/dropbear/Example.kt
new file mode 100644
index 0000000..1c84b73
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/Example.kt
@@ -0,0 +1,21 @@
+import com.dropbear.DropbearEngine
+import com.dropbear.EntityId
+import com.dropbear.System
+import com.dropbear.ProjectScriptingMetadata
+import com.dropbear.Runnable
+import com.dropbear.ScriptRegistration
+
+@Runnable(["player"])
+class Player: System {
+    override fun load(engine: DropbearEngine) {
+        TODO("Not yet implemented")
+    }
+
+    override fun update(engine: DropbearEngine, deltaTime: Float) {
+        TODO("Not yet implemented")
+    }
+
+    override fun destroy(engine: DropbearEngine) {
+        TODO("Not yet implemented")
+    }
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/ProjectScriptingMetadata.kt b/src/commonMain/kotlin/com/dropbear/ProjectScriptingMetadata.kt
new file mode 100644
index 0000000..daacac0
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/ProjectScriptingMetadata.kt
@@ -0,0 +1,5 @@
+package com.dropbear
+
+interface ProjectScriptingMetadata {
+    fun getScripts(): List<ScriptRegistration>
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/Runnable.kt b/src/commonMain/kotlin/com/dropbear/Runnable.kt
new file mode 100644
index 0000000..c316755
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/Runnable.kt
@@ -0,0 +1,5 @@
+package com.dropbear
+
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.SOURCE)
+annotation class Runnable(val tags: Array<String> = [])
diff --git a/src/commonMain/kotlin/com/dropbear/ScriptRegistration.kt b/src/commonMain/kotlin/com/dropbear/ScriptRegistration.kt
new file mode 100644
index 0000000..63b8e57
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/ScriptRegistration.kt
@@ -0,0 +1,6 @@
+package com.dropbear
+
+data class ScriptRegistration(
+    val tags: List<String>,
+    val script: System
+)
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/System.kt b/src/commonMain/kotlin/com/dropbear/System.kt
new file mode 100644
index 0000000..48bd854
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/System.kt
@@ -0,0 +1,7 @@
+package com.dropbear
+
+interface System {
+    fun load(engine: DropbearEngine)
+    fun update(engine: DropbearEngine, deltaTime: Float)
+    fun destroy(engine: DropbearEngine)
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/ffi/NativeEngine.kt b/src/commonMain/kotlin/com/dropbear/ffi/NativeEngine.kt
new file mode 100644
index 0000000..e87064c
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/ffi/NativeEngine.kt
@@ -0,0 +1,7 @@
+package com.dropbear.ffi
+
+import com.dropbear.EntityRef
+
+expect class NativeEngine {
+    fun getEntity(label: String): ULong?
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/math/Quaternion.kt b/src/commonMain/kotlin/com/dropbear/math/Quaternion.kt
new file mode 100644
index 0000000..97779eb
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/math/Quaternion.kt
@@ -0,0 +1,11 @@
+package com.dropbear.math
+
+typealias QuaternionD = Quaternion<Double>
+
+class Quaternion<T: Number>(var x: T, var y: T, var z: T, var w: T) {
+    companion object {
+        fun identity(): Quaternion<Double> {
+            return Quaternion(0.0, 0.0, 0.0, 1.0)
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/math/Vector3.kt b/src/commonMain/kotlin/com/dropbear/math/Vector3.kt
new file mode 100644
index 0000000..822dd23
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/math/Vector3.kt
@@ -0,0 +1,29 @@
+package com.dropbear.math
+
+/**
+ * A class for holding a vector of `3` values of the same type.
+ */
+class Vector3<T: Number>(var x: T, var y: T, var z: T) {
+    companion object {
+        /**
+         * Creates a new [com.dropbear.math.Vector3] of type `T` with one value.
+         */
+        fun <T: Number> uniform(value: T): Vector3<T> {
+            return Vector3(value, value, value)
+        }
+
+        /**
+         * Creates a [com.dropbear.math.Vector3] of type [Double] filled with only zeroes
+         */
+        fun zero(): Vector3<Double> {
+            return Vector3(0.0, 0.0, 0.0)
+        }
+    }
+
+    fun asDoubleVector(): Vector3<Double> {
+        return Vector3(this.x.toDouble(), this.y.toDouble(), this.z.toDouble())
+    }
+
+}
+
+typealias Vector3D = Vector3<Double>
\ No newline at end of file
diff --git a/src/dropbear.def b/src/dropbear.def
new file mode 100644
index 0000000..5a38050
--- /dev/null
+++ b/src/dropbear.def
@@ -0,0 +1,4 @@
+package: com.dropbear.ffi.generated
+headers: dropbear.h
+headerFilter: dropbear.h
+libraryPaths: src
\ No newline at end of file
diff --git a/src/jvmMain/java/com/dropbear/ffi/JNINative.java b/src/jvmMain/java/com/dropbear/ffi/JNINative.java
new file mode 100644
index 0000000..6429413
--- /dev/null
+++ b/src/jvmMain/java/com/dropbear/ffi/JNINative.java
@@ -0,0 +1,9 @@
+package com.dropbear.ffi;
+
+public class JNINative {
+    static {
+        System.loadLibrary("eucalyptus_core");
+    }
+
+    public static native long getEntity(long handle, String label);
+}
diff --git a/src/jvmMain/kotlin/com/dropbear/ffi/NativeEngine.jvm.kt b/src/jvmMain/kotlin/com/dropbear/ffi/NativeEngine.jvm.kt
new file mode 100644
index 0000000..512cf09
--- /dev/null
+++ b/src/jvmMain/kotlin/com/dropbear/ffi/NativeEngine.jvm.kt
@@ -0,0 +1,22 @@
+package com.dropbear.ffi
+
+actual class NativeEngine {
+    private var worldHandle: ULong = 0u
+
+    actual fun getEntity(label: String): ULong? {
+        val result = JNINative.getEntity(worldHandle.toLong(), label)
+        return if (result < 0) {
+            null
+        } else {
+            result.toULong()
+        }
+    }
+
+    fun init(handle: ULong) {
+        this.worldHandle = handle
+        if (this.worldHandle == 0uL) {
+            println("NativeEngine: Error - Invalid world handle received!")
+            return
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/dropbear/DropbearEngine.kt b/src/main/kotlin/com/dropbear/DropbearEngine.kt
deleted file mode 100644
index 34c932f..0000000
--- a/src/main/kotlin/com/dropbear/DropbearEngine.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-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 {
-    /**
-     * 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()
-    }
-
-    /**
-     * Fetches the currently active entity the script is attached to.
-     *
-     * If there is no entity this script is attached to, it will return
-     * a `null` in the form of a [Result]
-     */
-    fun getActiveEntity(): Result<EntityRef> {
-        // This would need additional native support to get entity ID and label
-        return Result.failure(Exception("Function not fully implemented - requires entity label support"))
-    }
-
-    /**
-     * Fetches an entity based on its label in the editor.
-     *
-     * If there is no entity under that label, it will return a
-     * `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
-    }
-}
diff --git a/src/main/kotlin/com/dropbear/EntityRef.kt b/src/main/kotlin/com/dropbear/EntityRef.kt
deleted file mode 100644
index d01228c..0000000
--- a/src/main/kotlin/com/dropbear/EntityRef.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.dropbear
-
-import com.dropbear.math.Transform
-import com.dropbear.math.Vector3D
-
-/**
- * A class to hold a reference to an entity.
- */
-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
deleted file mode 100644
index 2efd4e5..0000000
--- a/src/main/kotlin/com/dropbear/Example.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-package com.dropbear
-
-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() {
-        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))
-        }
-    }
-}
diff --git a/src/main/kotlin/com/dropbear/RunnableScript.kt b/src/main/kotlin/com/dropbear/RunnableScript.kt
deleted file mode 100644
index d7cb338..0000000
--- a/src/main/kotlin/com/dropbear/RunnableScript.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.dropbear
-
-/**
- * The basic interface that all classes implement for the class to be run.
- */
-interface RunnableScript {
-    var engine: DropbearEngine
-    /**
-     * A function that is run once during the lifetime of the entity.
-     *
-     * It can be used to set initial properties such as health and more.
-     *
-     * ALl classes that implement RunnableScript need to implement the load function.
-     */
-    fun load()
-
-    /**
-     * A function that is run every frame during the lifetime of the entity.
-     */
-    fun update()
-}
\ 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
deleted file mode 100644
index 90c45fb..0000000
--- a/src/main/kotlin/com/dropbear/ffi/NativeEngine.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package com.dropbear.ffi;
-
-/**
- * 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
-    // =============================================================================
-    
-    /**
-     * Set the current scripting context (internal use only)
-     */
-    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();
-    
-    /**
-     * Get the current mouse Y position
-     */
-    public static native double getMouseY();
-}
diff --git a/src/main/kotlin/com/dropbear/math/Quaternion.kt b/src/main/kotlin/com/dropbear/math/Quaternion.kt
deleted file mode 100644
index 97779eb..0000000
--- a/src/main/kotlin/com/dropbear/math/Quaternion.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.dropbear.math
-
-typealias QuaternionD = Quaternion<Double>
-
-class Quaternion<T: Number>(var x: T, var y: T, var z: T, var w: T) {
-    companion object {
-        fun identity(): Quaternion<Double> {
-            return Quaternion(0.0, 0.0, 0.0, 1.0)
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/main/kotlin/com/dropbear/math/Transform.kt b/src/main/kotlin/com/dropbear/math/Transform.kt
deleted file mode 100644
index 69cec1c..0000000
--- a/src/main/kotlin/com/dropbear/math/Transform.kt
+++ /dev/null
@@ -1,116 +0,0 @@
-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 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
-        )
-    }
-}
diff --git a/src/main/kotlin/com/dropbear/math/Vector3.kt b/src/main/kotlin/com/dropbear/math/Vector3.kt
deleted file mode 100644
index 822dd23..0000000
--- a/src/main/kotlin/com/dropbear/math/Vector3.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.dropbear.math
-
-/**
- * A class for holding a vector of `3` values of the same type.
- */
-class Vector3<T: Number>(var x: T, var y: T, var z: T) {
-    companion object {
-        /**
-         * Creates a new [com.dropbear.math.Vector3] of type `T` with one value.
-         */
-        fun <T: Number> uniform(value: T): Vector3<T> {
-            return Vector3(value, value, value)
-        }
-
-        /**
-         * Creates a [com.dropbear.math.Vector3] of type [Double] filled with only zeroes
-         */
-        fun zero(): Vector3<Double> {
-            return Vector3(0.0, 0.0, 0.0)
-        }
-    }
-
-    fun asDoubleVector(): Vector3<Double> {
-        return Vector3(this.x.toDouble(), this.y.toDouble(), this.z.toDouble())
-    }
-
-}
-
-typealias Vector3D = Vector3<Double>
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/ffi/Generated.kt b/src/nativeMain/kotlin/com/dropbear/ffi/Generated.kt
new file mode 100644
index 0000000..8fcfdc4
--- /dev/null
+++ b/src/nativeMain/kotlin/com/dropbear/ffi/Generated.kt
@@ -0,0 +1,72 @@
+// File to get an idea of what is generated
+@file:OptIn(ExperimentalForeignApi::class, ExperimentalNativeApi::class)
+import com.dropbear.DropbearEngine
+import com.dropbear.EntityId
+import com.dropbear.EntityRef
+import com.dropbear.ProjectScriptingMetadata
+import com.dropbear.ScriptRegistration
+import com.dropbear.ffi.NativeEngine
+import kotlinx.cinterop.COpaquePointer
+import kotlinx.cinterop.ExperimentalForeignApi
+import kotlin.experimental.ExperimentalNativeApi
+import kotlin.native.CName
+
+// import /* CLASS */
+
+private val scriptInstances: List<ScriptRegistration> by lazy {
+    Metadata().getScripts()
+}
+
+class Metadata: ProjectScriptingMetadata {
+    override fun getScripts(): List<ScriptRegistration> {
+        return listOf(
+            ScriptRegistration(
+                tags = listOf("player"),
+                script = Player()
+            ),
+
+//            ScriptRegistration(
+//                tags = /* TAGS */,
+//                script = /* CLASS NAME */
+//            ),
+
+        )
+    }
+}
+
+fun getDropbearEngine(worldPointer: COpaquePointer?, currentEntity: Long?): DropbearEngine {
+    val nativeEngine = NativeEngine()
+    nativeEngine.init(worldPointer)
+    val dropbearEngine = DropbearEngine(nativeEngine, if (currentEntity == null) null else EntityRef(EntityId(currentEntity.toULong())))
+    return dropbearEngine
+}
+
+@CName("dropbear_load")
+fun loadScriptByTag(worldPointer: COpaquePointer?, currentEntity: Long?, tag: String?) {
+    if (tag == null) return
+    val scripts = scriptInstances.filter { it.tags.contains(tag) }
+    val engine = getDropbearEngine(worldPointer, currentEntity)
+    for (script in scripts) {
+        script.script.load(engine)
+    }
+}
+
+@CName("dropbear_update")
+fun updateScriptByTag(worldPointer: COpaquePointer?, currentEntity: Long?, tag: String?, deltaTime: Double) {
+    if (tag == null) return
+    val scripts = scriptInstances.filter { it.tags.contains(tag) }
+    val engine = getDropbearEngine(worldPointer, currentEntity)
+    for (script in scripts) {
+        script.script.update(engine, deltaTime.toFloat())
+    }
+}
+
+@CName("dropbear_destroy")
+fun destroyScriptByTag(worldPointer: COpaquePointer?, currentEntity: Long?, tag: String?) {
+    if (tag == null) return
+    val scripts = scriptInstances.filter { it.tags.contains(tag) }
+    val engine = getDropbearEngine(worldPointer, currentEntity)
+    for (script in scripts) {
+        script.script.destroy(engine)
+    }
+}
diff --git a/src/nativeMain/kotlin/com/dropbear/ffi/NativeEngine.native.kt b/src/nativeMain/kotlin/com/dropbear/ffi/NativeEngine.native.kt
new file mode 100644
index 0000000..b78e78d
--- /dev/null
+++ b/src/nativeMain/kotlin/com/dropbear/ffi/NativeEngine.native.kt
@@ -0,0 +1,35 @@
+@file:OptIn(ExperimentalForeignApi::class, ExperimentalNativeApi::class)
+@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
+
+package com.dropbear.ffi
+
+import co.touchlab.kermit.Logger
+import com.dropbear.ffi.generated.dropbear_get_entity
+import kotlinx.cinterop.*
+import kotlin.experimental.ExperimentalNativeApi
+
+actual class NativeEngine {
+    private var worldHandle: COpaquePointer? = null
+
+    fun init(handle: COpaquePointer?) {
+        this.worldHandle = handle
+        if (this.worldHandle == null) {
+            Logger.i("NativeEngine: Error - Invalid world handle received!")
+        } else {
+            Logger.i("NativeEngine: Initialized with world handle")
+        }
+    }
+
+    actual fun getEntity(label: String): ULong? {
+        val world = worldHandle ?: return null
+        memScoped {
+            val outEntity = alloc<ULongVar>()
+            val result = dropbear_get_entity(
+                label = label,
+                world_ptr = world.reinterpret(),
+                out_entity = outEntity.ptr
+            )
+            return if (result == 0) outEntity.value else null
+        }
+    }
+}
\ No newline at end of file