Tracked generated content ownership

- Generated furniture keeps ownership tokens for targeted removal
- Native writes invalidate pending custom content and tile records
- Regeneration and chunk updates remove invalidated content on owning threads
- Provider callbacks return ownership tokens across server platforms
- Regression fixtures cover terrain replacement and restart cleanup
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-18 17:58:30 -04:00
1 parent ed5483d75a
commit 34417b2f5b
53 files changed
+2082 -110

No files matched your search

@@ -19,6 +19,10 @@
package art.arcane.iris.world.runtime;
import art.arcane.iris.api.world.IrisWorldPhase;
import art.arcane.iris.generation.mantle.CustomBlockOwnership;
import art.arcane.iris.integration.ExternalDataSVC;
import art.arcane.iris.spi.IrisServices;
import art.arcane.volmlib.nativelib.terrain.NativeBlockPoint;
import art.arcane.iris.pack.datapack.ServerConfigurator;
import art.arcane.iris.pack.datapack.DatapackIngestService;
import art.arcane.iris.world.event.IrisEngineHotloadEvent;
@@ -51,6 +55,13 @@ import org.bukkit.World;
import org.bukkit.WorldBorder;
public final class BukkitEnginePlatformHooks implements EnginePlatformHooks {
@Override
public boolean removeCustomBlock(Engine engine, NativeBlockPoint position, CustomBlockOwnership.Entry entry) {
ExternalDataSVC service = IrisServices.getOrNull(ExternalDataSVC.class);
return service != null && service.removeGenerated(
BukkitWorldBinding.world(engine.getWorld()).getBlockAt(position.x(), position.y(), position.z()), entry);
}
@Override
public CompletableFuture<Void> flushSavedTerrainCapture(Engine engine) {
return INMS.get().flushSavedTerrainCapture(BukkitWorldBinding.world(engine.getWorld()));
@@ -0,0 +1,46 @@
{
"name": "Flat",
"derivative": "minecraft:snowy_plains",
"vanillaDerivative": "minecraft:snowy_plains",
"layers": [
{
"minHeight": 1,
"maxHeight": 1,
"palette": [
{
"block": "minecraft:lime_wool"
}
]
},
{
"minHeight": 384,
"maxHeight": 384,
"palette": [
{
"block": "minecraft:green_concrete"
}
]
}
],
"generators": [
{
"generator": "flat",
"min": 96,
"max": 96
}
],
"decorators": [
{
"palette": [
{
"block": "craftengine:default/flower_basket",
"data": {
"variant": "ground"
}
}
],
"chance": 1,
"forcePlace": true
}
]
}
@@ -0,0 +1,39 @@
{
"name": "CraftEngine Imported Feature Fixture",
"version": 1,
"mode": {
"type": "OVERWORLD"
},
"regions": [
"flat"
],
"focusRegion": "flat",
"focus": "flat",
"landChance": 1,
"environment": "NORMAL",
"dimensionHeight": {
"min": -64,
"max": 320
},
"logicalHeight": 384,
"fluidHeight": 0,
"bedrock": false,
"rockPalette": {
"palette": [
{
"block": "minecraft:red_concrete"
}
]
},
"importedStructures": {
"disabled": [
"minecraft:"
]
},
"importedFeatures": {
"enabled": true,
"steps": [
"TOP_LAYER_MODIFICATION"
]
}
}
@@ -0,0 +1,15 @@
{
"interpolator": {
"function": "NONE",
"horizontalScale": 1
},
"seed": 310,
"composite": [
{
"seed": 310,
"style": {
"style": "FLAT"
}
}
]
}
@@ -0,0 +1,12 @@
{
"name": "Flat",
"landBiomes": [
"flat"
],
"seaBiomes": [
"flat"
],
"shoreBiomes": [
"flat"
]
}
@@ -0,0 +1,35 @@
plugins {
id 'java'
}
version = '1.0.0'
repositories {
mavenCentral()
maven { url = 'https://repo.papermc.io/repository/maven-public/' }
}
dependencies {
compileOnly files(providers.gradleProperty('irisJar').get())
compileOnly files(providers.gradleProperty('craftEngineJar').get())
compileOnly 'io.papermc.paper:paper-api:26.1.2.build.74-stable'
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(25)
}
sourceSets {
main {
java.srcDirs = ['src']
resources.srcDirs = ['resources']
}
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs += '-parameters'
}
tasks.named('jar') {
archiveBaseName = 'IrisCraftEngineNativeFixture'
}
@@ -0,0 +1,12 @@
name: IrisCraftEngineNativeFixture
version: 1.0.0
main: art.arcane.iris.fixture.CraftEngineNativeFixture
api-version: '26.1.2'
folia-supported: true
depend: [Iris, CraftEngine]
commands:
iriscenative:
permission: iriscenative.run
permissions:
iriscenative.run:
default: op
@@ -0,0 +1 @@
rootProject.name = 'iris-craftengine-native-fixture'
@@ -0,0 +1,385 @@
package art.arcane.iris.fixture;
import art.arcane.iris.generation.block.IrisCustomData;
import art.arcane.iris.generation.block.TileData;
import art.arcane.iris.generation.mantle.DeferredPlacementRecords;
import art.arcane.iris.generation.mantle.CustomBlockOwnership;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.integration.ExternalDataSVC;
import art.arcane.iris.integration.Identifier;
import art.arcane.iris.platform.generation.EngineBukkitOps;
import art.arcane.iris.spi.IrisServices;
import art.arcane.iris.world.IrisCreator;
import art.arcane.iris.world.IrisToolbelt;
import art.arcane.iris.world.storage.matter.TileWrapper;
import art.arcane.iris.world.task.J;
import art.arcane.volmlib.nativelib.terrain.NativeBlockChangeSet;
import art.arcane.volmlib.util.mantle.flag.MantleFlag;
import art.arcane.volmlib.util.mantle.runtime.Mantle;
import art.arcane.volmlib.util.matter.Matter;
import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks;
import net.momirealms.craftengine.bukkit.api.CraftEngineFurniture;
import net.momirealms.craftengine.core.util.Key;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
public final class CraftEngineNativeFixture extends JavaPlugin {
private static final NamespacedKey WORLD_KEY = new NamespacedKey("iris", "ce_native_probe");
private static final NamespacedKey FEATURE_WORLD_KEY = new NamespacedKey("iris", "ce_native_feature_probe");
private static final int TEST_Y = 180;
private static final int TEST_Z = 8;
private static final String BLOCK_ID = "craftengine:default/topaz_ore";
private static final String FURNITURE_ID = "craftengine:default/flower_basket[variant=ground]";
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] arguments) {
if (!(sender instanceof Player player) || arguments.length != 1) {
sender.sendMessage("CE_FAILED Use /iriscenative prepare|pending|neighbor|loaded|features|restart from a player");
return true;
}
if (arguments[0].equals("prepare")) {
player.setGameMode(GameMode.CREATIVE);
prepare(player);
return true;
}
if (arguments[0].equals("features")) {
importedFeatures(player);
return true;
}
World world = Bukkit.getWorld(WORLD_KEY);
if (world == null) {
fail(player, new IllegalStateException("Fixture world has not been created"));
return true;
}
if (arguments[0].equals("restart")) {
loadChunks().thenCompose(ignored -> J.runRegionFuture(world, 0, 0,
() -> later(player, world, () -> restarted(player, world))))
.whenComplete((ignored, failure) -> {
if (failure != null) {
fail(player, failure);
}
});
return true;
}
J.runRegionFuture(world, 0, 0, () -> {
switch (arguments[0]) {
case "pending" -> pending(player, world);
case "neighbor" -> neighbor(player, world);
case "loaded" -> invalidateLoaded(player, world, IrisToolbelt.access(world).getEngine());
default -> throw new IllegalArgumentException("Unknown fixture operation");
}
}).whenComplete((ignored, failure) -> {
if (failure != null) {
fail(player, failure);
}
});
return true;
}
private void prepare(Player player) {
CompletableFuture.runAsync(() -> {
if (Bukkit.getWorld(WORLD_KEY) == null) {
try {
new IrisCreator().name(WORLD_KEY.toString()).dimension("ce-native-fixture")
.seed(78264193L).create();
} catch (Exception failure) {
throw new IllegalStateException("Could not create CraftEngine fixture world", failure);
}
}
}).thenCompose(ignored -> loadChunks()).thenCompose(ignored -> {
World world = Bukkit.getWorld(WORLD_KEY);
return J.runRegionFuture(world, 0, 0, () -> preparePlatform(world));
}).thenAccept(ignored -> {
World world = Bukkit.getWorld(WORLD_KEY);
if (!J.runEntity(player, () -> player.teleportAsync(new Location(world, 8.5, TEST_Y + 3, 8.5))
.whenComplete((teleported, failure) -> {
if (failure != null || !Boolean.TRUE.equals(teleported)) {
fail(player, failure == null ? new IllegalStateException("Fixture teleport failed") : failure);
return;
}
player.sendMessage("CE_READY world=" + WORLD_KEY + " folia=" + J.isFolia());
}))) {
throw new IllegalStateException("Could not schedule fixture teleport");
}
}).exceptionally(failure -> {
fail(player, failure);
return null;
});
}
private void importedFeatures(Player player) {
CompletableFuture.runAsync(() -> {
if (Bukkit.getWorld(FEATURE_WORLD_KEY) == null) {
try {
new IrisCreator().name(FEATURE_WORLD_KEY.toString()).dimension("ce-native-feature-fixture")
.seed(78264193L).create();
} catch (Exception failure) {
throw new IllegalStateException("Could not create imported feature fixture world", failure);
}
}
}).thenCompose(ignored -> {
World world = Bukkit.getWorld(FEATURE_WORLD_KEY);
return world.getChunkAtAsync(0, 0, true);
}).thenCompose(chunk -> J.runRegionFuture(chunk.getWorld(), 0, 0, () -> {
World world = chunk.getWorld();
Engine engine = IrisToolbelt.access(world).getEngine();
int snow = 0;
int pending = 0;
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
if (world.getBlockAt(x, 97, z).getType() == Material.SNOW) {
snow++;
}
if (engine.getMantle().getMantle().get(x, 97 - engine.getMinHeight(), z, Identifier.class) != null) {
pending++;
}
}
}
require(snow == 256, "Imported snow feature did not cover all 256 test columns: " + snow);
require(pending == 0, "Imported feature left pending furniture records: " + pending);
for (Entity entity : chunk.getEntities()) {
require(!CraftEngineFurniture.isFurniture(entity), "Imported snow feature left generated furniture");
}
if (!J.runEntity(player, () -> player.teleportAsync(new Location(world, 8.5, 101, 8.5))
.whenComplete((teleported, failure) -> {
if (failure != null || !Boolean.TRUE.equals(teleported)) {
fail(player, failure == null ? new IllegalStateException("Feature world teleport failed") : failure);
return;
}
player.sendMessage("CE_FEATURES snow=256 pending=0 furniture=0");
}))) {
throw new IllegalStateException("Could not schedule feature world teleport");
}
})).exceptionally(failure -> {
fail(player, failure);
return null;
});
}
private CompletableFuture<Void> loadChunks() {
World world = Bukkit.getWorld(WORLD_KEY);
require(world != null && IrisToolbelt.isIrisWorld(world), "Fixture must use a real Iris world");
List<CompletableFuture<Void>> chunks = new ArrayList<>(15);
for (int x = -1; x <= 3; x++) {
for (int z = -1; z <= 1; z++) {
chunks.add(world.getChunkAtAsync(x, z, true).thenCompose(chunk ->
J.runRegionFuture(world, chunk.getX(), chunk.getZ(), () -> chunk.addPluginChunkTicket(this))));
}
}
return CompletableFuture.allOf(chunks.toArray(CompletableFuture[]::new));
}
private void preparePlatform(World world) {
require(CraftEngineBlocks.byId(Key.of("default:topaz_ore")) != null, "CraftEngine default block is missing");
require(CraftEngineFurniture.byId(Key.of("default:flower_basket")) != null, "CraftEngine furniture is missing");
for (int chunkX = 0; chunkX <= 2; chunkX++) {
for (Entity entity : world.getChunkAt(chunkX, 0).getEntities()) {
if (CraftEngineFurniture.isFurniture(entity)) {
CraftEngineFurniture.remove(entity, false, false);
}
}
}
for (int x = 0; x <= 47; x++) {
for (int z = 2; z <= 14; z++) {
world.getBlockAt(x, TEST_Y - 1, z).setType(Material.STONE, false);
for (int y = TEST_Y; y <= TEST_Y + 5; y++) {
world.getBlockAt(x, y, z).setType(Material.AIR, false);
}
}
}
}
private void pending(Player player, World world) {
Engine engine = IrisToolbelt.access(world).getEngine();
Mantle<Matter> mantle = engine.getMantle().getMantle();
queue(engine, world, 2, BLOCK_ID);
queue(engine, world, 4, BLOCK_ID);
queue(engine, world, 6, BLOCK_ID);
queue(engine, world, 8, BLOCK_ID);
queue(engine, world, 10, FURNITURE_ID);
queue(engine, world, 12, FURNITURE_ID);
Block chest = world.getBlockAt(14, TEST_Y, TEST_Z);
chest.setType(Material.CHEST, false);
mantle.set(14, TEST_Y - engine.getMinHeight(), TEST_Z, new TileWrapper(TileData.getTileState(chest, false)));
world.getBlockAt(4, TEST_Y, TEST_Z).setType(Material.AIR, false);
world.getBlockAt(6, TEST_Y, TEST_Z).setType(Material.COBBLESTONE, false);
NativeBlockChangeSet changes = DeferredPlacementRecords.track(engine, bounds(world));
changes.add(8, TEST_Y, TEST_Z);
changes.add(12, TEST_Y - 1, TEST_Z);
changes.add(12, TEST_Y, TEST_Z);
changes.add(14, TEST_Y, TEST_Z);
world.getBlockAt(8, TEST_Y, TEST_Z).setType(Material.AIR, false);
world.getBlockAt(12, TEST_Y - 1, TEST_Z).setType(Material.AIR, false);
chest.setType(Material.COBBLESTONE, false);
DeferredPlacementRecords.invalidate(engine, changes);
require(record(engine, 8, Identifier.class) == null, "Native cut retained a pending custom record");
require(record(engine, 12, Identifier.class) == null, "Flatten column retained pending furniture");
require(record(engine, 14, TileWrapper.class) == null, "Native wall retained pending chest data");
require(record(engine, 2, Identifier.class) != null, "Native writes invalidated an untouched custom record");
replay(engine, world, 0);
CustomBlockOwnership ownership = record(engine, 10, CustomBlockOwnership.class);
require(record(engine, 2, Identifier.class) == null, "Custom materialization did not consume pending records");
require(CraftEngineBlocks.isCustomBlock(world.getBlockAt(2, TEST_Y, TEST_Z)), "Intact custom block was not placed");
require(world.getBlockAt(4, TEST_Y, TEST_Z).getType().isAir(), "Cut custom block was resurrected");
require(world.getBlockAt(6, TEST_Y, TEST_Z).getType() == Material.COBBLESTONE, "Custom block replaced native wall");
require(world.getBlockAt(8, TEST_Y, TEST_Z).getType().isAir(), "Invalidated custom block was resurrected");
require(chest.getType() == Material.COBBLESTONE, "Chest data replaced native wall");
later(player, world, () -> {
int intactFurniture = furnitureAt(world, 10);
require(intactFurniture == 1, "Intact furniture count is " + intactFurniture
+ "; pending=" + record(engine, 10, Identifier.class)
+ "; ownership=" + ownership
+ "; chunkEntities=" + world.getChunkAt(0, 0).getEntities().length);
require(furnitureAt(world, 12) == 0, "Flattened furniture was resurrected");
player.sendMessage("CE_PENDING intact=1 air=1 wall=1 invalidated=1 flatten=1 chest=1 furniture=1");
});
}
private void neighbor(Player player, World world) {
Engine engine = IrisToolbelt.access(world).getEngine();
queue(engine, world, 18, FURNITURE_ID);
queue(engine, world, 20, FURNITURE_ID);
queue(engine, world, 22, FURNITURE_ID);
queue(engine, world, 42, FURNITURE_ID);
replay(engine, world, 1);
replay(engine, world, 2);
world.getBlockAt(40, TEST_Y, TEST_Z).setType(Material.DIAMOND_BLOCK, false);
later(player, world, () -> {
require(furnitureAt(world, 18) == 1 && furnitureAt(world, 20) == 1
&& furnitureAt(world, 22) == 1, "Neighbor furniture did not materialize");
require(furnitureAt(world, 42) == 1 && record(engine, 42, CustomBlockOwnership.class) != null,
"Regeneration target furniture did not materialize with saved ownership");
if (!J.runEntity(player, () -> player.teleportAsync(new Location(world, 40.5, TEST_Y + 3, TEST_Z + 0.5))
.whenComplete((teleported, failure) -> {
if (failure != null || !Boolean.TRUE.equals(teleported)) {
fail(player, failure == null ? new IllegalStateException("Regeneration target teleport failed") : failure);
return;
}
player.sendMessage("CE_NEIGHBOR_READY chunk=2,0 furniture=3 target=1");
}))) {
throw new IllegalStateException("Could not schedule regeneration target teleport");
}
});
}
private void invalidateLoaded(Player player, World world, Engine engine) {
require(world.getBlockAt(40, TEST_Y, TEST_Z).getType().isAir(), "Adjacent target chunk did not regenerate");
require(furnitureAt(world, 18) == 1 && furnitureAt(world, 20) == 1
&& furnitureAt(world, 22) == 1, "Neighbor furniture did not materialize");
NativeBlockChangeSet changes = DeferredPlacementRecords.track(engine, bounds(world));
changes.add(18, TEST_Y - 1, TEST_Z);
changes.add(18, TEST_Y, TEST_Z);
changes.add(22, TEST_Y, TEST_Z);
world.getBlockAt(18, TEST_Y - 1, TEST_Z).setType(Material.AIR, false);
world.getBlockAt(18, TEST_Y, TEST_Z).setType(Material.COBBLESTONE, false);
DeferredPlacementRecords.invalidate(engine, changes);
require(CraftEngineFurniture.place(new Location(world, 22.5, TEST_Y, TEST_Z + 0.5),
Key.of("default:flower_basket"), "ground", false) != null,
"Could not place replacement furniture before scheduled cleanup");
later(player, world, () -> {
require(furnitureAt(world, 42) == 0, "Regeneration retained target chunk furniture");
require(furnitureAt(world, 18) == 0, "Native write left materialized neighbor furniture");
require(furnitureAt(world, 20) == 1, "Native write removed untouched neighbor furniture");
require(furnitureAt(world, 22) == 1, "Native cleanup removed replacement furniture or retained the old one");
require(world.getBlockAt(18, TEST_Y, TEST_Z).getType() == Material.COBBLESTONE,
"Furniture cleanup replaced the native wall");
player.sendMessage("CE_LOADED removed=1 untouched=1 replacement=1 wall=1 target=1");
});
}
private void restarted(Player player, World world) {
require(furnitureAt(world, 20) == 1, "Generated neighbor furniture did not survive restart");
require(furnitureAt(world, 10) == 1, "Untouched furniture did not survive restart");
Engine engine = IrisToolbelt.access(world).getEngine();
NativeBlockChangeSet changes = DeferredPlacementRecords.track(engine, bounds(world));
changes.add(20, TEST_Y, TEST_Z);
DeferredPlacementRecords.invalidate(engine, changes);
later(player, world, () -> {
require(furnitureAt(world, 20) == 0, "Persisted furniture ownership was not invalidated after restart");
require(furnitureAt(world, 10) == 1, "Restart cleanup removed untouched furniture");
player.sendMessage("CE_RESTART removed=1 untouched=1");
});
}
private void queue(Engine engine, World world, int x, String identifier) {
ExternalDataSVC service = IrisServices.get(ExternalDataSVC.class);
BlockData data = service.getBlockData(Identifier.fromString(identifier)).orElseThrow();
require(data instanceof IrisCustomData, "Iris did not resolve real CraftEngine content: " + identifier);
IrisCustomData custom = (IrisCustomData) data;
world.getBlockAt(x, TEST_Y, TEST_Z).setBlockData(custom.getBase(), false);
engine.getMantle().getMantle().set(x, TEST_Y - engine.getMinHeight(), TEST_Z, custom.getCustom());
}
private <T> T record(Engine engine, int x, Class<T> type) {
return engine.getMantle().getMantle().get(x, TEST_Y - engine.getMinHeight(), TEST_Z, type);
}
private void replay(Engine engine, World world, int chunkX) {
Mantle<Matter> mantle = engine.getMantle().getMantle();
for (int x = chunkX - 1; x <= chunkX + 1; x++) {
for (int z = -1; z <= 1; z++) {
require(world.isChunkLoaded(x, z), "Replay neighbor is not loaded: " + x + "," + z);
}
}
require(mantle.isLoaded(world.getChunkAt(chunkX, 0)), "Replay mantle chunk is not loaded: " + chunkX);
mantle.flag(chunkX, 0, MantleFlag.ETCHED, false);
mantle.flag(chunkX, 0, MantleFlag.TILE, false);
mantle.flag(chunkX, 0, MantleFlag.CUSTOM, false);
EngineBukkitOps.updateChunk(engine, world.getChunkAt(chunkX, 0), mantle);
}
private int furnitureAt(World world, int x) {
int count = 0;
for (Entity entity : world.getChunkAt(x >> 4, TEST_Z >> 4).getEntities()) {
Location location = entity.getLocation();
if (location.getBlockX() == x && location.getBlockY() == TEST_Y && location.getBlockZ() == TEST_Z
&& CraftEngineFurniture.getLoadedFurnitureByMetaEntity(entity) != null) {
count++;
}
}
return count;
}
private void later(Player player, World world, Runnable assertion) {
Bukkit.getRegionScheduler().runDelayed(this, new Location(world, 8, TEST_Y, TEST_Z), task -> {
try {
assertion.run();
} catch (Throwable failure) {
fail(player, failure);
}
}, 20);
}
private NativeBlockChangeSet.Bounds bounds(World world) {
return new NativeBlockChangeSet.Bounds(-1, -1, 3, 1, world.getMinHeight(), world.getMaxHeight() - world.getMinHeight());
}
private void require(boolean condition, String message) {
if (!condition) {
throw new IllegalStateException(message);
}
}
private void fail(CommandSender sender, Throwable failure) {
getLogger().log(Level.SEVERE, "CraftEngine native placement assertion failed", failure);
sender.sendMessage("CE_FAILED " + failure.getMessage());
}
}
@@ -0,0 +1,32 @@
{
"name": "Flat",
"derivative": "minecraft:plains",
"vanillaDerivative": "minecraft:plains",
"layers": [
{
"minHeight": 1,
"maxHeight": 1,
"palette": [
{
"block": "minecraft:lime_wool"
}
]
},
{
"minHeight": 384,
"maxHeight": 384,
"palette": [
{
"block": "minecraft:green_concrete"
}
]
}
],
"generators": [
{
"generator": "flat",
"min": 96,
"max": 96
}
]
}
@@ -0,0 +1,33 @@
{
"name": "CraftEngine Native Fixture",
"version": 1,
"mode": {
"type": "OVERWORLD"
},
"regions": [
"flat"
],
"focusRegion": "flat",
"focus": "flat",
"landChance": 1,
"environment": "NORMAL",
"dimensionHeight": {
"min": -64,
"max": 320
},
"logicalHeight": 384,
"fluidHeight": 0,
"bedrock": false,
"rockPalette": {
"palette": [
{
"block": "minecraft:red_concrete"
}
]
},
"importedStructures": {
"disabled": [
"minecraft:"
]
}
}
@@ -0,0 +1,15 @@
{
"interpolator": {
"function": "NONE",
"horizontalScale": 1
},
"seed": 310,
"composite": [
{
"seed": 310,
"style": {
"style": "FLAT"
}
}
]
}
@@ -0,0 +1,12 @@
{
"name": "Flat",
"landBiomes": [
"flat"
],
"seaBiomes": [
"flat"
],
"shoreBiomes": [
"flat"
]
}
@@ -0,0 +1,15 @@
import { readFile } from 'node:fs/promises'
import path from 'node:path'
export default {
name: 'iris-craftengine-native-restart',
description: 'Verify persisted generated furniture ownership after a full server restart.',
async run(context) {
const source = await readFile(path.join(context.server.directory, '.server-source'), 'utf8')
context.expect(/^isolated=true\s*$/m.test(source), 'CraftEngine regression requires an isolated instance')
await context.step('native writes remove persisted ownership after restart', async () => {
const response = await context.command('/iriscenative restart', /CE_RESTART|CE_FAILED/, 60000)
context.expect(response.includes('CE_RESTART removed=1 untouched=1'), response)
})
}
}
@@ -0,0 +1,49 @@
import { readFile } from 'node:fs/promises'
import path from 'node:path'
export default {
name: 'iris-craftengine-native',
description: 'Verify deferred native invalidation and loaded neighbor cleanup with real Iris and CraftEngine.',
async run(context) {
const source = await readFile(path.join(context.server.directory, '.server-source'), 'utf8')
context.expect(/^isolated=true\s*$/m.test(source), 'CraftEngine regression requires an isolated instance')
await context.step('create and enter a real Iris world with CraftEngine content', async () => {
const response = await context.command('/iriscenative prepare', /CE_READY|CE_FAILED/, 180000)
context.expect(response.includes('CE_READY'), response)
})
await context.step('native writes remove pending block, chest, and flatten furniture records', async () => {
const response = await context.command('/iriscenative pending', /CE_PENDING|CE_FAILED/, 60000)
context.expect(response.includes('CE_PENDING intact=1 air=1 wall=1 invalidated=1 flatten=1 chest=1 furniture=1'), response)
await context.waitUntil(() => {
const origin = context.bot.entity.position.floored()
const cut = context.bot.blockAt(origin.set(4, 180, 8))
const wall = context.bot.blockAt(origin.set(6, 180, 8))
const invalidated = context.bot.blockAt(origin.set(8, 180, 8))
const chest = context.bot.blockAt(origin.set(14, 180, 8))
return cut?.name === 'air' && wall?.name === 'cobblestone'
&& invalidated?.name === 'air' && chest?.name === 'cobblestone'
}, { label: 'Client observes retained native air and walls', timeoutMs: 30000, intervalMs: 100 })
})
await context.step('regeneration removes target furniture and preserves neighbor ownership', async () => {
const ready = await context.command('/iriscenative neighbor', /CE_NEIGHBOR_READY|CE_FAILED/, 30000)
context.expect(ready.includes('CE_NEIGHBOR_READY chunk=2,0 furniture=3 target=1'), ready)
const regenerated = await context.command('/iris developer regen radius=0', /Regen complete:|Regen failed:/, 90000)
context.expect(regenerated.includes('Regen complete:'), regenerated)
const response = await context.command('/iriscenative loaded', /CE_LOADED|CE_FAILED/, 30000)
context.expect(response.includes('CE_LOADED removed=1 untouched=1 replacement=1 wall=1 target=1'), response)
})
await context.step('imported snow features replace generated furniture records', async () => {
const response = await context.command('/iriscenative features', /CE_FEATURES|CE_FAILED/, 180000)
context.expect(response.includes('CE_FEATURES snow=256 pending=0 furniture=0'), response)
await context.waitUntil(() => {
const position = context.bot.entity.position.floored()
for (let x = 0; x < 16; x++) {
for (let z = 0; z < 16; z++) {
if (context.bot.blockAt(position.set(x, 97, z))?.name !== 'snow') return false
}
}
return true
}, { label: 'Client observes 256 imported snow layers', timeoutMs: 30000, intervalMs: 100 })
})
}
}