From 6fd877ebfc86639f8bbf9a945d62425f70bc04e9 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Tue, 29 Sep 2026 03:56:40 -0400 Subject: [PATCH] Natural stronghold ring biomes - The modded structure-state biome lookup resolves from the natural true biome stream, the same as the Bukkit ring search - Stronghold ring placement no longer plans hydrology tiles around its 128 positions, so chunk structure starts stop waiting on river plans out to ~24k blocks - Ring lookups are cached per runtime and cleared with the other biome caches --- .../arcane/iris/modded/ModdedBiomePolicy.java | 34 ++++++++++++++++++- .../iris/modded/ModdedBiomePolicyTest.java | 32 +++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedBiomePolicy.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedBiomePolicy.java index cf6638152..83d69e3c3 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedBiomePolicy.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedBiomePolicy.java @@ -72,6 +72,7 @@ final class ModdedBiomePolicy implements NativeModdedBiomePolicy { private volatile BiomeHolderTable visibleBiomeCache = new BiomeHolderTable<>(); private volatile BiomeHolderTable structureBiomeCache = new BiomeHolderTable<>(); private volatile BiomeHolderTable surfaceStructureBiomeCache = new BiomeHolderTable<>(); + private volatile BiomeHolderTable naturalStructureBiomeCache = new BiomeHolderTable<>(); private volatile RuntimeCallbacks runtime; private volatile Set possibleStructureBiomeKeys; private volatile BiomeKeySets biomeKeySets; @@ -94,6 +95,7 @@ final class ModdedBiomePolicy implements NativeModdedBiomePolicy { visibleBiomeCache = new BiomeHolderTable<>(); structureBiomeCache = new BiomeHolderTable<>(); surfaceStructureBiomeCache = new BiomeHolderTable<>(); + naturalStructureBiomeCache = new BiomeHolderTable<>(); warnedUnresolvedBiomeKeys.clear(); possibleStructureBiomeKeys = null; biomeKeySets = null; @@ -104,6 +106,7 @@ final class ModdedBiomePolicy implements NativeModdedBiomePolicy { visibleBiomeCache.evictRuntime(runtimeIdentity); structureBiomeCache.evictRuntime(runtimeIdentity); surfaceStructureBiomeCache.evictRuntime(runtimeIdentity); + naturalStructureBiomeCache.evictRuntime(runtimeIdentity); } /** @@ -791,11 +794,40 @@ final class ModdedBiomePolicy implements NativeModdedBiomePolicy { if (!isReady(engine)) { throw new IllegalStateException("Iris structure biome lookup has no active engine runtime"); } - return getNoiseBiome(engine, quartX, quartY, quartZ, sampler); + return getNaturalStructureBiome(engine, quartX, quartZ); } } } + // The structure state only samples biomes for the concentric-ring search, which reaches ~24k blocks out for + // strongholds. The natural surface answers there without planning hydrology tiles, the same as the Bukkit ring search. + private H getNaturalStructureBiome(Engine engine, int quartX, int quartZ) { + long key = packColumnKey(quartX, quartZ); + int runtimeIdentity = engine.getCacheID(); + BiomeHolderTable cache = naturalStructureBiomeCache; + H cached = cache.get(runtimeIdentity, key); + if (cached != null) { + return cached; + } + NativeBiomeSourceAccess.RegistryView registry = biomeRegistry(); + if (registry == null) { + throw new IllegalStateException("Iris structure biome lookup has no biome registry"); + } + int blockX = quartX << 2; + int blockZ = quartZ << 2; + IrisBiome irisBiome = engine.getComplex().getNaturalTrueBiomeStream().get(blockX, blockZ); + if (irisBiome == null) { + throw new IllegalStateException("Iris returned no natural structure biome at block " + blockX + "," + blockZ); + } + H resolved = resolveHolder(registry, irisBiome.getStructureDerivativeKey()); + if (resolved == null) { + throw new IllegalStateException("Iris natural structure biome derivative '" + + irisBiome.getStructureDerivativeKey() + "' is not registered at block " + blockX + "," + blockZ); + } + cache.put(runtimeIdentity, key, resolved); + return resolved; + } + static long biomeResolutionSeed(long worldSeed, int blockX, int blockY, int blockZ) { return worldSeed ^ ((long) blockX * 341873128712L) diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedBiomePolicyTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedBiomePolicyTest.java index d590d36ff..644e24040 100644 --- a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedBiomePolicyTest.java +++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedBiomePolicyTest.java @@ -1,6 +1,12 @@ package art.arcane.iris.modded; +import art.arcane.iris.generation.biome.IrisBiome; +import art.arcane.iris.generation.runtime.Engine; +import art.arcane.iris.generation.runtime.GenerationSessionLease; +import art.arcane.iris.generation.runtime.IrisComplex; import art.arcane.volmlib.nativelib.terrain.NativeBiomeSourceAccess; +import art.arcane.volmlib.util.stream.ProceduralStream; +import art.arcane.volmlib.util.stream.interpolation.Interpolated; import org.junit.Test; import java.util.ArrayList; @@ -13,6 +19,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class ModdedBiomePolicyTest { @Test @@ -52,6 +61,29 @@ public class ModdedBiomePolicyTest { assertTrue(failure.getMessage().contains("example:missing")); } + @Test + public void structureRingBiomesResolveFromTheNaturalSurfaceWithoutPlanningHydrology() throws Exception { + BiomeToken forest = new BiomeToken("minecraft:forest"); + IrisBiome natural = mock(IrisBiome.class); + when(natural.getStructureDerivativeKey()).thenReturn(forest.key()); + IrisComplex complex = mock(IrisComplex.class); + when(complex.getNaturalTrueBiomeStream()).thenReturn( + ProceduralStream.of((x, z) -> natural, Interpolated.of(value -> 0D, value -> null))); + when(complex.getTrueBiomeStream()).thenThrow( + new AssertionError("Stronghold ring lookups must not plan hydrology")); + Engine engine = mock(Engine.class); + when(engine.getComplex()).thenReturn(complex); + when(engine.getMinHeight()).thenReturn(-64); + when(engine.acquireGenerationLease(anyString())).thenReturn(GenerationSessionLease.noop()); + ModdedBiomePolicy policy = new ModdedBiomePolicy<>( + new Access(new ArrayList<>(List.of(forest)))); + policy.bind(new ModdedBiomePolicy.RuntimeCallbacks(() -> engine, () -> engine, bound -> true, + () -> Set.of(forest.key()), bound -> Set.of(), () -> 0L)); + + assertSame(forest, policy.requiredStructureBiome(6000, 0, -6000, null)); + assertSame(forest, policy.requiredStructureBiome(6000, -30, -6000, null)); + } + private static ModdedBiomePolicy policy(List entries, AtomicReference> configured) { ModdedBiomePolicy policy = new ModdedBiomePolicy<>(new Access(entries));