diff --git a/adapters/fabric/build.gradle b/adapters/fabric/build.gradle
index ed65288cc..c6b3fdbf9 100644
--- a/adapters/fabric/build.gradle
+++ b/adapters/fabric/build.gradle
@@ -1,9 +1,39 @@
+/*
+ * Iris is a World Generator for Minecraft Servers
+ * Copyright (c) 2026 Arcane Arts (Volmit Software)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
+import org.gradle.api.tasks.compile.JavaCompile
+import org.gradle.jvm.toolchain.JavaLanguageVersion
+
plugins {
id 'java'
+ id 'net.fabricmc.fabric-loom' version '1.17.11'
+ alias(libs.plugins.shadow)
}
+Properties rootProperties = new Properties()
+file('../../gradle.properties').withInputStream { InputStream stream -> rootProperties.load(stream) }
+String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.0.0-26.1'))
+String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.1.2'))
+String fabricLoaderVersion = providers.gradleProperty('fabricLoaderVersion').getOrElse('0.19.3')
+
group = 'art.arcane'
-version = rootProject.version
+version = irisVersion
java {
toolchain {
@@ -11,17 +41,85 @@ java {
}
}
+repositories {
+ mavenCentral()
+ maven {
+ name = 'FabricMC'
+ url = uri('https://maven.fabricmc.net/')
+ }
+ maven { url = uri('https://repo.codemc.org/repository/maven-public/') }
+ maven { url = uri('https://jitpack.io') }
+}
+
+configurations {
+ bundle {
+ canBeConsumed = false
+ canBeResolved = true
+ }
+}
+
+configurations.named('bundle').configure {
+ exclude(group: 'com.google.code.gson')
+ exclude(group: 'com.google.guava')
+ exclude(group: 'commons-io')
+ exclude(group: 'org.apache.commons', module: 'commons-lang3')
+ exclude(group: 'it.unimi.dsi', module: 'fastutil')
+ exclude(group: 'org.slf4j')
+ exclude(group: 'org.apache.logging.log4j')
+ exclude(group: 'de.crazydev22.slimjar.helper')
+ exclude(group: 'de.crazydev22.slimjar')
+ exclude(group: 'io.github.slimjar')
+}
+
dependencies {
- implementation(project(':spi'))
+ minecraft("com.mojang:minecraft:${minecraftVersion}")
+ implementation("net.fabricmc:fabric-loader:${fabricLoaderVersion}")
+ compileOnly('org.slf4j:slf4j-api:2.0.17')
+
+ bundle("art.arcane:core:${irisVersion}")
+ bundle("art.arcane:spi:${irisVersion}")
+ bundle('com.github.VolmitSoftware:VolmLib:master-SNAPSHOT')
+
+ bundle(libs.paralithic)
+ bundle(libs.lru)
+ bundle(libs.kotlin.stdlib)
+ bundle(libs.kotlin.coroutines)
+ bundle(libs.commons.lang)
+ bundle(libs.commons.math3)
+ bundle(libs.caffeine)
+ bundle(libs.lz4)
+ bundle(libs.zip)
+ bundle(libs.sentry)
+ bundle(libs.oshi)
+ bundle(libs.byteBuddy.core)
+ bundle(libs.byteBuddy.agent)
+}
+
+tasks.named('compileJava', JavaCompile).configure {
+ options.encoding = 'UTF-8'
+ options.release.set(25)
}
processResources {
inputs.property('version', project.version)
+ inputs.property('minecraftVersion', minecraftVersion)
filesMatching('fabric.mod.json') {
- expand('version': project.version)
+ expand('version': project.version, 'minecraftVersion': minecraftVersion)
}
}
-tasks.named('jar', Jar).configure {
- archiveFileName.set("Iris-${project.version}-fabric-skeleton.jar")
+tasks.named('shadowJar', ShadowJar).configure {
+ archiveFileName.set("Iris-${project.version}+mc${minecraftVersion}-fabric.jar")
+ configurations = [project.configurations.named('bundle').get()]
+ mergeServiceFiles()
+ exclude('META-INF/maven/**')
+ exclude('META-INF/proguard/**')
+ exclude('META-INF/*.SF')
+ exclude('META-INF/*.DSA')
+ exclude('META-INF/*.RSA')
+ duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+}
+
+tasks.named('assemble').configure {
+ dependsOn(tasks.named('shadowJar'))
}
diff --git a/adapters/fabric/gradle.properties b/adapters/fabric/gradle.properties
new file mode 100644
index 000000000..af258fbfc
--- /dev/null
+++ b/adapters/fabric/gradle.properties
@@ -0,0 +1,5 @@
+org.gradle.daemon=true
+org.gradle.parallel=true
+org.gradle.jvmargs=-Xmx3072m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+org.gradle.caching=true
+org.gradle.configuration-cache=false
diff --git a/adapters/fabric/settings.gradle b/adapters/fabric/settings.gradle
new file mode 100644
index 000000000..c7106b7e7
--- /dev/null
+++ b/adapters/fabric/settings.gradle
@@ -0,0 +1,95 @@
+/*
+ * Iris is a World Generator for Minecraft Servers
+ * Copyright (c) 2026 Arcane Arts (Volmit Software)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import java.io.File
+
+pluginManagement {
+ repositories {
+ maven {
+ name = 'FabricMC'
+ url = uri('https://maven.fabricmc.net/')
+ }
+ gradlePluginPortal()
+ mavenCentral()
+ }
+}
+
+plugins {
+ id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
+}
+
+rootProject.name = 'iris-fabric'
+
+dependencyResolutionManagement {
+ versionCatalogs {
+ libs {
+ from(files('../../gradle/libs.versions.toml'))
+ }
+ }
+}
+
+boolean hasVolmLibSettings(File directory) {
+ new File(directory, 'settings.gradle.kts').exists() || new File(directory, 'settings.gradle').exists()
+}
+
+File resolveLocalVolmLibDirectory() {
+ String configuredPath = providers.gradleProperty('localVolmLibDirectory')
+ .orElse(providers.environmentVariable('VOLMLIB_DIR'))
+ .orNull
+ if (configuredPath != null && !configuredPath.isBlank()) {
+ File configuredDirectory = file(configuredPath)
+ if (hasVolmLibSettings(configuredDirectory)) {
+ return configuredDirectory
+ }
+ }
+
+ File currentDirectory = settingsDir
+ while (currentDirectory != null) {
+ File candidate = new File(currentDirectory, 'VolmLib')
+ if (hasVolmLibSettings(candidate)) {
+ return candidate
+ }
+
+ currentDirectory = currentDirectory.parentFile
+ }
+
+ null
+}
+
+boolean useLocalVolmLib = providers.gradleProperty('useLocalVolmLib')
+ .orElse('true')
+ .map { String value -> value.equalsIgnoreCase('true') }
+ .get()
+File localVolmLibDirectory = resolveLocalVolmLibDirectory()
+
+if (useLocalVolmLib && localVolmLibDirectory != null) {
+ includeBuild(localVolmLibDirectory) {
+ dependencySubstitution {
+ substitute(module('com.github.VolmitSoftware:VolmLib')).using(project(':shared'))
+ substitute(module('com.github.VolmitSoftware.VolmLib:shared')).using(project(':shared'))
+ substitute(module('com.github.VolmitSoftware.VolmLib:volmlib-shared')).using(project(':shared'))
+ }
+ }
+}
+
+includeBuild('../..') {
+ dependencySubstitution {
+ substitute(module('art.arcane:core')).using(project(':core'))
+ substitute(module('art.arcane:spi')).using(project(':spi'))
+ }
+}
diff --git a/adapters/fabric/src/main/java/art/arcane/iris/fabric/IrisFabricBootstrap.java b/adapters/fabric/src/main/java/art/arcane/iris/fabric/IrisFabricBootstrap.java
index b3a1f7d55..226ec0c21 100644
--- a/adapters/fabric/src/main/java/art/arcane/iris/fabric/IrisFabricBootstrap.java
+++ b/adapters/fabric/src/main/java/art/arcane/iris/fabric/IrisFabricBootstrap.java
@@ -18,8 +18,46 @@
package art.arcane.iris.fabric;
-public final class IrisFabricBootstrap {
+import net.fabricmc.api.ModInitializer;
+import net.fabricmc.loader.api.FabricLoader;
+import net.fabricmc.loader.api.ModContainer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class IrisFabricBootstrap implements ModInitializer {
+ private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
+ private static final String[] CORE_SELF_TEST_CLASSES = {
+ "art.arcane.iris.engine.IrisEngine",
+ "art.arcane.iris.util.common.data.B",
+ "art.arcane.iris.core.loader.IrisData"
+ };
+
+ @Override
public void onInitialize() {
- throw new UnsupportedOperationException("The Iris Fabric adapter is a build skeleton; worldgen is not wired yet (see CROSSPLATFORM_PLAN.md Phase 4).");
+ FabricLoader loader = FabricLoader.getInstance();
+ String modVersion = loader.getModContainer("irisworldgen")
+ .map((ModContainer container) -> container.getMetadata().getVersion().getFriendlyString())
+ .orElse("unknown");
+ String minecraftVersion = loader.getModContainer("minecraft")
+ .map((ModContainer container) -> container.getMetadata().getVersion().getFriendlyString())
+ .orElse("unknown");
+ LOGGER.info("Iris {} bootstrapping on Minecraft {} (Fabric)", modVersion, minecraftVersion);
+
+ int loadedClasses = 0;
+ for (String className : CORE_SELF_TEST_CLASSES) {
+ try {
+ Class.forName(className, true, IrisFabricBootstrap.class.getClassLoader());
+ loadedClasses++;
+ } catch (Throwable error) {
+ LOGGER.error("Iris core self-test failed to initialize {}", className, error);
+ }
+ }
+
+ if (loadedClasses != CORE_SELF_TEST_CLASSES.length) {
+ throw new IllegalStateException("Iris core self-test failed: only " + loadedClasses + " of " + CORE_SELF_TEST_CLASSES.length + " engine classes initialized");
+ }
+
+ LOGGER.info("Iris core loaded ({} classes ok)", loadedClasses);
+ LOGGER.info("Iris worldgen is not wired on Fabric yet; this build verifies packaging and engine classloading");
}
}
diff --git a/adapters/fabric/src/main/resources/fabric.mod.json b/adapters/fabric/src/main/resources/fabric.mod.json
index 47ea06179..5767c5fbd 100644
--- a/adapters/fabric/src/main/resources/fabric.mod.json
+++ b/adapters/fabric/src/main/resources/fabric.mod.json
@@ -3,11 +3,19 @@
"id": "irisworldgen",
"version": "${version}",
"name": "Iris",
- "description": "Iris World Generation Engine (Fabric adapter - SKELETON, worldgen not yet wired)",
+ "description": "Iris World Generation Engine (Fabric adapter - packaging + core bundle; worldgen wiring lands next milestone)",
"authors": ["Arcane Arts (Volmit Software)"],
+ "contact": {
+ "sources": "https://github.com/VolmitSoftware/Iris"
+ },
"license": "GPL-3.0",
- "environment": "server",
+ "environment": "*",
"entrypoints": {
"main": ["art.arcane.iris.fabric.IrisFabricBootstrap"]
+ },
+ "depends": {
+ "fabricloader": ">=0.19.0",
+ "minecraft": "~${minecraftVersion}",
+ "java": ">=25"
}
}
diff --git a/build.gradle b/build.gradle
index ec028f5fd..a9d789497 100644
--- a/build.gradle
+++ b/build.gradle
@@ -42,6 +42,11 @@ plugins {
group = 'art.arcane'
version = providers.gradleProperty('irisVersion').getOrElse('4.0.0-26.1')
+
+project(':core') {
+ group = 'art.arcane'
+ version = rootProject.version
+}
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse('26.1.2')
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate')
.orElse('com.github.VolmitSoftware:VolmLib:master-SNAPSHOT')
@@ -128,11 +133,17 @@ tasks.register('buildBukkit', Copy) {
into(layout.projectDirectory.dir('dist'))
}
+tasks.register('fabricJar', Exec) {
+ group = 'iris'
+ workingDir = layout.projectDirectory.dir('adapters/fabric').asFile
+ String wrapperScript = System.getProperty('os.name').toLowerCase().contains('windows') ? 'gradlew.bat' : 'gradlew'
+ commandLine(layout.projectDirectory.file(wrapperScript).asFile.absolutePath, 'shadowJar', '--console=plain')
+}
+
tasks.register('buildFabric', Copy) {
group = 'iris'
- dependsOn(':adapters:fabric:jar')
- from(project(':adapters:fabric').layout.buildDirectory.file("libs/Iris-${project.version}-fabric-skeleton.jar"))
- rename { "Iris-${project.version}+mc${minecraftVersion}-fabric-skeleton.jar" }
+ dependsOn('fabricJar')
+ from(layout.projectDirectory.file("adapters/fabric/build/libs/Iris-${project.version}+mc${minecraftVersion}-fabric.jar"))
into(layout.projectDirectory.dir('dist'))
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2e1113280..5dd3c0121 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/settings.gradle b/settings.gradle
index 07f33339e..de2abc625 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -74,6 +74,5 @@ include(':spi')
include(':adapters:bukkit:plugin')
include(':adapters:bukkit:nms:v1_21_R7')
include(':adapters:bukkit:nms:v26_1_R1')
-include(':adapters:fabric')
include(':adapters:forge')
include(':adapters:neoforge')