Fixing a handfull of issues!

This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-21 18:07:10 -04:00
1 parent 1132242c58
commit c3359e0a16
30 files changed
+1324 -282

No files matched your search

@@ -245,9 +245,15 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
for (int y = groundY + 1; y <= groundY + 32; y++) {
assertTrue("Roof at " + x + "," + y, state(blocks, x, y, 0).isAir());
}
for (int y = 12; y < groundY; y++) {
int supportY = x < 2 ? groundY - 33 : 12;
for (int y = supportY; y < groundY; y++) {
assertTrue("Missing foundation at " + x + "," + y, state(blocks, x, y, 0).isSolid());
}
if (x < 2) {
for (int y = 13; y < supportY; y++) {
assertTrue("Cave beneath support at " + x + "," + y, state(blocks, x, y, 0).isAir());
}
}
}
}
@@ -268,6 +274,57 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
assertTrue(state(blocks, 0, groundY + 1, 0).isAir());
}
@Test
public void flattenPreservesCavesBelowIntactGroundAtTheSkirtEdge() {
StructureStart start = desertStart();
int groundY = start.getBoundingBox().minY() - 1;
int x = start.getBoundingBox().maxX() + 23;
int z = start.getBoundingBox().minZ();
BoundingBox area = new BoundingBox(x, 0, z, x, 127, z);
Map<BlockPos, BlockState> blocks = new HashMap<>();
for (int y = groundY - 35; y <= groundY; y++) {
BlockState block = y >= groundY - 34 && y <= groundY - 13
? Blocks.CAVE_AIR.defaultBlockState() : Blocks.STONE.defaultBlockState();
put(blocks, x, y, z, block);
}
put(blocks, x, groundY - 1, z, Blocks.DIRT.defaultBlockState());
put(blocks, x, groundY, z, Blocks.GRASS_BLOCK.defaultBlockState());
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, StructureTerrainMode.FLATTEN);
((IrisStructureTerrain) target.terrain()).setHorizontalPadding(24).setFlattenRange(64);
NativeStructureSurfaceFitter.prepareSurfaceStructures(world(blocks), area, List.of(target), (px, pz) -> groundY);
for (int y = groundY - 34; y <= groundY - 13; y++) {
assertEquals("Cave at " + y, Blocks.CAVE_AIR.defaultBlockState(), state(blocks, x, y, z));
}
assertEquals(Blocks.DIRT.defaultBlockState(), state(blocks, x, groundY - 1, z));
}
@Test
public void flattenBridgesTheFoundationGapWithoutFillingACaveBelowItsSupport() {
StructureStart start = desertStart();
int groundY = start.getBoundingBox().minY() - 1;
BoundingBox area = new BoundingBox(0, 0, 0, 0, 127, 0);
Map<BlockPos, BlockState> blocks = new HashMap<>();
put(blocks, 0, groundY, 0, Blocks.GRASS_BLOCK.defaultBlockState());
for (int y = groundY - 11; y <= groundY - 9; y++) {
put(blocks, 0, y, 0, Blocks.STONE.defaultBlockState());
}
for (int y = groundY - 20; y <= groundY - 12; y++) {
put(blocks, 0, y, 0, Blocks.CAVE_AIR.defaultBlockState());
}
NativeStructureSurfaceFitter.prepareSurfaceStructures(world(blocks), area,
List.of(surfaceTarget(start, StructureTerrainMode.FLATTEN)), (x, z) -> groundY);
for (int y = groundY - 8; y < groundY; y++) {
assertEquals("Foundation at " + y, Blocks.STONE.defaultBlockState(), state(blocks, 0, y, 0));
}
for (int y = groundY - 20; y <= groundY - 12; y++) {
assertEquals("Cave at " + y, Blocks.CAVE_AIR.defaultBlockState(), state(blocks, 0, y, 0));
}
}
@Test
public void flattenLeavesBuriedAndSubmergedInstancesUntouched() {
StructureStart start = desertStart();
@@ -619,10 +676,18 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
for (int z = 0; z <= 8; z++) {
int groundY = plan.flattenedHeights().get(NativeStructureTemplateOccupancy.columnKey(x, z)) - 1;
assertTrue("Village grade at " + x + "," + z + ": " + groundY, groundY >= 64 && groundY <= 70);
for (int y = 20; y <= groundY; y++) {
boolean originalFoundation = (x == 0 || x == 8) && (z == 0 || z == 8);
int supportY = originalFoundation ? 63 : 20;
for (int y = supportY; y <= groundY; y++) {
assertTrue("Disconnected village ground at " + x + "," + y + "," + z,
state(blocks, x, y, z).isSolid());
}
if (originalFoundation) {
for (int y = 21; y < supportY; y++) {
assertTrue("Cave beneath foundation at " + x + "," + y + "," + z,
state(blocks, x, y, z).isAir());
}
}
for (int y = groundY + 1; y <= 96; y++) {
assertTrue("Roof remains above village at " + x + "," + y + "," + z,
state(blocks, x, y, z).isAir());
@@ -9,6 +9,7 @@ import art.arcane.iris.structure.nativegen.NativeStructureVolumeIndex;
import art.arcane.iris.generation.runtime.IrisEngine;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.volmlib.nativelib.terrain.structure.NativeStructureVolume;
import art.arcane.volmlib.nativelib.terrain.structure.StructureTerrainSettings;
import art.arcane.iris.world.history.GenerationHistoryRuntimeRouter;
import art.arcane.volmlib.util.collection.KList;
import net.minecraft.SharedConstants;
@@ -69,7 +70,8 @@ public class NativeStructureVolumeIndexTest {
StructureStart start = swampHut(0, 0, 4, 6);
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
KList<NativeStructureVolume> volumes = NativeStructureVolumeSource.appendPieces(null, STRUCTURE_KEY, start);
KList<NativeStructureVolume> volumes = NativeStructureVolumeSource.appendPieces(null, STRUCTURE_KEY, start,
StructureTerrainSettings.SOURCE, (x, z) -> 63);
assertEquals(1, volumes.size());
NativeStructureVolume volume = volumes.getFirst();
@@ -84,8 +86,10 @@ public class NativeStructureVolumeIndexTest {
@Test
public void invalidStartsContributeNoVolumes() {
assertNull(NativeStructureVolumeSource.appendPieces(null, STRUCTURE_KEY, StructureStart.INVALID_START));
assertNull(NativeStructureVolumeSource.appendPieces(null, STRUCTURE_KEY, null));
assertNull(NativeStructureVolumeSource.appendPieces(null, STRUCTURE_KEY, StructureStart.INVALID_START,
StructureTerrainSettings.SOURCE, (x, z) -> 63));
assertNull(NativeStructureVolumeSource.appendPieces(null, STRUCTURE_KEY, null,
StructureTerrainSettings.SOURCE, (x, z) -> 63));
}
@Test
@@ -323,7 +327,7 @@ public class NativeStructureVolumeIndexTest {
BlockingResolver coordination = new BlockingResolver();
NativeStructureVolumeIndex.OriginResolver volumes = (engine, chunkX, chunkZ) ->
new KList<>(new NativeStructureVolume(chunkX + ":" + chunkZ,
-256, 0, -256, 256, 255, 256));
-256, 0, -256, 256, 255, 256, null));
NativeStructureVolumeIndex index = NativeStructureVolumeIndex.forTesting((engine, chunkX, chunkZ) -> {
coordination.volumesAt(engine, chunkX, chunkZ);
return volumes.volumesAt(engine, chunkX, chunkZ);
@@ -369,7 +373,7 @@ public class NativeStructureVolumeIndexTest {
terrain.join();
}
return new KList<>(new NativeStructureVolume(chunkX + ":" + chunkZ,
-256, 0, -256, 256, 100, 256));
-256, 0, -256, 256, 100, 256, null));
});
ExecutorService owner = Executors.newSingleThreadExecutor();
ForkJoinPool workers = new ForkJoinPool(2);
@@ -544,7 +548,8 @@ public class NativeStructureVolumeIndexTest {
if (chunkX != originChunkX || chunkZ != originChunkZ) {
return NativeStructureVolume.NONE;
}
return NativeStructureVolumeSource.appendPieces(null, STRUCTURE_KEY, swampHut(chunkX, chunkZ, 4, 6));
return NativeStructureVolumeSource.appendPieces(null, STRUCTURE_KEY, swampHut(chunkX, chunkZ, 4, 6),
StructureTerrainSettings.SOURCE, (x, z) -> 63);
}
}
@@ -596,7 +601,7 @@ public class NativeStructureVolumeIndexTest {
return new KList<>(new NativeStructureVolume(
chunkX + ":" + chunkZ,
0, 0, 0,
15, 255, 15));
15, 255, 15, null));
}
}
private record ScopedEngine(IrisEngine engine, AtomicInteger scopes) {
@@ -245,9 +245,15 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
for (int y = groundY + 1; y <= groundY + 32; y++) {
assertTrue("Roof at " + x + "," + y, state(blocks, x, y, 0).isAir());
}
for (int y = 12; y < groundY; y++) {
int supportY = x < 2 ? groundY - 33 : 12;
for (int y = supportY; y < groundY; y++) {
assertTrue("Missing foundation at " + x + "," + y, state(blocks, x, y, 0).isSolid());
}
if (x < 2) {
for (int y = 13; y < supportY; y++) {
assertTrue("Cave beneath support at " + x + "," + y, state(blocks, x, y, 0).isAir());
}
}
}
}
@@ -268,6 +274,57 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
assertTrue(state(blocks, 0, groundY + 1, 0).isAir());
}
@Test
public void flattenPreservesCavesBelowIntactGroundAtTheSkirtEdge() {
StructureStart start = desertStart();
int groundY = start.getBoundingBox().minY() - 1;
int x = start.getBoundingBox().maxX() + 23;
int z = start.getBoundingBox().minZ();
BoundingBox area = new BoundingBox(x, 0, z, x, 127, z);
Map<BlockPos, BlockState> blocks = new HashMap<>();
for (int y = groundY - 35; y <= groundY; y++) {
BlockState block = y >= groundY - 34 && y <= groundY - 13
? Blocks.CAVE_AIR.defaultBlockState() : Blocks.STONE.defaultBlockState();
put(blocks, x, y, z, block);
}
put(blocks, x, groundY - 1, z, Blocks.DIRT.defaultBlockState());
put(blocks, x, groundY, z, Blocks.GRASS_BLOCK.defaultBlockState());
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, StructureTerrainMode.FLATTEN);
((IrisStructureTerrain) target.terrain()).setHorizontalPadding(24).setFlattenRange(64);
NativeStructureSurfaceFitter.prepareSurfaceStructures(world(blocks), area, List.of(target), (px, pz) -> groundY);
for (int y = groundY - 34; y <= groundY - 13; y++) {
assertEquals("Cave at " + y, Blocks.CAVE_AIR.defaultBlockState(), state(blocks, x, y, z));
}
assertEquals(Blocks.DIRT.defaultBlockState(), state(blocks, x, groundY - 1, z));
}
@Test
public void flattenBridgesTheFoundationGapWithoutFillingACaveBelowItsSupport() {
StructureStart start = desertStart();
int groundY = start.getBoundingBox().minY() - 1;
BoundingBox area = new BoundingBox(0, 0, 0, 0, 127, 0);
Map<BlockPos, BlockState> blocks = new HashMap<>();
put(blocks, 0, groundY, 0, Blocks.GRASS_BLOCK.defaultBlockState());
for (int y = groundY - 11; y <= groundY - 9; y++) {
put(blocks, 0, y, 0, Blocks.STONE.defaultBlockState());
}
for (int y = groundY - 20; y <= groundY - 12; y++) {
put(blocks, 0, y, 0, Blocks.CAVE_AIR.defaultBlockState());
}
NativeStructureSurfaceFitter.prepareSurfaceStructures(world(blocks), area,
List.of(surfaceTarget(start, StructureTerrainMode.FLATTEN)), (x, z) -> groundY);
for (int y = groundY - 8; y < groundY; y++) {
assertEquals("Foundation at " + y, Blocks.STONE.defaultBlockState(), state(blocks, 0, y, 0));
}
for (int y = groundY - 20; y <= groundY - 12; y++) {
assertEquals("Cave at " + y, Blocks.CAVE_AIR.defaultBlockState(), state(blocks, 0, y, 0));
}
}
@Test
public void flattenLeavesBuriedAndSubmergedInstancesUntouched() {
StructureStart start = desertStart();
@@ -619,10 +676,18 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
for (int z = 0; z <= 8; z++) {
int groundY = plan.flattenedHeights().get(NativeStructureTemplateOccupancy.columnKey(x, z)) - 1;
assertTrue("Village grade at " + x + "," + z + ": " + groundY, groundY >= 64 && groundY <= 70);
for (int y = 20; y <= groundY; y++) {
boolean originalFoundation = (x == 0 || x == 8) && (z == 0 || z == 8);
int supportY = originalFoundation ? 63 : 20;
for (int y = supportY; y <= groundY; y++) {
assertTrue("Disconnected village ground at " + x + "," + y + "," + z,
state(blocks, x, y, z).isSolid());
}
if (originalFoundation) {
for (int y = 21; y < supportY; y++) {
assertTrue("Cave beneath foundation at " + x + "," + y + "," + z,
state(blocks, x, y, z).isAir());
}
}
for (int y = groundY + 1; y <= 96; y++) {
assertTrue("Roof remains above village at " + x + "," + y + "," + z,
state(blocks, x, y, z).isAir());
@@ -9,6 +9,7 @@ import art.arcane.iris.structure.nativegen.NativeStructureVolumeIndex;
import art.arcane.iris.generation.runtime.IrisEngine;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.volmlib.nativelib.terrain.structure.NativeStructureVolume;
import art.arcane.volmlib.nativelib.terrain.structure.StructureTerrainSettings;
import art.arcane.iris.world.history.GenerationHistoryRuntimeRouter;
import art.arcane.volmlib.util.collection.KList;
import net.minecraft.SharedConstants;
@@ -69,7 +70,8 @@ public class NativeStructureVolumeIndexTest {
StructureStart start = swampHut(0, 0, 4, 6);
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
KList<NativeStructureVolume> volumes = NativeStructureVolumeSource.appendPieces(null, STRUCTURE_KEY, start);
KList<NativeStructureVolume> volumes = NativeStructureVolumeSource.appendPieces(null, STRUCTURE_KEY, start,
StructureTerrainSettings.SOURCE, (x, z) -> 63);
assertEquals(1, volumes.size());
NativeStructureVolume volume = volumes.getFirst();
@@ -84,8 +86,10 @@ public class NativeStructureVolumeIndexTest {
@Test
public void invalidStartsContributeNoVolumes() {
assertNull(NativeStructureVolumeSource.appendPieces(null, STRUCTURE_KEY, StructureStart.INVALID_START));
assertNull(NativeStructureVolumeSource.appendPieces(null, STRUCTURE_KEY, null));
assertNull(NativeStructureVolumeSource.appendPieces(null, STRUCTURE_KEY, StructureStart.INVALID_START,
StructureTerrainSettings.SOURCE, (x, z) -> 63));
assertNull(NativeStructureVolumeSource.appendPieces(null, STRUCTURE_KEY, null,
StructureTerrainSettings.SOURCE, (x, z) -> 63));
}
@Test
@@ -323,7 +327,7 @@ public class NativeStructureVolumeIndexTest {
BlockingResolver coordination = new BlockingResolver();
NativeStructureVolumeIndex.OriginResolver volumes = (engine, chunkX, chunkZ) ->
new KList<>(new NativeStructureVolume(chunkX + ":" + chunkZ,
-256, 0, -256, 256, 255, 256));
-256, 0, -256, 256, 255, 256, null));
NativeStructureVolumeIndex index = NativeStructureVolumeIndex.forTesting((engine, chunkX, chunkZ) -> {
coordination.volumesAt(engine, chunkX, chunkZ);
return volumes.volumesAt(engine, chunkX, chunkZ);
@@ -369,7 +373,7 @@ public class NativeStructureVolumeIndexTest {
terrain.join();
}
return new KList<>(new NativeStructureVolume(chunkX + ":" + chunkZ,
-256, 0, -256, 256, 100, 256));
-256, 0, -256, 256, 100, 256, null));
});
ExecutorService owner = Executors.newSingleThreadExecutor();
ForkJoinPool workers = new ForkJoinPool(2);
@@ -544,7 +548,8 @@ public class NativeStructureVolumeIndexTest {
if (chunkX != originChunkX || chunkZ != originChunkZ) {
return NativeStructureVolume.NONE;
}
return NativeStructureVolumeSource.appendPieces(null, STRUCTURE_KEY, swampHut(chunkX, chunkZ, 4, 6));
return NativeStructureVolumeSource.appendPieces(null, STRUCTURE_KEY, swampHut(chunkX, chunkZ, 4, 6),
StructureTerrainSettings.SOURCE, (x, z) -> 63);
}
}
@@ -596,7 +601,7 @@ public class NativeStructureVolumeIndexTest {
return new KList<>(new NativeStructureVolume(
chunkX + ":" + chunkZ,
0, 0, 0,
15, 255, 15));
15, 255, 15, null));
}
}
private record ScopedEngine(IrisEngine engine, AtomicInteger scopes) {
@@ -0,0 +1,61 @@
import { readFile } from 'node:fs/promises'
import path from 'node:path'
const world = 'native-terrain-support'
const air = new Set(['air', 'cave_air', 'void_air'])
export default {
name: 'iris-native-terrain-support',
description: 'Verify native terrain fitting preserves buried caves and excludes trees from lowered ground.',
async run(context) {
const bot = context.bot
const source = await readFile(path.join(context.server.directory, '.server-source'), 'utf8')
context.expect(/^isolated=true\s*$/m.test(source), 'Requires an isolated instance')
const position = bot.entity.position.clone()
const at = (x, y, z) => bot.blockAt(position.clone().set(x, y, z))
const teleport = async (x, y, z) => {
await context.command(`/minecraft:tp @s ${x + 0.5} ${y} ${z + 0.5}`, /Teleported/i, 60000)
await context.waitUntil(() => Math.abs(bot.entity.position.x - x - 0.5) < 1
&& Math.abs(bot.entity.position.z - z - 0.5) < 1 && at(x, y, z),
{ label: 'Coordinate chunk received', timeoutMs: 90000, intervalMs: 100 })
}
await context.step('enter the seeded overworld', async () => {
if (context.options.command !== 'reuse') {
await context.command(`/iris create name=${world} type=overworld seed=1337`,
/Successfully created your world/i, 240000)
}
await context.command(`/iris tp ${world}`)
await context.waitUntil(async () => {
const snapshot = await context.observe()
const player = snapshot.players.find(entry => entry.username === bot.username)
return snapshot.worlds.some(entry => entry.id === player?.world && entry.name.endsWith(world))
}, { label: 'Entered seeded overworld', timeoutMs: 90000, intervalMs: 100 })
await context.command('/minecraft:gamemode spectator @s', /Spectator Mode/i)
await context.command('/minecraft:seed', /Seed:.*1337/)
})
await context.step('preserve caves beneath the ruined portal terrain skirt', async () => {
await teleport(995, 44, -3611)
await context.waitUntil(() => at(990, 43, -3626) && at(992, 43, -3627),
{ label: 'Cave columns received', timeoutMs: 90000, intervalMs: 100 })
for (const [x, z, bottom, top] of [[990, -3626, 33, 54], [992, -3627, 34, 55]]) {
for (let y = bottom; y <= top; y++) {
const block = at(x, y, z)
context.expect(block && air.has(block.name), 'Native terrain fitting filled a buried cave',
{ x, y, z, material: block?.name })
}
await context.command(`/execute if block ${x} 43 ${z} minecraft:cave_air run say CAVE_PRESERVED_${x}`,
new RegExp(`CAVE_PRESERVED_${x}`))
}
})
await context.step('exclude the pine whose support is removed by village grading', async () => {
await teleport(-2601, 174, -7063)
await context.waitUntil(() => at(-2601, 167, -7063),
{ label: 'Pine column received', timeoutMs: 90000, intervalMs: 100 })
const block = at(-2601, 167, -7063)
context.expect(air.has(block.name), 'Tree remains above terrain removed by native grading',
{ x: -2601, y: 167, z: -7063, material: block.name })
await context.command('/execute if block -2601 167 -7063 minecraft:air run say PINE_EXCLUDED',
/PINE_EXCLUDED/)
})
}
}
@@ -0,0 +1,20 @@
{
"name": "Flat",
"derivative": "minecraft:plains",
"vanillaDerivative": "minecraft:plains",
"layers": [
{"minHeight": 1, "maxHeight": 1, "palette": [{"block": "minecraft:lime_wool"}]},
{"minHeight": 128, "maxHeight": 128, "palette": [{"block": "minecraft:stone"}]}
],
"generators": [{"generator": "flat", "min": 32, "max": 32}],
"objects": [{
"place": ["smolshroom1"],
"chance": 1,
"density": 8,
"mode": "ORGANIC_STILT",
"rotation": {"enabled": false},
"scale": {"size": 0.75, "interpolation": "NONE"},
"translate": {"y": 6},
"stiltSettings": {"organicMaxScan": 8, "organicJitter": 0, "organicScratch": 0}
}]
}
@@ -0,0 +1,15 @@
{
"name": "Object Scaling Fixture",
"version": 1,
"mode": {"type": "OVERWORLD"},
"regions": ["flat"],
"focusRegion": "flat",
"focus": "flat",
"landChance": 1,
"environment": "NORMAL",
"dimensionHeight": {"min": 0, "max": 128},
"logicalHeight": 128,
"fluidHeight": 0,
"bedrock": false,
"caveProfile": {"enabled": false}
}
@@ -0,0 +1,5 @@
{
"seed": 310,
"interpolator": {"function": "NONE", "horizontalScale": 1},
"composite": [{"seed": 310, "style": {"style": "FLAT"}}]
}
@@ -0,0 +1,6 @@
{
"name": "Flat",
"landBiomes": ["flat"],
"seaBiomes": ["flat"],
"shoreBiomes": ["flat"]
}
@@ -0,0 +1,105 @@
import { createHash } from 'node:crypto'
import { readFile, realpath } from 'node:fs/promises'
import path from 'node:path'
export default {
name: 'iris-object-scaling',
description: 'Verify fractional mushroom scaling retains its cap and uses its stem for organic supports.',
async run(context) {
const directory = await realpath(context.server.directory)
const source = await readFile(path.join(directory, '.server-source'), 'utf8')
context.expect(/^isolated=true\s*$/m.test(source), 'Object scaling requires an isolated instance')
const pack = await realpath(path.join(directory, 'plugins', 'Iris', 'packs', 'object-scaling-fixture'))
context.expect(pack.startsWith(`${directory}${path.sep}`), 'Object scaling requires a local fixture pack')
const object = await readFile(path.join(pack, 'objects', 'smolshroom1.iob'))
const objectHash = createHash('sha256').update(object).digest('hex')
const biome = JSON.parse(await readFile(path.join(pack, 'biomes', 'flat.json'), 'utf8'))
const surfaceY = biome.generators[0].min
const originalStemY = surfaceY + biome.objects[0].translate.y
const expectedCapY = originalStemY + 1
const maximumExtension = biome.objects[0].stiltSettings.organicMaxScan
context.expect(surfaceY === 32 && expectedCapY === 39 && maximumExtension === 8,
'Expected the bounded organic support fixture', { surfaceY, expectedCapY, maximumExtension })
const world = context.options.command ?? 'object_scaling_probe'
context.expect(/^[a-z0-9_]+$/.test(world), 'Fixture world name must be a simple identifier')
await context.step('create the fractional mushroom fixture world', async () => {
const response = await context.command(`/iris create name=${world} type=object-scaling-fixture seed=1337`,
/Successfully created your world|World ready|Failed to create|Exception raised/i, 180000)
context.expect(/Successfully created your world|World ready/i.test(response), response)
await context.command(`/iris tp ${world}`)
await context.waitUntil(async () => {
const observation = await context.observe()
const player = observation.players.find(entry => entry.username === context.bot.username)
const current = observation.worlds.find(entry => entry.id === player?.world)
return current?.key === `iris:${world}` || current?.name.endsWith(world)
}, { label: 'Enter the scaling fixture world', timeoutMs: 60000, intervalMs: 100 })
await context.command('/minecraft:gamemode spectator @s')
await context.waitUntil(() => context.bot.game.gameMode === 'spectator',
{ label: 'Spectator mode', timeoutMs: 30000, intervalMs: 100 })
await context.command('/minecraft:tp @s 0.5 48 0.5', /Teleported/i, 30000)
})
const columns = []
await context.step('client sees one cap and continuous stem supports', async () => {
const position = context.bot.entity.position.clone()
await context.waitUntil(() => {
if (Math.abs(context.bot.entity.position.x - 0.5) > 1
|| Math.abs(context.bot.entity.position.z - 0.5) > 1) return false
for (const x of [-16, 0, 16, 31]) {
for (const z of [-16, 0, 16, 31]) {
for (const y of [0, 32, 63]) {
if (!context.bot.blockAt(position.set(x, y, z))) return false
}
}
}
return true
}, { label: 'Complete mushroom fixture chunks', timeoutMs: 60000, intervalMs: 100 })
for (let x = -16; x < 32; x++) {
for (let z = -16; z < 32; z++) {
const caps = []
const stems = []
for (let y = 1; y < 63; y++) {
const block = context.bot.blockAt(position.set(x, y, z))
context.expect(block !== null, 'Every sampled mushroom block must be loaded', { x, y, z })
if (block.name === 'red_mushroom_block') caps.push(y)
if (block.name === 'mushroom_stem') stems.push(y)
}
if (caps.length === 0) continue
context.expect(caps.length === 1, 'Mushroom cap was copied into its stem or support', { x, z, caps })
const capY = caps[0]
context.expect(capY === expectedCapY, 'Fractional scaling changed the expected mushroom height',
{ x, z, capY, expectedCapY })
const bottomY = stems[0]
const stemBlocks = stems.length
context.expect(bottomY >= originalStemY - maximumExtension && bottomY <= surfaceY + 1,
'Mushroom support must reach the surface within its configured extension limit',
{ x, z, capY, bottomY, surfaceY, maximumExtension, stems })
context.expect(stemBlocks === capY - bottomY
&& stems.every((y, index) => y === bottomY + index),
'Scaled mushroom must have a continuous stem from its cap to the ground', { x, z, capY, stems })
const supportY = bottomY - 1
const support = context.bot.blockAt(position.set(x, supportY, z))?.name
const above = context.bot.blockAt(position.set(x, capY + 1, z))?.name
context.expect(support === (supportY === surfaceY ? 'lime_wool' : 'stone'),
'Mushroom stem must terminate on the fixture terrain', { x, z, capY, supportY, support })
context.expect(above === 'air', 'Scaled mushroom has unexpected blocks above its cap', { x, z, capY, above })
columns.push({ x, z, surfaceY, capY, bottomY, stemBlocks,
buriedStemBlocks: Math.max(0, surfaceY - bottomY + 1) })
}
}
context.expect(columns.length >= 8, 'Expected at least eight scaled mushroom columns', { columns })
})
await context.step('server confirms the sampled stem and cap materials', async () => {
for (const [index, column] of columns.slice(0, 8).entries()) {
const { x, z, surfaceY, capY, bottomY } = column
const marker = `IRIS_SCALING_OK_${index}`
await context.command(`/minecraft:execute if block ${x} ${surfaceY + 1} ${z} minecraft:mushroom_stem`
+ ` if block ${x} ${bottomY} ${z} minecraft:mushroom_stem`
+ ` if block ${x} ${capY} ${z} minecraft:red_mushroom_block run minecraft:say ${marker}`,
new RegExp(marker), 10000)
}
})
context.report.objectScaling = { world, scale: 0.75, object: 'smolshroom1', objectHash,
surfaceY, maximumExtension, checkedColumns: columns.length,
serverConfirmedColumns: Math.min(8, columns.length), columns }
}
}
@@ -0,0 +1,82 @@
import { createHash } from 'node:crypto'
import { readFile, writeFile } from 'node:fs/promises'
import path from 'node:path'
const worlds = [
{ name: 'seed-selection-default', seed: '1337', argument: '' },
{ name: 'seed-selection-positive', seed: '78264193', argument: ' seed=78264193' },
{ name: 'seed-selection-negative', seed: '-734829104958217364', argument: ' seed=-734829104958217364' },
{ name: 'seed-selection-zero', seed: '0', argument: ' seed=0' }
]
export default {
name: 'iris-seed-selection',
description: 'Verify omitted, positive, negative, and zero creation seeds against visible terrain and a cold restart.',
async run(context) {
const source = await readFile(path.join(context.server.directory, '.server-source'), 'utf8')
context.expect(/^isolated=true\s*$/m.test(source), 'Seed selection requires an isolated instance')
const wait = (predicate, label) => context.waitUntil(predicate, { label, timeoutMs: 60000, intervalMs: 100 })
const restart = context.options.command === 'restart-check'
const stateFile = path.join(context.server.directory, '.seed-selection.json')
const saved = restart ? JSON.parse(await readFile(stateFile, 'utf8')) : undefined
const jarHash = createHash('sha256').update(await readFile(path.join(context.server.directory, 'plugins', 'Iris.jar'))).digest('hex')
const processId = (await context.observe()).processId
if (restart) {
context.expect(saved.processId !== processId, 'Restart-check requires a cold server restart')
context.expect(saved.jarHash === jarHash, 'Restart-check requires the same Iris jar')
}
const outputs = {}
for (const world of worlds) {
await context.step(`${restart ? 'Reload' : 'Create'} ${world.name} with seed ${world.seed}`, async () => {
if (!restart) {
await context.command(`/iris create name=${world.name} type=cohesion${world.argument}`,
/Successfully created your world/i, 180000)
}
await context.command(`/iris tp ${world.name}`)
await wait(async () => {
const observation = await context.observe()
const player = observation.players.find(entry => entry.username === context.bot.username)
const current = observation.worlds.find(entry => entry.id === player?.world)
return current?.key === `iris:${world.name}` || current?.name.endsWith(world.name)
}, `Enter ${world.name}`)
await context.command('/minecraft:gamemode spectator @s')
await wait(() => context.bot.game.gameMode === 'spectator', 'Spectator mode')
await context.command('/minecraft:seed', new RegExp(`Seed:\\s*\\[?${world.seed}\\]?(?:\\s|$)`))
const x = 1024
const z = -1024
await context.command(`/minecraft:tp @s ${x + 8.5} 48 ${z + 8.5}`, /Teleported/i, 30000)
await wait(() => Math.abs(context.bot.entity.position.x - x - 8.5) < 1
&& Math.abs(context.bot.entity.position.z - z - 8.5) < 1, 'Sample teleport')
const position = context.bot.entity.position.clone()
await wait(() => context.bot.blockAt(position.clone().set(x, 0, z))
&& context.bot.blockAt(position.clone().set(x + 15, 63, z + 15)), 'Complete sample chunk')
const digest = createHash('sha256')
let stone = 0
for (let dx = 0; dx < 16; dx++) {
for (let dz = 0; dz < 16; dz++) {
for (let y = 0; y < 64; y++) {
const block = context.bot.blockAt(position.set(x + dx, y, z + dz))
context.expect(block !== null, 'Missing sampled block', { x: x + dx, y, z: z + dz })
digest.update(`${block.stateId},`)
if (block.name === 'stone') stone++
}
}
}
context.expect(stone > 1000, 'Fixture terrain was not generated', { world: world.name, stone })
const blocks = digest.digest('hex')
outputs[world.name] = { seed: world.seed, blocks }
if (restart) {
context.expect(saved.worlds[world.name].seed === world.seed, 'Seed expectation changed after restart')
context.expect(saved.worlds[world.name].blocks === blocks, 'Persisted terrain changed after restart',
{ world: world.name, before: saved.worlds[world.name].blocks, after: blocks })
}
})
}
const signatures = new Set(Object.values(outputs).map(output => output.blocks))
context.expect(signatures.size === worlds.length, 'Different requested seeds generated identical sample chunks', outputs)
context.report.seedSelection = { restart, jarHash, worlds: outputs }
if (!restart) {
await writeFile(stateFile, JSON.stringify({ processId, jarHash, worlds: outputs }))
}
}
}