kitgit

tirbofish/dropbear · diff

09d5dab · tk

added docs with dokka, added publishing (mavenLocal) because fuck dealing with namespacing. worked a bunch on native functions and rust. also got native library linking working, a better gradle project and *soon to be* some more updated UI to allow for better script attachment. a lot of stuff a broken, but thats an issue for later me.

Unverified

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 ba1ab3d..ffc1a5a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,6 +53,6 @@ Cargo.lock
 
 .idea/
 
-src/*.dll
-src/*.dylib
-src/*.so
\ No newline at end of file
+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/.run/generateJniHeaders.run.xml b/.run/generateJniHeaders.run.xml
deleted file mode 100644
index acc9512..0000000
--- a/.run/generateJniHeaders.run.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<component name="ProjectRunConfigurationManager">
-  <configuration default="false" name="generateJniHeaders" 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="generateJniHeaders" />
-        </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/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 8835271..8b53e2a 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)_
 
@@ -44,6 +45,7 @@ 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
 ```
 
diff --git a/build.gradle.kts b/build.gradle.kts
index 3144994..46e7c09 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,6 +1,8 @@
 plugins {
     alias(libs.plugins.kotlinMultiplatform)
     alias(libs.plugins.kotlinxSerialization)
+    `maven-publish`
+    id("org.jetbrains.dokka") version "2.0.0"
 }
 
 group = "com.dropbear"
@@ -10,6 +12,15 @@ repositories {
     mavenCentral()
 }
 
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath("io.github.cdimascio:dotenv-kotlin:6.4.1")
+    }
+}
+
 kotlin {
     jvm()
 
@@ -25,6 +36,27 @@ kotlin {
         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 {
@@ -44,7 +76,7 @@ kotlin {
     sourceSets {
         commonMain {
             dependencies {
-                implementation("co.touchlab:kermit:2.0.4")
+                api("co.touchlab:kermit:2.0.4")
             }
         }
         nativeMain {
@@ -87,4 +119,29 @@ tasks.register<JavaCompile>("generateJniHeaders") {
     }
 
     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 e7f2f9d..9df9909 100644
--- a/eucalyptus-core/Cargo.toml
+++ b/eucalyptus-core/Cargo.toml
@@ -32,9 +32,11 @@ jni.workspace = true
 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/src/scripting.rs b/eucalyptus-core/src/scripting.rs
index 970b0aa..635b6f5 100644
--- a/eucalyptus-core/src/scripting.rs
+++ b/eucalyptus-core/src/scripting.rs
@@ -1,47 +1,92 @@
-mod kmp;
-mod jni;
+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 std::sync::LazyLock;
-use crate::ptr::SafePointer;
+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");
 
-pub struct ScriptManager;
+#[derive(Default)]
+pub enum ScriptTarget {
+    #[default]
+    None,
+    JVM { library_path: PathBuf },
+    Native { library_path: PathBuf },
+}
+
+pub struct ScriptManager {
+    jvm: Option<JavaContext>,
+    library: Option<Library>,
+    script_target: ScriptTarget,
+    entity_tag_database: HashMap<String, Vec<Entity>>,
+}
 
 impl ScriptManager {
     pub fn new() -> anyhow::Result<Self> {
-        Ok(Self)
-        // Err(anyhow::anyhow!("it aint ready yet bozo"))
+        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,
+        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,
-        tags: Vec<String>,
+        tag: String,
         world: &mut World,
         input_state: &InputState,
     ) -> anyhow::Result<()> {
 
+        #[cfg(feature = "jvm")]
+        {
+            if let Some(jvm) = &mut self.jvm {
+                jvm.init(world)?;
+            }
+        }
+
         Err(anyhow::anyhow!("it aint ready yet bozo"))
     }
 
-    pub fn update_entity_script(
+    pub fn update_script(
         &mut self,
         entity_id: hecs::Entity,
-        tags: Vec<String>,
         world: &mut World,
         input_state: &InputState,
         dt: f32,
diff --git a/eucalyptus-core/src/scripting/jni.rs b/eucalyptus-core/src/scripting/jni.rs
index 4997a86..7fe40e9 100644
--- a/eucalyptus-core/src/scripting/jni.rs
+++ b/eucalyptus-core/src/scripting/jni.rs
@@ -1,2 +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
index 943f2af..0cdd567 100644
--- a/eucalyptus-core/src/scripting/kmp.rs
+++ b/eucalyptus-core/src/scripting/kmp.rs
@@ -1,28 +1,48 @@
 //! Deals with Kotlin/Native library loading for different platforms.
 
-use std::ffi::c_char;
-use hecs::World;
+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 extern "C" fn dropbear_get_entity(label: *const c_char, world_ptr: *const World) -> usize {
-    if world_ptr.is_null() {
-        log::debug!("World pointer is null");
-        return 0;
+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;
     }
 
-    unsafe {
-        let world = &*world_ptr;
+    let world = unsafe { &*world_ptr };
 
-        for (id, entity) in world.query::<&AdoptedEntity>().iter() {
-            if let Ok(label) = std::ffi::CStr::from_ptr(label).to_str()
-                && entity.model.label == label
-            {
-                log::debug!("Found entity with label: {}", label);
-                return id.id() as usize;
+    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;
             }
         }
-        log::warn!("Entity with label: {} not found", std::ffi::CStr::from_ptr(label).to_str().unwrap());
-        0
+    };
+
+    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 6b936e7..5ced5bb 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,
diff --git a/eucalyptus-editor/src/editor/dock.rs b/eucalyptus-editor/src/editor/dock.rs
index cf0fbbe..596ecc4 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};
@@ -448,91 +448,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/scene.rs b/eucalyptus-editor/src/editor/scene.rs
index 492ebf6..2eb5306 100644
--- a/eucalyptus-editor/src/editor/scene.rs
+++ b/eucalyptus-editor/src/editor/scene.rs
@@ -119,9 +119,8 @@ 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.clone(),
                     &mut self.world,
                     &self.input_state,
                     dt,
diff --git a/eucalyptus-editor/src/signal.rs b/eucalyptus-editor/src/signal.rs
index 198810b..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,6 +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::ScriptTarget;
 use eucalyptus_core::spawn::{push_pending_spawn, PendingSpawn};
 use crate::editor::{ComponentType, Editor, EditorState, EntityType, PendingSpawn2, Signal, UndoableAction};
 
@@ -125,38 +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 for entity {:?} with tags: {:?}",
-                            entity_id,
-                            script.tags
+                            "Initialising script for tag {:?} with entities: {:?}",
+                            tag,
+                            entities
                         );
 
-                        match self.script_manager.load_script() {
-                            Ok(_) => {
-                                if let Err(e) = self.script_manager.init_entity_script(
-                                    entity_id,
-                                    script.tags.clone(),
-                                    &mut self.world,
-                                    &self.input_state,
-                                ) {
-                                    log::warn!(
-                                        "Failed to initialise script for entity {:?}: {}",
-                                        entity_id,
-                                        e
-                                    );
-                                    self.signal = Signal::StopPlaying;
-                                } else {
-                                    success_without_console!(
+                        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));
+                            } 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 for {:?} with tags {:?} because {}", entity_id, script.tags, e);
+                                log::info!("You are in play mode now! Press Escape to exit");
                             }
                         }
                     }
@@ -454,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 29e08e8..9b79a16 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1 +1,3 @@
-kotlin.code.style=official
\ No newline at end of file
+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/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/src/commonMain/kotlin/com/dropbear/DropbearEngine.kt b/src/commonMain/kotlin/com/dropbear/DropbearEngine.kt
index 502bc9d..e599e40 100644
--- a/src/commonMain/kotlin/com/dropbear/DropbearEngine.kt
+++ b/src/commonMain/kotlin/com/dropbear/DropbearEngine.kt
@@ -1,13 +1,11 @@
 package com.dropbear
 
 import com.dropbear.ffi.NativeEngine
-import getProjectScriptMetadata
 
-class DropbearEngine(val native: NativeEngine) {
+class DropbearEngine(val native: NativeEngine, val currentEntity: EntityRef? = null) {
     private val globalScripts = mutableListOf<System>()
 
     fun init() {
-        val scriptRegistration = getProjectScriptMetadata()
     }
 
     fun getEntity(label: String): Result<EntityRef> {
diff --git a/src/commonMain/kotlin/com/dropbear/EntityRef.kt b/src/commonMain/kotlin/com/dropbear/EntityRef.kt
index b49852f..d4a17ee 100644
--- a/src/commonMain/kotlin/com/dropbear/EntityRef.kt
+++ b/src/commonMain/kotlin/com/dropbear/EntityRef.kt
@@ -18,8 +18,5 @@ import com.dropbear.math.Vector3D
  * by [EntityRef], which will require a reference to [DropbearEngine] to push the commands.
  */
 class EntityRef(val id: EntityId = EntityId(0u)) {
-    /**
-     * Sets the position of the entity by a Vector
-     */
     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
index 6443e59..1c84b73 100644
--- a/src/commonMain/kotlin/com/dropbear/Example.kt
+++ b/src/commonMain/kotlin/com/dropbear/Example.kt
@@ -2,12 +2,10 @@ import com.dropbear.DropbearEngine
 import com.dropbear.EntityId
 import com.dropbear.System
 import com.dropbear.ProjectScriptingMetadata
+import com.dropbear.Runnable
 import com.dropbear.ScriptRegistration
 
-fun playerMovement(engine: DropbearEngine, entityId: EntityId, deltaTime: Double) {
-
-}
-
+@Runnable(["player"])
 class Player: System {
     override fun load(engine: DropbearEngine) {
         TODO("Not yet implemented")
@@ -20,17 +18,4 @@ class Player: System {
     override fun destroy(engine: DropbearEngine) {
         TODO("Not yet implemented")
     }
-}
-
-class Metadata : ProjectScriptingMetadata {
-    override fun getScripts(): List<ScriptRegistration> {
-        return listOf (
-            ScriptRegistration(
-                tags = listOf("player", "movement"),
-                script = Player()
-            ),
-        )
-    }
-}
-
-fun getProjectScriptMetadata(): ProjectScriptingMetadata = Metadata()
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/ProjectScriptingMetadata.kt b/src/commonMain/kotlin/com/dropbear/ProjectScriptingMetadata.kt
index 3965904..daacac0 100644
--- a/src/commonMain/kotlin/com/dropbear/ProjectScriptingMetadata.kt
+++ b/src/commonMain/kotlin/com/dropbear/ProjectScriptingMetadata.kt
@@ -1,16 +1,5 @@
 package com.dropbear
 
-/**
- * A mandatory class defined in a eucalyptus project that "discovers" and attaches
- * tags to files for the registry to use.
- */
 interface ProjectScriptingMetadata {
-    /**
-     * A function that is placed in Manifest.kt, and allows you to attach the required
-     * tags to the correct files.
-     *
-     * The original method was to use Kotlin Symbol Processing, however Kotlin/Native
-     * doesn't support KSP.
-     */
     fun getScripts(): List<ScriptRegistration>
 }
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/Registry.kt b/src/commonMain/kotlin/com/dropbear/Registry.kt
deleted file mode 100644
index 533f268..0000000
--- a/src/commonMain/kotlin/com/dropbear/Registry.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.dropbear
-
-internal object Registry {
-    private val _entries = mutableListOf<ScriptEntry>()
-    val entries: List<ScriptEntry> get() = _entries.toList()
-
-    fun register(entry: ScriptEntry) {
-        _entries.add(entry)
-    }
-
-    fun findByTag(tag: String): List<ScriptEntry> {
-        return _entries.filter { tag in it.tags }
-    }
-
-    internal fun registerAll() {
-    }
-}
\ 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/RunnableScript.kt b/src/commonMain/kotlin/com/dropbear/RunnableScript.kt
deleted file mode 100644
index b10521e..0000000
--- a/src/commonMain/kotlin/com/dropbear/RunnableScript.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.dropbear
-
-/**
- * The basic interface that all classes implement for the class to be run.
- */
-sealed class RunnableScript {
-    /**
-     * 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.
-     */
-    abstract fun load()
-
-    /**
-     * A function that is run every frame during the lifetime of the entity.
-     */
-    abstract fun update()
-}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/ScriptEntry.kt b/src/commonMain/kotlin/com/dropbear/ScriptEntry.kt
deleted file mode 100644
index 6448f56..0000000
--- a/src/commonMain/kotlin/com/dropbear/ScriptEntry.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.dropbear
-
-internal data class ScriptEntry(
-    val tags: List<String>,
-    val functionName: String,
-    val invoker: (DropbearEngine, EntityId, Double) -> Unit
-)
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/ffi/NativeEngine.kt b/src/commonMain/kotlin/com/dropbear/ffi/NativeEngine.kt
index 28c8d95..e87064c 100644
--- a/src/commonMain/kotlin/com/dropbear/ffi/NativeEngine.kt
+++ b/src/commonMain/kotlin/com/dropbear/ffi/NativeEngine.kt
@@ -3,6 +3,5 @@ package com.dropbear.ffi
 import com.dropbear.EntityRef
 
 expect class NativeEngine {
-    fun init(handle: ULong)
     fun getEntity(label: String): ULong?
 }
\ 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
index 2a2e28c..6429413 100644
--- a/src/jvmMain/java/com/dropbear/ffi/JNINative.java
+++ b/src/jvmMain/java/com/dropbear/ffi/JNINative.java
@@ -1,5 +1,9 @@
 package com.dropbear.ffi;
 
 public class JNINative {
-    public native long getEntity(long handle, String label);
+    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
index 47486aa..512cf09 100644
--- a/src/jvmMain/kotlin/com/dropbear/ffi/NativeEngine.jvm.kt
+++ b/src/jvmMain/kotlin/com/dropbear/ffi/NativeEngine.jvm.kt
@@ -2,10 +2,9 @@ package com.dropbear.ffi
 
 actual class NativeEngine {
     private var worldHandle: ULong = 0u
-    private val jni = JNINative()
 
     actual fun getEntity(label: String): ULong? {
-        val result = jni.getEntity(worldHandle.toLong(), label)
+        val result = JNINative.getEntity(worldHandle.toLong(), label)
         return if (result < 0) {
             null
         } else {
@@ -13,7 +12,7 @@ actual class NativeEngine {
         }
     }
 
-    actual fun init(handle: ULong) {
+    fun init(handle: ULong) {
         this.worldHandle = handle
         if (this.worldHandle == 0uL) {
             println("NativeEngine: Error - Invalid world handle received!")
diff --git a/src/nativeMain/kotlin/com/dropbear/ffi/Exports.kt b/src/nativeMain/kotlin/com/dropbear/ffi/Exports.kt
deleted file mode 100644
index ff89e75..0000000
--- a/src/nativeMain/kotlin/com/dropbear/ffi/Exports.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-@file:OptIn(ExperimentalForeignApi::class, ExperimentalNativeApi::class)
-
-package com.dropbear.ffi
-
-import co.touchlab.kermit.Logger
-import com.dropbear.DropbearEngine
-import kotlinx.cinterop.ExperimentalForeignApi
-import kotlin.experimental.ExperimentalNativeApi
-
-@CName("dropbear_entry")
-fun entry(worldHandle: ULong) {
-    Logger.i { "Starting kotlin scripting guest" }
-    val nativeEngine = NativeEngine()
-    nativeEngine.init(worldHandle)
-}
-
-@CName("dropbear_load")
-fun loadScriptByTag(tag: String?) {
-
-}
-
-@CName("dropbear_update")
-fun updateScriptByTag(tag: String?, deltaTime: Double) {
-
-}
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
index d2d366d..b78e78d 100644
--- a/src/nativeMain/kotlin/com/dropbear/ffi/NativeEngine.native.kt
+++ b/src/nativeMain/kotlin/com/dropbear/ffi/NativeEngine.native.kt
@@ -4,23 +4,32 @@
 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: ULong = 0u
+    private var worldHandle: COpaquePointer? = null
 
-    actual fun init(handle: ULong) {
+    fun init(handle: COpaquePointer?) {
         this.worldHandle = handle
-        if (this.worldHandle == 0uL) {
+        if (this.worldHandle == null) {
             Logger.i("NativeEngine: Error - Invalid world handle received!")
-            return
         } else {
-            Logger.i("NativeEngine: Initialized with world handle: ${this.worldHandle}")
+            Logger.i("NativeEngine: Initialized with world handle")
         }
     }
 
     actual fun getEntity(label: String): ULong? {
-        TODO("Not yet implemented")
+        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