diff --git a/core/build.gradle b/core/build.gradle
index bed15cb73..eb7053511 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -61,6 +61,8 @@ boolean hasSentryAuthToken = sentryAuthToken != null && !sentryAuthToken.isBlank
* these dependencies if they are available on mvn central.
*/
dependencies {
+ api(project(':spi'))
+
// Provided or Classpath
compileOnly(libs.spigot)
compileOnly(libs.log4j.api)
diff --git a/core/src/main/java/art/arcane/iris/Iris.java b/core/src/main/java/art/arcane/iris/Iris.java
index 6859f10fa..1c10df9cf 100644
--- a/core/src/main/java/art/arcane/iris/Iris.java
+++ b/core/src/main/java/art/arcane/iris/Iris.java
@@ -49,6 +49,8 @@ import art.arcane.iris.engine.object.IrisWorld;
import art.arcane.iris.engine.platform.BukkitChunkGenerator;
import art.arcane.iris.core.safeguard.IrisSafeguard;
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
+import art.arcane.iris.platform.bukkit.BukkitPlatform;
+import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.volmlib.integration.ReloadAware;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KMap;
@@ -911,6 +913,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
}
public void onEnable() {
+ IrisPlatforms.bind(new BukkitPlatform());
enable();
super.onEnable();
Bukkit.getPluginManager().registerEvents(this, this);
diff --git a/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitBiome.java b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitBiome.java
new file mode 100644
index 000000000..245ffe4a6
--- /dev/null
+++ b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitBiome.java
@@ -0,0 +1,75 @@
+/*
+ * Iris is a World Generator for Minecraft Bukkit 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 .
+ */
+
+package art.arcane.iris.platform.bukkit;
+
+import art.arcane.iris.spi.PlatformBiome;
+import org.bukkit.block.Biome;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Interned Bukkit adapter for a neutral biome handle.
+ */
+public final class BukkitBiome implements PlatformBiome {
+ private static final ConcurrentHashMap CACHE = new ConcurrentHashMap<>();
+
+ private final Biome biome;
+ private final String key;
+ private final String namespace;
+
+ private BukkitBiome(Biome biome, String key) {
+ this.biome = biome;
+ this.key = key;
+ int colon = key.indexOf(':');
+ this.namespace = colon >= 0 ? key.substring(0, colon) : "minecraft";
+ }
+
+ public static BukkitBiome of(Biome biome) {
+ String key = biomeKey(biome);
+ return CACHE.computeIfAbsent(key, (String k) -> new BukkitBiome(biome, k));
+ }
+
+ private static String biomeKey(Biome biome) {
+ for (String method : new String[]{"getKeyOrNull", "getKeyOrThrow", "getKey"}) {
+ try {
+ Object key = Biome.class.getMethod(method).invoke(biome);
+ if (key != null) {
+ return key.toString();
+ }
+ } catch (Throwable ignored) {
+ }
+ }
+ return biome.toString();
+ }
+
+ @Override
+ public String key() {
+ return key;
+ }
+
+ @Override
+ public String namespace() {
+ return namespace;
+ }
+
+ @Override
+ public Object nativeHandle() {
+ return biome;
+ }
+}
diff --git a/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitBlockState.java b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitBlockState.java
new file mode 100644
index 000000000..428b1427b
--- /dev/null
+++ b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitBlockState.java
@@ -0,0 +1,126 @@
+/*
+ * Iris is a World Generator for Minecraft Bukkit 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 .
+ */
+
+package art.arcane.iris.platform.bukkit;
+
+import art.arcane.iris.core.nms.INMS;
+import art.arcane.iris.spi.PlatformBlockState;
+import art.arcane.iris.util.common.data.B;
+import org.bukkit.Bukkit;
+import org.bukkit.block.data.BlockData;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Interned Bukkit adapter for a neutral block state backed by BlockData.
+ */
+public final class BukkitBlockState implements PlatformBlockState {
+ private static final ConcurrentHashMap CACHE = new ConcurrentHashMap<>();
+
+ private final BlockData data;
+ private final String key;
+ private final String namespace;
+
+ private BukkitBlockState(BlockData data, String key) {
+ this.data = data;
+ this.key = key;
+ this.namespace = parseNamespace(key);
+ }
+
+ public static BukkitBlockState of(BlockData data) {
+ String key = data.getAsString();
+ return CACHE.computeIfAbsent(key, (String k) -> new BukkitBlockState(data, k));
+ }
+
+ private static String parseNamespace(String key) {
+ String base = key;
+ int bracket = base.indexOf('[');
+ if (bracket >= 0) {
+ base = base.substring(0, bracket);
+ }
+ int colon = base.indexOf(':');
+ return colon >= 0 ? base.substring(0, colon) : "minecraft";
+ }
+
+ private static String mergeProperty(String key, String name, String value) {
+ int bracket = key.indexOf('[');
+ if (bracket < 0) {
+ return key + "[" + name + "=" + value + "]";
+ }
+ String base = key.substring(0, bracket);
+ String body = key.substring(bracket + 1, key.lastIndexOf(']'));
+ LinkedHashMap properties = new LinkedHashMap<>();
+ for (String entry : body.split(",")) {
+ int equals = entry.indexOf('=');
+ if (equals < 0) {
+ continue;
+ }
+ properties.put(entry.substring(0, equals).trim(), entry.substring(equals + 1).trim());
+ }
+ properties.put(name, value);
+ StringBuilder merged = new StringBuilder(base).append('[');
+ boolean first = true;
+ for (Map.Entry property : properties.entrySet()) {
+ if (!first) {
+ merged.append(',');
+ }
+ merged.append(property.getKey()).append('=').append(property.getValue());
+ first = false;
+ }
+ return merged.append(']').toString();
+ }
+
+ @Override
+ public String key() {
+ return key;
+ }
+
+ @Override
+ public String namespace() {
+ return namespace;
+ }
+
+ @Override
+ public boolean isAir() {
+ return B.isAir(data);
+ }
+
+ @Override
+ public boolean isSolid() {
+ return B.isSolid(data);
+ }
+
+ @Override
+ public boolean hasTileEntity() {
+ return INMS.get().hasTile(data.getMaterial());
+ }
+
+ @Override
+ public PlatformBlockState withProperty(String name, String value) {
+ String merged = mergeProperty(key, name, value);
+ BlockData resolved = Bukkit.createBlockData(merged);
+ return of(resolved);
+ }
+
+ @Override
+ public Object nativeHandle() {
+ return data;
+ }
+}
diff --git a/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitChunkWriteTarget.java b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitChunkWriteTarget.java
new file mode 100644
index 000000000..3b33890df
--- /dev/null
+++ b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitChunkWriteTarget.java
@@ -0,0 +1,57 @@
+/*
+ * Iris is a World Generator for Minecraft Bukkit 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 .
+ */
+
+package art.arcane.iris.platform.bukkit;
+
+import art.arcane.iris.engine.data.chunk.TerrainChunk;
+import art.arcane.iris.spi.ChunkWriteTarget;
+import art.arcane.iris.spi.PlatformBiome;
+import art.arcane.iris.spi.PlatformBlockState;
+import org.bukkit.block.Biome;
+import org.bukkit.block.data.BlockData;
+
+/**
+ * Bukkit adapter for the hot-path chunk write surface backed by TerrainChunk.
+ */
+public final class BukkitChunkWriteTarget implements ChunkWriteTarget {
+ private final TerrainChunk terrain;
+
+ public BukkitChunkWriteTarget(TerrainChunk terrain) {
+ this.terrain = terrain;
+ }
+
+ @Override
+ public void setBlock(int x, int y, int z, PlatformBlockState state) {
+ terrain.setBlock(x, y, z, (BlockData) state.nativeHandle());
+ }
+
+ @Override
+ public void setBiome(int x, int y, int z, PlatformBiome biome) {
+ terrain.setBiome(x, y, z, (Biome) biome.nativeHandle());
+ }
+
+ @Override
+ public int minHeight() {
+ return terrain.getMinHeight();
+ }
+
+ @Override
+ public int maxHeight() {
+ return terrain.getMaxHeight();
+ }
+}
diff --git a/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitEntityType.java b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitEntityType.java
new file mode 100644
index 000000000..4d394c3d6
--- /dev/null
+++ b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitEntityType.java
@@ -0,0 +1,62 @@
+/*
+ * Iris is a World Generator for Minecraft Bukkit 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 .
+ */
+
+package art.arcane.iris.platform.bukkit;
+
+import art.arcane.iris.spi.PlatformEntityType;
+import org.bukkit.entity.EntityType;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Interned Bukkit adapter for a neutral entity type handle.
+ */
+public final class BukkitEntityType implements PlatformEntityType {
+ private static final ConcurrentHashMap CACHE = new ConcurrentHashMap<>();
+
+ private final EntityType type;
+ private final String key;
+ private final String namespace;
+
+ private BukkitEntityType(EntityType type, String key) {
+ this.type = type;
+ this.key = key;
+ int colon = key.indexOf(':');
+ this.namespace = colon >= 0 ? key.substring(0, colon) : "minecraft";
+ }
+
+ public static BukkitEntityType of(EntityType type) {
+ String key = type.getKey().toString();
+ return CACHE.computeIfAbsent(key, (String k) -> new BukkitEntityType(type, k));
+ }
+
+ @Override
+ public String key() {
+ return key;
+ }
+
+ @Override
+ public String namespace() {
+ return namespace;
+ }
+
+ @Override
+ public Object nativeHandle() {
+ return type;
+ }
+}
diff --git a/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitItem.java b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitItem.java
new file mode 100644
index 000000000..f98b0ed1b
--- /dev/null
+++ b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitItem.java
@@ -0,0 +1,62 @@
+/*
+ * Iris is a World Generator for Minecraft Bukkit 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 .
+ */
+
+package art.arcane.iris.platform.bukkit;
+
+import art.arcane.iris.spi.PlatformItem;
+import org.bukkit.Material;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Interned Bukkit adapter for a neutral item handle backed by Material.
+ */
+public final class BukkitItem implements PlatformItem {
+ private static final ConcurrentHashMap CACHE = new ConcurrentHashMap<>();
+
+ private final Material material;
+ private final String key;
+ private final String namespace;
+
+ private BukkitItem(Material material, String key) {
+ this.material = material;
+ this.key = key;
+ int colon = key.indexOf(':');
+ this.namespace = colon >= 0 ? key.substring(0, colon) : "minecraft";
+ }
+
+ public static BukkitItem of(Material material) {
+ String key = material.getKey().toString();
+ return CACHE.computeIfAbsent(key, (String k) -> new BukkitItem(material, k));
+ }
+
+ @Override
+ public String key() {
+ return key;
+ }
+
+ @Override
+ public String namespace() {
+ return namespace;
+ }
+
+ @Override
+ public Object nativeHandle() {
+ return material;
+ }
+}
diff --git a/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitPlatform.java b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitPlatform.java
new file mode 100644
index 000000000..7338208c3
--- /dev/null
+++ b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitPlatform.java
@@ -0,0 +1,102 @@
+/*
+ * Iris is a World Generator for Minecraft Bukkit 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 .
+ */
+
+package art.arcane.iris.platform.bukkit;
+
+import art.arcane.iris.Iris;
+import art.arcane.iris.core.nms.INMS;
+import art.arcane.iris.spi.IrisPlatform;
+import art.arcane.iris.spi.LogLevel;
+import art.arcane.iris.spi.PlatformCapabilities;
+import art.arcane.iris.spi.PlatformRegistries;
+import art.arcane.iris.spi.PlatformScheduler;
+import art.arcane.iris.util.common.scheduling.J;
+import org.bukkit.Bukkit;
+
+import java.io.File;
+
+/**
+ * Bukkit implementation of the root Iris platform service.
+ */
+public final class BukkitPlatform implements IrisPlatform {
+ private final BukkitRegistries registries = new BukkitRegistries();
+ private final BukkitScheduler scheduler = new BukkitScheduler();
+ private final PlatformCapabilities capabilities = new BukkitCapabilities();
+
+ @Override
+ public String platformName() {
+ return "bukkit";
+ }
+
+ @Override
+ public String minecraftVersion() {
+ return Bukkit.getBukkitVersion();
+ }
+
+ @Override
+ public PlatformRegistries registries() {
+ return registries;
+ }
+
+ @Override
+ public PlatformScheduler scheduler() {
+ return scheduler;
+ }
+
+ @Override
+ public PlatformCapabilities capabilities() {
+ return capabilities;
+ }
+
+ @Override
+ public File dataFolder() {
+ return Iris.instance.getDataFolder();
+ }
+
+ @Override
+ public void log(LogLevel level, String message) {
+ switch (level) {
+ case DEBUG -> Iris.debug(message);
+ case INFO -> Iris.info(message);
+ case WARN -> Iris.warn(message);
+ case ERROR -> Iris.error(message);
+ }
+ }
+
+ private static final class BukkitCapabilities implements PlatformCapabilities {
+ @Override
+ public boolean customBiomes() {
+ return INMS.get().supportsCustomBiomes();
+ }
+
+ @Override
+ public boolean dataPacks() {
+ return INMS.get().supportsDataPacks();
+ }
+
+ @Override
+ public boolean structurePlacement() {
+ return true;
+ }
+
+ @Override
+ public boolean regionizedThreading() {
+ return J.isFolia();
+ }
+ }
+}
diff --git a/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitRegistries.java b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitRegistries.java
new file mode 100644
index 000000000..5b4bf20aa
--- /dev/null
+++ b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitRegistries.java
@@ -0,0 +1,98 @@
+/*
+ * Iris is a World Generator for Minecraft Bukkit 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 .
+ */
+
+package art.arcane.iris.platform.bukkit;
+
+import art.arcane.iris.core.nms.INMS;
+import art.arcane.iris.spi.PlatformBiome;
+import art.arcane.iris.spi.PlatformBlockState;
+import art.arcane.iris.spi.PlatformEntityType;
+import art.arcane.iris.spi.PlatformItem;
+import art.arcane.iris.spi.PlatformRegistries;
+import art.arcane.iris.util.common.data.B;
+import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
+import org.bukkit.Registry;
+import org.bukkit.block.Biome;
+import org.bukkit.block.data.BlockData;
+import org.bukkit.entity.EntityType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Bukkit adapter resolving namespaced keys against the live Bukkit registries.
+ */
+public final class BukkitRegistries implements PlatformRegistries {
+ @Override
+ public PlatformBlockState block(String key) {
+ BlockData data = B.get(key);
+ return data == null ? null : BukkitBlockState.of(data);
+ }
+
+ @Override
+ public PlatformBiome biome(String key) {
+ NamespacedKey namespacedKey = NamespacedKey.fromString(key);
+ if (namespacedKey == null) {
+ return null;
+ }
+ Biome biome = Registry.BIOME.get(namespacedKey);
+ return biome == null ? null : BukkitBiome.of(biome);
+ }
+
+ @Override
+ public PlatformItem item(String key) {
+ Material material = Material.matchMaterial(key);
+ return material == null ? null : BukkitItem.of(material);
+ }
+
+ @Override
+ public PlatformEntityType entity(String key) {
+ NamespacedKey namespacedKey = NamespacedKey.fromString(key);
+ if (namespacedKey == null) {
+ return null;
+ }
+ EntityType type = Registry.ENTITY_TYPE.get(namespacedKey);
+ return type == null ? null : BukkitEntityType.of(type);
+ }
+
+ @Override
+ public List blockKeys() {
+ List keys = new ArrayList<>();
+ for (Material material : Registry.MATERIAL) {
+ if (material.isBlock()) {
+ keys.add(material.getKey().toString());
+ }
+ }
+ return keys;
+ }
+
+ @Override
+ public List biomeKeys() {
+ List keys = new ArrayList<>();
+ for (Biome biome : Registry.BIOME) {
+ keys.add(BukkitBiome.of(biome).key());
+ }
+ return keys;
+ }
+
+ @Override
+ public List structureKeys() {
+ return new ArrayList<>(INMS.get().getStructureKeys());
+ }
+}
diff --git a/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitScheduler.java b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitScheduler.java
new file mode 100644
index 000000000..70766410f
--- /dev/null
+++ b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitScheduler.java
@@ -0,0 +1,60 @@
+/*
+ * Iris is a World Generator for Minecraft Bukkit 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 .
+ */
+
+package art.arcane.iris.platform.bukkit;
+
+import art.arcane.iris.spi.PlatformScheduler;
+import art.arcane.iris.spi.PlatformWorld;
+import art.arcane.iris.util.common.scheduling.J;
+import org.bukkit.World;
+
+/**
+ * Bukkit adapter dispatching platform tasks through the Folia-aware J scheduler.
+ */
+public final class BukkitScheduler implements PlatformScheduler {
+ @Override
+ public void global(Runnable task) {
+ J.s(task);
+ }
+
+ @Override
+ public void region(PlatformWorld world, int chunkX, int chunkZ, Runnable task) {
+ World bukkitWorld = (World) world.nativeHandle();
+ if (!J.runRegion(bukkitWorld, chunkX, chunkZ, task)) {
+ J.s(task);
+ }
+ }
+
+ @Override
+ public void async(Runnable task) {
+ J.a(task);
+ }
+
+ @Override
+ public void laterGlobal(Runnable task, int ticks) {
+ J.s(task, ticks);
+ }
+
+ @Override
+ public void laterRegion(PlatformWorld world, int chunkX, int chunkZ, Runnable task, int ticks) {
+ World bukkitWorld = (World) world.nativeHandle();
+ if (!J.runRegion(bukkitWorld, chunkX, chunkZ, task, ticks)) {
+ J.s(task, ticks);
+ }
+ }
+}
diff --git a/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitWorld.java b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitWorld.java
new file mode 100644
index 000000000..ed91c7ba3
--- /dev/null
+++ b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitWorld.java
@@ -0,0 +1,81 @@
+/*
+ * Iris is a World Generator for Minecraft Bukkit 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 .
+ */
+
+package art.arcane.iris.platform.bukkit;
+
+import art.arcane.iris.spi.PlatformBiome;
+import art.arcane.iris.spi.PlatformBlockState;
+import art.arcane.iris.spi.PlatformWorld;
+import org.bukkit.World;
+import org.bukkit.block.data.BlockData;
+
+/**
+ * Bukkit adapter for a neutral world view backed by org.bukkit.World.
+ */
+public final class BukkitWorld implements PlatformWorld {
+ private final World world;
+
+ public BukkitWorld(World world) {
+ this.world = world;
+ }
+
+ @Override
+ public String name() {
+ return world.getName();
+ }
+
+ @Override
+ public long seed() {
+ return world.getSeed();
+ }
+
+ @Override
+ public int minHeight() {
+ return world.getMinHeight();
+ }
+
+ @Override
+ public int maxHeight() {
+ return world.getMaxHeight();
+ }
+
+ @Override
+ public PlatformBlockState getBlock(int x, int y, int z) {
+ return BukkitBlockState.of(world.getBlockAt(x, y, z).getBlockData());
+ }
+
+ @Override
+ public void setBlock(int x, int y, int z, PlatformBlockState block, int flags) {
+ world.getBlockAt(x, y, z).setBlockData((BlockData) block.nativeHandle(), (flags & 1) != 0);
+ }
+
+ @Override
+ public PlatformBiome getBiome(int x, int y, int z) {
+ return BukkitBiome.of(world.getBiome(x, y, z));
+ }
+
+ @Override
+ public boolean isChunkLoaded(int chunkX, int chunkZ) {
+ return world.isChunkLoaded(chunkX, chunkZ);
+ }
+
+ @Override
+ public Object nativeHandle() {
+ return world;
+ }
+}
diff --git a/core/src/test/java/art/arcane/iris/platform/bukkit/BukkitSpiConformanceTest.java b/core/src/test/java/art/arcane/iris/platform/bukkit/BukkitSpiConformanceTest.java
new file mode 100644
index 000000000..3c20b63e6
--- /dev/null
+++ b/core/src/test/java/art/arcane/iris/platform/bukkit/BukkitSpiConformanceTest.java
@@ -0,0 +1,131 @@
+/*
+ * Iris is a World Generator for Minecraft Bukkit 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 .
+ */
+
+package art.arcane.iris.platform.bukkit;
+
+import art.arcane.iris.spi.IrisPlatform;
+import art.arcane.iris.spi.IrisPlatforms;
+import art.arcane.iris.spi.PlatformBlockState;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.Server;
+import org.bukkit.block.data.BlockData;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+
+import java.util.logging.Logger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+public class BukkitSpiConformanceTest {
+ @BeforeClass
+ public static void setup() {
+ Server server = Bukkit.getServer();
+ if (server == null) {
+ server = mock(Server.class);
+ doReturn(Logger.getLogger("IrisTest")).when(server).getLogger();
+ doReturn("IrisTestServer").when(server).getName();
+ doReturn("1.0").when(server).getVersion();
+ doReturn("1.0").when(server).getBukkitVersion();
+ doReturn(mock(BlockData.class)).when(server).createBlockData(any(Material.class));
+ Bukkit.setServer(server);
+ }
+ doAnswer((InvocationOnMock invocation) -> blockData(invocation.getArgument(0))).when(server).createBlockData(anyString());
+ }
+
+ private static BlockData blockData(String asString) {
+ BlockData data = mock(BlockData.class);
+ doReturn(asString).when(data).getAsString();
+ return data;
+ }
+
+ @Test
+ public void blockStateInterningReturnsSameInstanceForSameKey() {
+ BlockData first = blockData("iristest:intern_block[axis=y]");
+ BlockData second = blockData("iristest:intern_block[axis=y]");
+ assertSame(BukkitBlockState.of(first), BukkitBlockState.of(second));
+ }
+
+ @Test
+ public void blockStateKeyMatchesCanonicalString() {
+ BlockData data = blockData("iristest:key_block[facing=north,lit=true]");
+ assertEquals("iristest:key_block[facing=north,lit=true]", BukkitBlockState.of(data).key());
+ }
+
+ @Test
+ public void blockStateNamespaceParsesWithAndWithoutProperties() {
+ BlockData withProps = blockData("iristest:ns_block[axis=y]");
+ BlockData withoutProps = blockData("iristest:ns_plain");
+ assertEquals("iristest", BukkitBlockState.of(withProps).namespace());
+ assertEquals("iristest", BukkitBlockState.of(withoutProps).namespace());
+ }
+
+ @Test
+ public void withPropertyReplacesExistingProperty() {
+ BlockData data = blockData("iristest:merge_block[axis=y,waterlogged=false]");
+ PlatformBlockState merged = BukkitBlockState.of(data).withProperty("axis", "x");
+ assertEquals("iristest:merge_block[axis=x,waterlogged=false]", merged.key());
+ }
+
+ @Test
+ public void withPropertyAppendsToExistingProperties() {
+ BlockData data = blockData("iristest:append_block[axis=y]");
+ PlatformBlockState merged = BukkitBlockState.of(data).withProperty("lit", "true");
+ assertEquals("iristest:append_block[axis=y,lit=true]", merged.key());
+ }
+
+ @Test
+ public void withPropertyAddsBracketSectionWhenAbsent() {
+ BlockData data = blockData("iristest:bare_block");
+ PlatformBlockState merged = BukkitBlockState.of(data).withProperty("lit", "true");
+ assertEquals("iristest:bare_block[lit=true]", merged.key());
+ }
+
+ @Test
+ public void platformBindingLifecycle() {
+ assertFalse(IrisPlatforms.isBound());
+ try {
+ IrisPlatforms.get();
+ fail("get() must throw while unbound");
+ } catch (IllegalStateException expected) {
+ }
+ IrisPlatform first = mock(IrisPlatform.class);
+ IrisPlatforms.bind(first);
+ assertTrue(IrisPlatforms.isBound());
+ assertSame(first, IrisPlatforms.get());
+ IrisPlatforms.bind(first);
+ assertSame(first, IrisPlatforms.get());
+ IrisPlatform second = mock(IrisPlatform.class);
+ try {
+ IrisPlatforms.bind(second);
+ fail("bind() must reject a different instance");
+ } catch (IllegalStateException expected) {
+ }
+ assertSame(first, IrisPlatforms.get());
+ }
+}
diff --git a/spi/src/main/java/art/arcane/iris/spi/IrisPlatforms.java b/spi/src/main/java/art/arcane/iris/spi/IrisPlatforms.java
new file mode 100644
index 000000000..b36ef62e5
--- /dev/null
+++ b/spi/src/main/java/art/arcane/iris/spi/IrisPlatforms.java
@@ -0,0 +1,48 @@
+/*
+ * Iris is a World Generator for Minecraft Bukkit 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 .
+ */
+
+package art.arcane.iris.spi;
+
+/**
+ * Static holder binding the active platform adapter for the lifetime of the runtime.
+ */
+public final class IrisPlatforms {
+ private static volatile IrisPlatform platform;
+
+ private IrisPlatforms() {
+ }
+
+ public static synchronized void bind(IrisPlatform p) {
+ if (platform != null && platform != p) {
+ throw new IllegalStateException("Iris platform is already bound to a different instance");
+ }
+ platform = p;
+ }
+
+ public static IrisPlatform get() {
+ IrisPlatform bound = platform;
+ if (bound == null) {
+ throw new IllegalStateException("No Iris platform is bound");
+ }
+ return bound;
+ }
+
+ public static boolean isBound() {
+ return platform != null;
+ }
+}