Added dimension stacking

- Dimension files define upright top-to-bottom terrain layers
- Spacer values set exact air gaps between layers
- Optional noise styles shape blended layer boundaries
- Runtime queries resolve stacked terrain and biome ownership
- Pack validation rejects invalid stack definitions
- Project copy and hotload paths include stack dependencies
- Superflat fixtures cover gaps, blending, clipping, and fluids
- Platform tests cover stacked world generation contracts
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-04 14:23:15 -04:00
1 parent 366033a535
commit 6549b40797
119 files changed
+6874 -743

No files matched your search

@@ -4,6 +4,9 @@ import art.arcane.iris.platform.bukkit.BukkitWorldBinding;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.MapCodec;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.engine.DimensionStackContext;
import art.arcane.iris.engine.DimensionStackLayout;
import art.arcane.iris.engine.DimensionTerrainContext;
import art.arcane.iris.engine.data.cache.AtomicCache;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.GenerationSessionException;
@@ -16,6 +19,7 @@ import art.arcane.iris.util.project.context.IrisContext;
import art.arcane.volmlib.util.collection.KMap;
import art.arcane.volmlib.util.math.RNG;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Holder;
import net.minecraft.core.QuartPos;
import net.minecraft.core.Registry;
@@ -24,6 +28,8 @@ import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.util.RandomSource;
import net.minecraft.util.Mth;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.biome.Climate;
@@ -80,27 +86,31 @@ public class CustomBiomeSource extends BiomeSource {
this.cacheRuntimeId = engine.getCacheID();
}
private static List<Holder<Biome>> getAllBiomes(Registry<Biome> customRegistry, Registry<Biome> registry, Engine engine) {
private static List<Holder<Biome>> getAllBiomes(
Registry<Biome> customRegistry,
Registry<Biome> registry,
Engine engine,
boolean includeDimensionStack
) {
LinkedHashSet<Holder<Biome>> biomes = new LinkedHashSet<>();
for (IrisBiome i : engine.getAllBiomes()) {
for (OwnedBiome ownedBiome : getHostOwnedBiomes(engine)) {
IrisBiome i = ownedBiome.biome();
Holder<Biome> vanillaHolder = resolveBiomeHolder(registry, i.getStructureDerivativeKey());
if (vanillaHolder == null) {
throw new IllegalStateException("Iris structure biome derivative '"
+ i.getStructureDerivativeKey() + "' is not registered for biome '" + i.getLoadKey() + "'");
}
biomes.add(vanillaHolder);
}
if (i.isCustom()) {
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
Holder<Biome> customHolder = resolveCustomBiomeHolder(customRegistry, engine, j.getId());
if (customHolder == null) {
throw new IllegalStateException("Iris custom structure biome '"
+ engine.getDimension().getLoadKey() + ":" + j.getId() + "' is not registered");
}
biomes.add(customHolder);
}
}
if (includeDimensionStack) {
biomes.addAll(getVisibleBiomes(
customRegistry,
registry,
engine,
resolveFallbackBiome(registry, customRegistry)
));
}
if (biomes.isEmpty()) {
@@ -110,6 +120,57 @@ public class CustomBiomeSource extends BiomeSource {
return new ArrayList<>(biomes);
}
private static List<Holder<Biome>> getVisibleBiomes(
Registry<Biome> customRegistry,
Registry<Biome> registry,
Engine engine,
Holder<Biome> fallback
) {
LinkedHashSet<Holder<Biome>> biomes = new LinkedHashSet<>();
boolean fallbackPossible = false;
for (OwnedBiome ownedBiome : getOwnedBiomes(engine)) {
IrisBiome irisBiome = ownedBiome.biome();
if (irisBiome.isCustom()) {
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
Holder<Biome> customHolder = resolveCustomBiomeHolder(
customRegistry, ownedBiome.dimension(), customBiome.getId());
if (customHolder == null) {
throw new IllegalStateException("Iris custom visible biome '"
+ customBiomeKey(ownedBiome.dimension(), customBiome.getId())
+ "' is not registered");
}
biomes.add(customHolder);
}
continue;
}
fallbackPossible |= !addVisibleBiomeHolder(
biomes, registry, irisBiome.getDerivativeKey());
for (String scatter : irisBiome.getBiomeScatter()) {
fallbackPossible |= !addVisibleBiomeHolder(biomes, registry, scatter);
}
for (String scatter : irisBiome.getBiomeSkyScatter()) {
fallbackPossible |= !addVisibleBiomeHolder(biomes, registry, scatter);
}
}
if (fallbackPossible && fallback != null) {
biomes.add(fallback);
}
return new ArrayList<>(biomes);
}
private static boolean addVisibleBiomeHolder(
Set<Holder<Biome>> biomes,
Registry<Biome> registry,
String biomeKey
) {
Holder<Biome> holder = resolveBiomeHolder(registry, biomeKey);
if (holder == null) {
return false;
}
biomes.add(holder);
return true;
}
private static Object getFor(Class<?> type, Object source) {
Object o = fieldFor(type, source);
@@ -175,10 +236,19 @@ public class CustomBiomeSource extends BiomeSource {
@Override
protected Stream<Holder<Biome>> collectPossibleBiomes() {
return possibleStructureBiomes().stream();
return possibleBiomes(true).stream();
}
@Override
public Set<Holder<Biome>> possibleBiomes() {
return possibleBiomes(true);
}
Set<Holder<Biome>> possibleStructureBiomes() {
return possibleBiomes(false);
}
private Set<Holder<Biome>> possibleBiomes(boolean includeDimensionStack) {
GenerationSessionLease lease = tryAcquireGenerationLease("bukkit_possible_biomes");
if (lease == null) {
throw new IllegalStateException("Iris possible biome lookup was rejected during an engine transition");
@@ -197,7 +267,8 @@ public class CustomBiomeSource extends BiomeSource {
.lookup(Registries.BIOME).orElse(null);
Registry<Biome> worldRegistry = ((CraftWorld) world).getHandle().registryAccess()
.lookup(Registries.BIOME).orElse(null);
return Set.copyOf(getAllBiomes(customRegistry, worldRegistry, engine));
return Set.copyOf(getAllBiomes(
customRegistry, worldRegistry, engine, includeDimensionStack));
}
}
@@ -207,18 +278,22 @@ public class CustomBiomeSource extends BiomeSource {
return m;
}
for (IrisBiome i : engine.getAllBiomes()) {
for (OwnedBiome ownedBiome : getOwnedBiomes(engine)) {
IrisBiome i = ownedBiome.biome();
if (i.isCustom()) {
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
Holder<Biome> holder = resolveCustomBiomeHolder(customRegistry, engine, j.getId());
String key = customBiomeKey(ownedBiome.dimension(), j.getId());
Holder<Biome> holder = resolveCustomBiomeHolder(
customRegistry, ownedBiome.dimension(), j.getId());
if (holder == null) {
if (fallback != null) {
m.put(j.getId(), fallback);
m.put(key, fallback);
}
IrisLogging.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
IrisLogging.error("Cannot find biome for IrisBiomeCustom " + key
+ " from engine " + engine.getName());
continue;
}
m.put(j.getId(), holder);
m.put(key, holder);
}
}
}
@@ -232,7 +307,8 @@ public class CustomBiomeSource extends BiomeSource {
return Collections.unmodifiableMap(spawnBiomes);
}
for (IrisBiome irisBiome : engine.getAllBiomes()) {
for (OwnedBiome ownedBiome : getOwnedBiomes(engine)) {
IrisBiome irisBiome = ownedBiome.biome();
if (!irisBiome.isCustom()) {
continue;
}
@@ -241,7 +317,8 @@ public class CustomBiomeSource extends BiomeSource {
continue;
}
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
Holder<Biome> customHolder = resolveCustomBiomeHolder(customRegistry, engine, customBiome.getId());
Holder<Biome> customHolder = resolveCustomBiomeHolder(
customRegistry, ownedBiome.dimension(), customBiome.getId());
if (customHolder != null) {
spawnBiomes.putIfAbsent(customHolder.value(), vanillaHolder);
}
@@ -268,6 +345,34 @@ public class CustomBiomeSource extends BiomeSource {
}
}
Holder<Biome> getVisibleSurfaceBiome(int blockX, int blockZ) {
GenerationSessionLease lease = tryAcquireGenerationLease("bukkit_surface_spawn_biome");
if (lease == null) {
return null;
}
try (lease; IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
if (!isRuntimeAvailable()) {
return null;
}
ensureCachesCurrent();
int quartX = QuartPos.fromBlock(blockX);
int quartZ = QuartPos.fromBlock(blockZ);
int sampleX = QuartPos.toBlock(quartX);
int sampleZ = QuartPos.toBlock(quartZ);
boolean cacheable = isBiomeCacheable(engine, sampleX, sampleZ);
DimensionStackContext stackContext = engine.getDimensionStackContext();
DimensionStackLayout.Layer layer = stackContext == null
? null
: stackContext.getLayout(sampleX, sampleZ).surfaceLayer();
int internalY = layer == null
? Engine.hostHeight(engine, sampleX, sampleZ, true)
: layer.clippedSurfaceY();
int quartY = QuartPos.fromBlock(internalY + engine.getWorld().minHeight());
return getVisibleNoiseBiomeWithActiveGenerationLease(
quartX, quartY, quartZ, null, null, cacheable);
}
}
private RegistryAccess registry() {
RegistryAccess access = registryAccess.aquire(() -> (RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer()));
@@ -294,28 +399,38 @@ public class CustomBiomeSource extends BiomeSource {
throw new IllegalStateException("Iris structure biome lookup has no active engine runtime");
}
ensureCachesCurrent();
if (isGuaranteedSurfaceBiome(y)) {
return getSurfaceStructureBiomeHolder(x, z);
}
return getStructureNoiseBiomeWithActiveGenerationLease(x, y, z);
}
}
long cacheKey = packNoiseKey(x, y, z);
private Holder<Biome> getStructureNoiseBiomeWithActiveGenerationLease(int x, int y, int z) {
if (isGuaranteedSurfaceBiome(y)) {
return getSurfaceStructureBiomeHolder(x, z);
}
boolean cacheable = isBiomeCacheable(x, z);
long cacheKey = packNoiseKey(x, y, z);
if (cacheable) {
Holder<Biome> cachedHolder = structureBiomeCache.get(cacheKey);
if (cachedHolder != null) {
return cachedHolder;
}
}
Holder<Biome> resolvedHolder = resolveStructureBiomeHolder(x, y, z);
Holder<Biome> existingHolder = structureBiomeCache.putIfAbsent(cacheKey, resolvedHolder);
if (existingHolder != null) {
return existingHolder;
}
if (structureBiomeCache.size() > NOISE_BIOME_CACHE_MAX) {
structureBiomeCache.clear();
}
Holder<Biome> resolvedHolder = resolveStructureBiomeHolder(x, y, z);
if (!cacheable) {
return resolvedHolder;
}
Holder<Biome> existingHolder = structureBiomeCache.putIfAbsent(cacheKey, resolvedHolder);
if (existingHolder != null) {
return existingHolder;
}
if (structureBiomeCache.size() > NOISE_BIOME_CACHE_MAX) {
structureBiomeCache.clear();
}
return resolvedHolder;
}
@Override
@@ -346,6 +461,62 @@ public class CustomBiomeSource extends BiomeSource {
}
}
@Override
public Pair<BlockPos, Holder<Biome>> findClosestBiome3d(
BlockPos origin,
int searchRadius,
int sampleResolutionHorizontal,
int sampleResolutionVertical,
Predicate<Holder<Biome>> allowed,
Climate.Sampler sampler,
LevelReader level
) {
GenerationSessionLease lease = tryAcquireGenerationLease("bukkit_locate_visible_biome");
if (lease == null) {
throw new IllegalStateException("Iris visible biome search was rejected during an engine transition");
}
try (lease; IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
prepareVisibleBiomeBatch();
DimensionStackContext stackContext = engine.getDimensionStackContext();
if (stackContext == null) {
return super.findClosestBiome3d(
origin, searchRadius, sampleResolutionHorizontal, sampleResolutionVertical,
allowed, sampler, level);
}
Set<Holder<Biome>> candidates = new HashSet<>();
for (Holder<Biome> biome : getVisibleBiomes(
biomeCustomRegistry, biomeRegistry, engine, fallbackBiome)) {
if (allowed.test(biome)) {
candidates.add(biome);
}
}
if (candidates.isEmpty()) {
return null;
}
int sampleRadius = Math.floorDiv(searchRadius, sampleResolutionHorizontal);
int[] sampleYs = Mth.outFromOrigin(
origin.getY(), level.getMinY() + 1, level.getMaxY() + 1, sampleResolutionVertical).toArray();
for (BlockPos.MutableBlockPos sampleColumn : BlockPos.spiralAround(
BlockPos.ZERO, sampleRadius, Direction.EAST, Direction.SOUTH)) {
int blockX = origin.getX() + sampleColumn.getX() * sampleResolutionHorizontal;
int blockZ = origin.getZ() + sampleColumn.getZ() * sampleResolutionHorizontal;
int quartX = QuartPos.fromBlock(blockX);
int quartZ = QuartPos.fromBlock(blockZ);
DimensionStackLayout stackLayout = stackContext.sample(
QuartPos.toBlock(quartX), QuartPos.toBlock(quartZ));
for (int blockY : sampleYs) {
int quartY = QuartPos.fromBlock(blockY);
Holder<Biome> visibleBiome = resolveVisibleBiomeHolder(
quartX, quartY, quartZ, null, stackLayout);
if (candidates.contains(visibleBiome)) {
return Pair.of(new BlockPos(blockX, blockY, blockZ), visibleBiome);
}
}
}
return null;
}
}
static int horizontalBiomeSearchQuartStep(int blockY, int searchRadius) {
return blockY == STRONGHOLD_RING_SEARCH_Y && searchRadius == STRONGHOLD_RING_SEARCH_RADIUS
? STRONGHOLD_RING_SEARCH_QUART_STEP
@@ -468,7 +639,7 @@ public class CustomBiomeSource extends BiomeSource {
private Holder<Biome> resolveSurfaceStructureBiomeHolder(int x, int z) {
int blockX = x << 2;
int blockZ = z << 2;
IrisBiome irisBiome = engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
IrisBiome irisBiome = resolveSurfaceStructureBiome(engine, blockX, blockZ);
if (irisBiome == null) {
throw new IllegalStateException("Iris returned no surface structure biome at block "
+ blockX + "," + blockZ);
@@ -532,14 +703,32 @@ public class CustomBiomeSource extends BiomeSource {
int z,
Climate.Sampler sampler,
IrisDimensionCarvingResolver.State resolverState
) {
boolean cacheable = isBiomeCacheable(x, z);
return getVisibleNoiseBiomeWithActiveGenerationLease(
x, y, z, sampler, resolverState, cacheable);
}
private Holder<Biome> getVisibleNoiseBiomeWithActiveGenerationLease(
int x,
int y,
int z,
Climate.Sampler sampler,
IrisDimensionCarvingResolver.State resolverState,
boolean cacheable
) {
long cacheKey = packNoiseKey(x, y, z);
Holder<Biome> cachedHolder = noiseBiomeCache.get(cacheKey);
if (cachedHolder != null) {
return cachedHolder;
if (cacheable) {
Holder<Biome> cachedHolder = noiseBiomeCache.get(cacheKey);
if (cachedHolder != null) {
return cachedHolder;
}
}
Holder<Biome> resolvedHolder = resolveVisibleBiomeHolder(x, y, z, resolverState);
if (!cacheable) {
return resolvedHolder;
}
Holder<Biome> existingHolder = noiseBiomeCache.putIfAbsent(cacheKey, resolvedHolder);
if (existingHolder != null) {
return existingHolder;
@@ -552,6 +741,18 @@ public class CustomBiomeSource extends BiomeSource {
return resolvedHolder;
}
private boolean isBiomeCacheable(int quartX, int quartZ) {
return isBiomeCacheable(
engine,
QuartPos.toBlock(quartX),
QuartPos.toBlock(quartZ)
);
}
static boolean isBiomeCacheable(Engine engine, int blockX, int blockZ) {
return !engine.answersFromNaturalTerrain(blockX, blockZ);
}
private GenerationSessionLease tryAcquireGenerationLease(String operation) {
if (engine.isClosed()) {
return null;
@@ -597,7 +798,7 @@ public class CustomBiomeSource extends BiomeSource {
}
private Holder<Biome> resolveStructureBiomeHolder(int x, int y, int z) {
BiomeResolution resolution = resolveBiomeResolution(x, y, z);
BiomeResolution resolution = resolveStructureBiomeResolution(x, y, z);
if (resolution == null) {
throw new IllegalStateException("Iris returned no structure biome at quart "
+ x + "," + y + "," + z);
@@ -619,11 +820,26 @@ public class CustomBiomeSource extends BiomeSource {
int z,
IrisDimensionCarvingResolver.State resolverState
) {
BiomeResolution resolution = resolveBiomeResolution(x, y, z, resolverState);
return resolveVisibleBiomeHolder(x, y, z, resolverState, null);
}
private Holder<Biome> resolveVisibleBiomeHolder(
int x,
int y,
int z,
IrisDimensionCarvingResolver.State resolverState,
DimensionStackLayout stackLayout
) {
BiomeResolution resolution = resolveBiomeResolution(
x, y, z, resolverState, true, stackLayout);
if (resolution == null) {
return getFallbackBiome();
}
return resolveVisibleBiomeHolder(resolution);
}
private Holder<Biome> resolveVisibleBiomeHolder(BiomeResolution resolution) {
if (resolution.irisBiome.isCustom()) {
return resolveCustomHolder(resolution);
}
@@ -642,7 +858,8 @@ public class CustomBiomeSource extends BiomeSource {
private Holder<Biome> resolveCustomHolder(BiomeResolution resolution) {
IrisBiomeCustom customBiome = resolution.irisBiome.getCustomBiome(resolution.rng, engine, resolution.blockX, resolution.blockY, resolution.blockZ);
if (customBiome != null) {
Holder<Biome> holder = customBiomes.get(customBiome.getId());
Holder<Biome> holder = customBiomes.get(customBiomeKey(
resolution.dimension, customBiome.getId()));
if (holder != null) {
return holder;
}
@@ -651,8 +868,8 @@ public class CustomBiomeSource extends BiomeSource {
return getFallbackBiome();
}
private BiomeResolution resolveBiomeResolution(int x, int y, int z) {
return resolveBiomeResolution(x, y, z, null);
private BiomeResolution resolveStructureBiomeResolution(int x, int y, int z) {
return resolveBiomeResolution(x, y, z, null, false);
}
private BiomeResolution resolveBiomeResolution(
@@ -660,6 +877,28 @@ public class CustomBiomeSource extends BiomeSource {
int y,
int z,
IrisDimensionCarvingResolver.State resolverState
) {
return resolveBiomeResolution(x, y, z, resolverState, true);
}
private BiomeResolution resolveBiomeResolution(
int x,
int y,
int z,
IrisDimensionCarvingResolver.State resolverState,
boolean includeDimensionStack
) {
return resolveBiomeResolution(
x, y, z, resolverState, includeDimensionStack, null);
}
private BiomeResolution resolveBiomeResolution(
int x,
int y,
int z,
IrisDimensionCarvingResolver.State resolverState,
boolean includeDimensionStack,
DimensionStackLayout stackLayout
) {
if (engine == null || engine.isClosed()) {
return null;
@@ -675,31 +914,63 @@ public class CustomBiomeSource extends BiomeSource {
int worldMinHeight = engine.getWorld().minHeight();
int internalY = blockY - worldMinHeight;
int caveSwitchInternalY = Math.max(-8 - worldMinHeight, 40);
DimensionStackLayout.Layer stackLayer = includeDimensionStack
? stackLayout == null
? resolveDimensionStackLayer(engine, blockX, internalY, blockZ)
: stackLayout.layerAt(internalY)
: null;
IrisDimension owningDimension = stackLayer == null
? engine.getDimension()
: stackLayer.terrainContext().getDimension();
if (stackLayer != null && !stackLayer.terrainContext().isSelfReferencing()) {
IrisBiome stackedBiome = stackLayer.biome();
return stackedBiome == null
? null
: createBiomeResolution(
stackedBiome, false, owningDimension, blockX, blockY, blockZ);
}
boolean deepUnderground = internalY <= caveSwitchInternalY;
boolean underground = false;
IrisBiome irisBiome;
if (deepUnderground) {
int surfaceInternalY = engine.getComplex().getHeightStream().get(blockX, blockZ).intValue();
if (stackLayer != null) {
int surfaceInternalY = stackLayer.surfaceY();
underground = internalY <= surfaceInternalY - 8;
irisBiome = underground
? engine.getCaveBiome(
blockX,
internalY,
blockZ,
resolverState,
stackLayer.biome(),
surfaceInternalY
)
: stackLayer.biome();
} else if (deepUnderground) {
int surfaceInternalY = Engine.hostHeight(engine, blockX, blockZ, true);
underground = internalY <= surfaceInternalY - 8;
irisBiome = underground
? engine.getCaveBiome(blockX, internalY, blockZ, resolverState)
: engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
: engine.getHostSurfaceBiome(blockX, blockZ);
} else {
irisBiome = engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
irisBiome = engine.getHostSurfaceBiome(blockX, blockZ);
}
if (irisBiome == null && underground) {
irisBiome = engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
irisBiome = stackLayer == null
? engine.getHostSurfaceBiome(blockX, blockZ)
: stackLayer.biome();
}
if (irisBiome == null) {
return null;
}
return createBiomeResolution(irisBiome, underground, blockX, blockY, blockZ);
return createBiomeResolution(
irisBiome, underground, owningDimension, blockX, blockY, blockZ);
}
private BiomeResolution createBiomeResolution(
IrisBiome irisBiome,
boolean underground,
IrisDimension dimension,
int blockX,
int blockY,
int blockZ
@@ -708,7 +979,8 @@ public class CustomBiomeSource extends BiomeSource {
^ (((long) blockX) * 341873128712L)
^ (((long) blockY) * 132897987541L)
^ (((long) blockZ) * 42317861L));
return new BiomeResolution(irisBiome, underground, blockX, blockY, blockZ, noiseRng);
return new BiomeResolution(
irisBiome, underground, dimension, blockX, blockY, blockZ, noiseRng);
}
private Holder<Biome> getFallbackBiome() {
@@ -734,15 +1006,20 @@ public class CustomBiomeSource extends BiomeSource {
return ((long) x << 32) ^ ((long) z & 4294967295L);
}
private static Holder<Biome> resolveCustomBiomeHolder(Registry<Biome> customRegistry, Engine engine, String customBiomeId) {
if (customRegistry == null || engine == null || customBiomeId == null || customBiomeId.isBlank()) {
private static Holder<Biome> resolveCustomBiomeHolder(
Registry<Biome> customRegistry,
IrisDimension dimension,
String customBiomeId
) {
if (customRegistry == null || dimension == null
|| customBiomeId == null || customBiomeId.isBlank()) {
return null;
}
Identifier resourceLocation = Identifier.fromNamespaceAndPath(
engine.getDimension().getLoadKey().toLowerCase(java.util.Locale.ROOT),
customBiomeId.toLowerCase(java.util.Locale.ROOT)
);
Identifier resourceLocation = Identifier.tryParse(customBiomeKey(dimension, customBiomeId));
if (resourceLocation == null) {
return null;
}
Biome biome = customRegistry.getValue(resourceLocation);
if (biome == null) {
return null;
@@ -761,6 +1038,63 @@ public class CustomBiomeSource extends BiomeSource {
return optionalReferenceHolder.get();
}
static String customBiomeKey(IrisDimension dimension, String customBiomeId) {
return dimension.getCustomBiomeKey(customBiomeId);
}
private static List<OwnedBiome> getOwnedBiomes(Engine engine) {
ArrayList<OwnedBiome> biomes = new ArrayList<>(getHostOwnedBiomes(engine));
DimensionStackContext stackContext = engine.getDimensionStackContext();
if (stackContext == null) {
return biomes;
}
for (DimensionTerrainContext terrainContext : stackContext.getLayersBottomToTop()) {
if (terrainContext.isSelfReferencing()) {
continue;
}
IrisDimension dimension = terrainContext.getDimension();
addOwnedBiomes(
biomes,
dimension,
dimension.getReachableBiomes(terrainContext)
);
}
return biomes;
}
private static List<OwnedBiome> getHostOwnedBiomes(Engine engine) {
IrisDimension hostDimension = engine.getDimension();
ArrayList<OwnedBiome> biomes = new ArrayList<>();
addOwnedBiomes(biomes, hostDimension, hostDimension.getReachableBiomes(engine));
return biomes;
}
private static void addOwnedBiomes(
List<OwnedBiome> target,
IrisDimension dimension,
Iterable<IrisBiome> biomes
) {
for (IrisBiome biome : biomes) {
if (biome != null) {
target.add(new OwnedBiome(dimension, biome));
}
}
}
private static DimensionStackLayout.Layer resolveDimensionStackLayer(
Engine engine,
int blockX,
int internalY,
int blockZ
) {
DimensionStackContext stackContext = engine.getDimensionStackContext();
return stackContext == null ? null : stackContext.getLayerAt(blockX, internalY, blockZ);
}
private static IrisBiome resolveSurfaceStructureBiome(Engine engine, int blockX, int blockZ) {
return engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
}
private static Holder<Biome> resolveBiomeHolder(Registry<Biome> registry, String biomeKey) {
if (registry == null || biomeKey == null || biomeKey.isBlank()) {
return null;
@@ -809,18 +1143,31 @@ public class CustomBiomeSource extends BiomeSource {
private static final class BiomeResolution {
private final IrisBiome irisBiome;
private final boolean underground;
private final IrisDimension dimension;
private final int blockX;
private final int blockY;
private final int blockZ;
private final RNG rng;
private BiomeResolution(IrisBiome irisBiome, boolean underground, int blockX, int blockY, int blockZ, RNG rng) {
private BiomeResolution(
IrisBiome irisBiome,
boolean underground,
IrisDimension dimension,
int blockX,
int blockY,
int blockZ,
RNG rng
) {
this.irisBiome = irisBiome;
this.underground = underground;
this.dimension = dimension;
this.blockX = blockX;
this.blockY = blockY;
this.blockZ = blockZ;
this.rng = rng;
}
}
private record OwnedBiome(IrisDimension dimension, IrisBiome biome) {
}
}
@@ -1,5 +1,7 @@
package art.arcane.iris.core.nms.v26_2_R1;
import art.arcane.iris.engine.DimensionStackContext;
import art.arcane.iris.engine.DimensionStackLayout;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.NativeFeatureGenerationPolicy;
import art.arcane.iris.engine.object.IrisBiome;
@@ -40,6 +42,9 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.IntBinaryOperator;
import java.util.function.Predicate;
/**
* Bukkit twin of the modded imported-feature stage. Same control, same semantics, same seeds: with
@@ -198,8 +203,7 @@ final class ImportedFeatureStage {
*/
private Set<String> visibleBiomeKeys() {
Set<String> keys = new LinkedHashSet<>();
String namespace = engine.getDimension().getLoadKey().toLowerCase(Locale.ROOT);
for (IrisBiome irisBiome : engine.getAllBiomes()) {
for (IrisBiome irisBiome : engine.getDimension().getReachableBiomes(engine)) {
addKey(keys, irisBiome.getStructureDerivativeKey());
addKey(keys, irisBiome.getDerivativeKey());
addKey(keys, irisBiome.getVanillaDerivativeKey());
@@ -213,7 +217,7 @@ final class ImportedFeatureStage {
continue;
}
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
addKey(keys, namespace + ":" + customBiome.getId());
addKey(keys, engine.getDimension().getCustomBiomeKey(customBiome.getId()));
}
}
return keys;
@@ -226,8 +230,7 @@ final class ImportedFeatureStage {
private Map<String, Holder<Biome>> customBiomeDerivatives(Registry<Biome> registry,
Map<String, Holder<Biome>> byKey) {
Map<String, Holder<Biome>> derivatives = new HashMap<>();
String namespace = engine.getDimension().getLoadKey().toLowerCase(Locale.ROOT);
for (IrisBiome irisBiome : engine.getAllBiomes()) {
for (IrisBiome irisBiome : engine.getDimension().getReachableBiomes(engine)) {
if (!irisBiome.isCustom()) {
continue;
}
@@ -246,7 +249,7 @@ final class ImportedFeatureStage {
continue;
}
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
derivatives.put(namespace + ":" + customBiome.getId().toLowerCase(Locale.ROOT), derivative);
derivatives.put(engine.getDimension().getCustomBiomeKey(customBiome.getId()), derivative);
}
}
return derivatives;
@@ -288,14 +291,24 @@ final class ImportedFeatureStage {
List<FeatureSorter.StepFeatureData> steps = table.steps();
WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed()));
long decorationSeed = random.setDecorationSeed(level.getSeed(), origin.getX(), origin.getZ());
Set<Holder<Biome>> chunkBiomes = chunkBiomes(level, sectionPos, table);
IrisStaticObjectLayer staticObjects = engine.getDimension().getStaticObjectLayer(engine.getData());
int staticMinY = engine.getMinHeight();
WorldGenLevel placementLevel = staticObjects.isEmpty() ? level : NativeStructureWorldgenAccess.create(
DimensionStackContext stackContext = engine.getDimensionStackContext();
Set<Holder<Biome>> chunkBiomes = chunkBiomes(level, sectionPos, table, stackContext);
IntBinaryOperator surfaceFirstFreeY = stackContext == null
? (x, z) -> level.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z)
: (x, z) -> Engine.hostHeight(engine, x, z, false) + staticMinY + 1;
IntBinaryOperator floorFirstFreeY = stackContext == null
? (x, z) -> level.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, x, z)
: (x, z) -> Engine.hostHeight(engine, x, z, true) + staticMinY + 1;
WorldGenLevel placementLevel = staticObjects.isEmpty() && stackContext == null
? level
: NativeStructureWorldgenAccess.create(
level, centerPos,
(x, z) -> level.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z),
(x, z) -> level.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, x, z),
position -> staticObjects.contains(position.getX(), position.getY() - staticMinY, position.getZ()));
surfaceFirstFreeY,
floorFirstFreeY,
stackContext != null,
importedFeatureProtection(staticObjects, stackContext, staticMinY));
try {
for (int stepIndex = 0; stepIndex < steps.size(); stepIndex++) {
@@ -313,6 +326,30 @@ final class ImportedFeatureStage {
}
}
private static Predicate<BlockPos> importedFeatureProtection(
IrisStaticObjectLayer staticObjects,
DimensionStackContext stackContext,
int minimumY
) {
Map<Long, DimensionStackLayout> layouts = new ConcurrentHashMap<>();
return position -> {
if (!staticObjects.isEmpty() && staticObjects.contains(
position.getX(), position.getY() - minimumY, position.getZ())) {
return true;
}
if (stackContext == null) {
return false;
}
long columnKey = ((long) position.getX() << 32)
^ (position.getZ() & 0xFFFFFFFFL);
DimensionStackLayout layout = layouts.computeIfAbsent(
columnKey,
ignored -> stackContext.sample(position.getX(), position.getZ())
);
return layout.isHostFeatureProtectedY(position.getY() - minimumY);
};
}
private void placeStep(WorldGenLevel level, FeatureTable table, FeatureSorter.StepFeatureData stepData,
Registry<PlacedFeature> featureRegistry, Set<Holder<Biome>> chunkBiomes,
ChunkGenerator owner, WorldgenRandom random, long decorationSeed,
@@ -353,12 +390,37 @@ final class ImportedFeatureStage {
return id == null ? feature.toString() : id.toString();
}
private Set<Holder<Biome>> chunkBiomes(WorldGenLevel level, SectionPos sectionPos, FeatureTable table) {
private Set<Holder<Biome>> chunkBiomes(
WorldGenLevel level,
SectionPos sectionPos,
FeatureTable table,
DimensionStackContext stackContext
) {
List<Holder<Biome>> collected = new ArrayList<>();
ChunkPos.rangeClosed(sectionPos.chunk(), 1).forEach((ChunkPos chunkPos) -> {
ChunkAccess neighbour = level.getChunk(chunkPos.x(), chunkPos.z());
for (LevelChunkSection section : neighbour.getSections()) {
section.getBiomes().getAll(collected::add);
LevelChunkSection[] sections = neighbour.getSections();
for (int sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) {
LevelChunkSection section = sections[sectionIndex];
if (stackContext == null) {
section.getBiomes().getAll(collected::add);
continue;
}
int sectionMinimumY = neighbour.getSectionYFromSectionIndex(sectionIndex) << 4;
for (int quartY = 0; quartY < 4; quartY++) {
int internalY = sectionMinimumY + (quartY << 2) - level.getMinY();
for (int quartX = 0; quartX < 4; quartX++) {
int blockX = chunkPos.getMinBlockX() + (quartX << 2);
for (int quartZ = 0; quartZ < 4; quartZ++) {
int blockZ = chunkPos.getMinBlockZ() + (quartZ << 2);
DimensionStackLayout.Layer layer = stackContext.getLayerAt(
blockX, internalY, blockZ);
if (layer.terrainContext().isSelfReferencing()) {
collected.add(section.getBiomes().get(quartX, quartY, quartZ));
}
}
}
}
}
});
Set<Holder<Biome>> present = new LinkedHashSet<>();
@@ -1,5 +1,7 @@
package art.arcane.iris.core.nms.v26_2_R1;
import art.arcane.iris.engine.DimensionStackContext;
import art.arcane.iris.engine.DimensionStackLayout;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.GenerationSessionException;
import art.arcane.iris.engine.framework.GenerationSessionLease;
@@ -115,6 +117,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CompletionException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.IntBinaryOperator;
import java.util.function.Predicate;
public class IrisChunkGenerator extends CustomChunkGenerator {
private static final WrappedField<ChunkGenerator, BiomeSource> BIOME_SOURCE;
@@ -429,7 +432,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
undergroundStep,
decision.preserveSourceY(),
decision.yBand(),
(x, z) -> engine.getHeight(x, z, true) + engine.getMinHeight());
(x, z) -> Engine.hostHeight(engine, x, z, true) + engine.getMinHeight());
StructureStart wrapped = NativeStructureReferenceEnvelope.wrapForPublication(
start, structure, start.getReferences(),
NativeStructureTerrainIntegrator.resolveNativeTerrain(start, decision.terrain()),
@@ -726,6 +729,14 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
return (x, z) -> engine.getHeight(x, z, true) + runtimeMinY + 1;
}
private IntBinaryOperator hostWorldgenSurfaceHeight() {
return (x, z) -> Engine.hostHeight(engine, x, z, false) + runtimeMinY + 1;
}
private IntBinaryOperator hostWorldgenFloorHeight() {
return (x, z) -> Engine.hostHeight(engine, x, z, true) + runtimeMinY + 1;
}
@Override
public WeightedList<MobSpawnSettings.SpawnerData> getMobsAt(Holder<Biome> holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) {
Holder<Biome> vanillaSpawnBiome = customBiomeSource.getVanillaSpawnBiome(holder);
@@ -868,17 +879,22 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
try {
WorldgenTerrainHeightmaps.primeStructurePlacement(
world, chunkPos, heightmapStarts, worldgenSurfaceHeight(), worldgenFloorHeight());
world, chunkPos, heightmapStarts,
hostWorldgenSurfaceHeight(), hostWorldgenFloorHeight());
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
"heightmap priming", nativeStructureBatchContext(placementGroups),
chunkPos.x(), chunkPos.z(), error);
}
IrisStaticObjectLayer staticObjects = engine.getDimension().getStaticObjectLayer(engine.getData());
int staticMinY = engine.getMinHeight();
WorldGenLevel boundedWorld = staticObjects.isEmpty() ? world : NativeStructureWorldgenAccess.create(
world, chunkPos, worldgenSurfaceHeight(), worldgenFloorHeight(),
position -> staticObjects.contains(position.getX(), position.getY() - staticMinY, position.getZ()));
Predicate<BlockPos> protectedPosition = nativeStructureProtection(staticObjects);
WorldGenLevel boundedWorld = staticObjects.isEmpty()
&& engine.getDimensionStackContext() == null
? world
: NativeStructureWorldgenAccess.create(
world, chunkPos, hostWorldgenSurfaceHeight(), hostWorldgenFloorHeight(),
engine.getDimensionStackContext() != null,
protectedPosition);
try {
NativeStructureVegetationClearer.clearIntersectingVegetation(
boundedWorld, chunk, area, vegetationTargets);
@@ -891,7 +907,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
try {
vacuumFoundationPlan = NativeStructureSurfaceFitter.prepareSurfaceStructures(
boundedWorld, area, terrainTargets,
(x, z) -> engine.getHeight(x, z, true) + engine.getMinHeight());
(x, z) -> Engine.hostHeight(engine, x, z, true) + engine.getMinHeight());
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
"terrain integration", nativeStructureBatchContext(placementGroups),
@@ -945,17 +961,45 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
BoundingBox area, ChunkPos chunkPos, String structureId, StructureStart start,
IrisNativeStructureDecision decision) {
WorldGenLevel boundedWorld = world instanceof NativeStructureWorldgenAccess ? world : NativeStructureWorldgenAccess.create(
world, chunkPos, worldgenSurfaceHeight(), worldgenFloorHeight(), position -> false);
world,
chunkPos,
hostWorldgenSurfaceHeight(),
hostWorldgenFloorHeight(),
engine.getDimensionStackContext() != null,
nativeStructureProtection(
engine.getDimension().getStaticObjectLayer(engine.getData())));
world.setCurrentlyGenerating(() -> "Iris native structure " + structureId);
try {
NativeStructurePostProcessor.place(boundedWorld, structureManager, this, random, area, chunkPos,
structureId, start, decision, this::resolvePaletteBlock,
(x, z) -> engine.getHeight(x, z, true) + engine.getMinHeight());
(x, z) -> Engine.hostHeight(engine, x, z, true) + engine.getMinHeight());
} finally {
world.setCurrentlyGenerating(null);
}
}
private Predicate<BlockPos> nativeStructureProtection(IrisStaticObjectLayer staticObjects) {
DimensionStackContext stackContext = engine.getDimensionStackContext();
int minimumY = engine.getMinHeight();
Map<Long, DimensionStackLayout> layouts = new ConcurrentHashMap<>();
return position -> {
if (!staticObjects.isEmpty() && staticObjects.contains(
position.getX(), position.getY() - minimumY, position.getZ())) {
return true;
}
if (stackContext == null) {
return false;
}
long columnKey = ((long) position.getX() << 32)
^ (position.getZ() & 0xFFFFFFFFL);
DimensionStackLayout layout = layouts.computeIfAbsent(
columnKey,
ignored -> stackContext.sample(position.getX(), position.getZ())
);
return layout.isHostFeatureProtectedY(position.getY() - minimumY);
};
}
private List<List<Structure>> structuresByStep(Registry<Structure> registry) {
StructureStepCache cached = structureStepCache;
if (cached != null && cached.registry() == registry) {
@@ -1041,7 +1085,17 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public void spawnOriginalMobs(WorldGenRegion region) {
ChunkPos center = region.getCenter();
Holder<Biome> visibleBiome = region.getBiome(center.getWorldPosition().atY(region.getMaxY()));
Holder<Biome> visibleBiome;
if (engine.getDimensionStackContext() == null) {
visibleBiome = region.getBiome(center.getWorldPosition().atY(region.getMaxY()));
} else {
visibleBiome = customBiomeSource.getVisibleSurfaceBiome(
center.getMinBlockX() + 8,
center.getMinBlockZ() + 8);
if (visibleBiome == null) {
visibleBiome = region.getBiome(center.getWorldPosition().atY(region.getMaxY()));
}
}
Holder<Biome> vanillaBiome = customBiomeSource.getVanillaSpawnBiome(visibleBiome);
WorldgenRandom random = new WorldgenRandom(new LegacyRandomSource(RandomSupport.generateUniqueSeed()));
random.setDecorationSeed(region.getSeed(), center.getMinBlockX(), center.getMinBlockZ());
@@ -1081,9 +1135,12 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
try (GenerationSessionLease lease = engine.acquireGenerationLease("bukkit_nms_base_height");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
boolean ignoreFluid = !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState());
int height = engine.getDimensionStackContext() == null
? engine.getHeight(i, j, ignoreFluid)
: Engine.hostHeight(engine, i, j, ignoreFluid);
return levelheightaccessor.getMinY()
+ engine.getHeight(i, j, !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState()))
+ 1;
+ height + 1;
} catch (GenerationSessionException e) {
throw new IllegalStateException("Iris base height query could not acquire its engine runtime.", e);
}
@@ -1093,8 +1150,13 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
try (GenerationSessionLease lease = engine.acquireGenerationLease("bukkit_nms_base_column");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
int block = engine.getHeight(i, j, true);
int water = engine.getHeight(i, j, false);
boolean dimensionStack = engine.getDimensionStackContext() != null;
int block = dimensionStack
? Engine.hostHeight(engine, i, j, true)
: engine.getHeight(i, j, true);
int water = dimensionStack
? Engine.hostHeight(engine, i, j, false)
: engine.getHeight(i, j, false);
BlockState[] column = new BlockState[levelheightaccessor.getHeight()];
for (int k = 0; k < column.length; k++) {
if (k <= block) {
@@ -824,6 +824,11 @@ public class NMSBinding implements INMSBinding {
return NativeStructureVolumeIndex.volumes(engine, minX, minZ, maxX, maxZ);
}
@Override
public void invalidateNativeStructureVolumeIndex(Engine engine) {
NativeStructureVolumeIndex.invalidate(engine);
}
@Override
public int getBiomeId(Biome biome) {
for (World i : Bukkit.getWorlds()) {
@@ -64,6 +64,7 @@ final class NativeStructureWorldgenAccess implements WorldGenLevel {
private final ChunkPos generationCenter;
private final IntBinaryOperator surfaceFirstFreeY;
private final IntBinaryOperator floorFirstFreeY;
private final boolean virtualizeHeights;
private final Predicate<BlockPos> protectedPosition;
private final Holder<Biome> fallbackBiome;
private final BiomeManager biomeManager;
@@ -81,6 +82,7 @@ final class NativeStructureWorldgenAccess implements WorldGenLevel {
boundary.surfaceFirstFreeY(), "Native structure world access requires a surface resolver");
this.floorFirstFreeY = Objects.requireNonNull(
boundary.floorFirstFreeY(), "Native structure world access requires an ocean-floor resolver");
this.virtualizeHeights = boundary.virtualizeHeights();
this.protectedPosition = Objects.requireNonNull(
boundary.protectedPosition(), "World access requires a protected-position predicate");
BlockPos biomeSample = generationCenter.getMiddleBlockPosition(delegate.getSeaLevel());
@@ -103,9 +105,10 @@ final class NativeStructureWorldgenAccess implements WorldGenLevel {
static NativeStructureWorldgenAccess create(WorldGenLevel delegate, ChunkPos generationCenter,
IntBinaryOperator surfaceFirstFreeY,
IntBinaryOperator floorFirstFreeY,
boolean virtualizeHeights,
Predicate<BlockPos> protectedPosition) {
return new NativeStructureWorldgenAccess(delegate, new Boundary(
generationCenter, surfaceFirstFreeY, floorFirstFreeY, protectedPosition));
generationCenter, surfaceFirstFreeY, floorFirstFreeY, virtualizeHeights, protectedPosition));
}
@Override
@@ -250,7 +253,7 @@ final class NativeStructureWorldgenAccess implements WorldGenLevel {
@Override
public int getHeight(Heightmap.Types type, int x, int z) {
if (isInsideGenerationRegion(x >> 4, z >> 4)) {
if (!virtualizeHeights && isInsideGenerationRegion(x >> 4, z >> 4)) {
return delegate.getHeight(type, x, z);
}
return height(type, x, z);
@@ -549,6 +552,7 @@ final class NativeStructureWorldgenAccess implements WorldGenLevel {
private record Boundary(ChunkPos generationCenter,
IntBinaryOperator surfaceFirstFreeY,
IntBinaryOperator floorFirstFreeY,
boolean virtualizeHeights,
Predicate<BlockPos> protectedPosition) {
}
@@ -1,15 +1,29 @@
package art.arcane.iris.core.nms.v26_2_R1;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.IrisDimension;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
public class CustomBiomeSourceStructureContractTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void nativeStructuresUseTerrainSafeDerivativeAtEveryBiomeBoundary() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.customBiomeSource")));
@@ -69,14 +83,53 @@ public class CustomBiomeSourceStructureContractTest {
String realResolution = source.substring(realResolutionStart, naturalResolutionStart);
String naturalResolution = source.substring(naturalResolutionStart, resolutionEnd);
assertTrue(realResolution.contains("engine.getComplex().getTrueBiomeStream().get(blockX, blockZ)"));
assertTrue(realResolution.contains("resolveSurfaceStructureBiome(engine, blockX, blockZ)"));
assertFalse(realResolution.contains("getNaturalTrueBiomeStream()"));
assertTrue(naturalResolution.contains(
"engine.getComplex().getNaturalTrueBiomeStream().get(blockX, blockZ)"));
assertFalse(naturalResolution.contains("getTrueBiomeStream()"));
int helperStart = source.indexOf("private static IrisBiome resolveSurfaceStructureBiome(");
int helperEnd = source.indexOf("private static Holder<Biome> resolveBiomeHolder(", helperStart);
String helper = source.substring(helperStart, helperEnd);
assertTrue(helper.contains("engine.getComplex().getTrueBiomeStream().get(blockX, blockZ)"));
assertFalse(helper.contains("getDimensionStackContext()"));
assertFalse(helper.contains("getSurfaceBiome("));
assertFalse(source.contains("studioBootstrapSurfaceStructureBiomeCache"));
}
@Test
public void stackedCustomBiomesUseTheirOwningDimensionNamespace() {
IrisDimension host = new IrisDimension();
host.setLoadKey("Host");
IrisDimension upper = new IrisDimension();
upper.setLoadKey("Layers/Upper");
assertEquals("layers:upper/aurora", CustomBiomeSource.customBiomeKey(upper, "Aurora"));
assertNotEquals(
CustomBiomeSource.customBiomeKey(host, "Aurora"),
CustomBiomeSource.customBiomeKey(upper, "Aurora")
);
}
@Test
public void visibleBiomeResolutionSelectsStackOwnershipBeforeHostCaves() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.customBiomeSource")));
int resolutionStart = source.indexOf("private BiomeResolution resolveBiomeResolution(");
int resolutionEnd = source.indexOf("private BiomeResolution createBiomeResolution(", resolutionStart);
assertTrue(resolutionStart >= 0);
assertTrue(resolutionEnd > resolutionStart);
String resolution = source.substring(resolutionStart, resolutionEnd);
assertTrue(resolution.contains("resolveDimensionStackLayer("));
assertTrue(resolution.contains("!stackLayer.terrainContext().isSelfReferencing()"));
assertTrue(resolution.indexOf("resolveDimensionStackLayer(")
< resolution.indexOf("boolean deepUnderground"));
assertTrue(resolution.contains("includeDimensionStack"));
assertTrue(source.contains("resolveBiomeResolution(x, y, z, null, false)"));
assertTrue(source.contains("resolution.dimension, customBiome.getId()"));
}
@Test
public void visibleBiomeBatchReusesTheCallersGenerationLease() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.customBiomeSource")));
@@ -94,4 +147,126 @@ public class CustomBiomeSourceStructureContractTest {
< lookup.indexOf("getVisibleNoiseBiomeWithActiveGenerationLease(x, y, z, sampler)"));
assertFalse(source.contains("studioBootstrapNoiseBiomeCache"));
}
@Test
public void stackBiomesAreVisibleButExcludedFromStructureReachability() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.customBiomeSource")));
assertTrue(source.contains("return possibleBiomes(true).stream()"));
assertTrue(source.contains("public Set<Holder<Biome>> possibleBiomes()"));
assertTrue(source.contains("irisBiome.getDerivativeKey()"));
assertTrue(source.contains("irisBiome.getBiomeScatter()"));
assertTrue(source.contains("irisBiome.getBiomeSkyScatter()"));
assertTrue(source.contains("return possibleBiomes(false)"));
assertTrue(source.contains("for (OwnedBiome ownedBiome : getHostOwnedBiomes(engine))"));
assertTrue(source.contains("for (OwnedBiome ownedBiome : getOwnedBiomes(engine))"));
}
@Test
public void biomeLocatorSearchesTheVisibleStack() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.customBiomeSource")));
int start = source.indexOf("public Pair<BlockPos, Holder<Biome>> findClosestBiome3d(");
int end = source.indexOf("static int horizontalBiomeSearchQuartStep", start);
String method = source.substring(start, end);
assertTrue(method.contains("bukkit_locate_visible_biome"));
assertTrue(method.contains("return super.findClosestBiome3d("));
assertTrue(method.contains("getVisibleBiomes("));
assertTrue(method.contains("QuartPos.toBlock(quartX), QuartPos.toBlock(quartZ)"));
assertTrue(method.contains("resolveVisibleBiomeHolder("));
assertFalse(method.contains("getStructureNoiseBiomeWithActiveGenerationLease("));
assertTrue(method.indexOf("tryAcquireGenerationLease(\"bukkit_locate_visible_biome\")")
< method.indexOf("engine.getDimensionStackContext()"));
}
@Test
public void surfaceSpawnBiomeUsesTheVisibleQuartCell() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.customBiomeSource")));
int start = source.indexOf("Holder<Biome> getVisibleSurfaceBiome(");
int end = source.indexOf("private RegistryAccess registry()", start);
String method = source.substring(start, end);
assertTrue(method.contains("QuartPos.fromBlock(blockX)"));
assertTrue(method.contains("QuartPos.toBlock(quartX)"));
assertTrue(method.contains("QuartPos.fromBlock(internalY + engine.getWorld().minHeight())"));
assertTrue(method.contains("stackContext.getLayout(sampleX, sampleZ).surfaceLayer()"));
assertTrue(method.contains("getVisibleNoiseBiomeWithActiveGenerationLease("));
assertTrue(method.contains("quartX, quartY, quartZ, null, null, cacheable"));
assertTrue(method.indexOf("boolean cacheable = isBiomeCacheable(engine, sampleX, sampleZ)")
< method.indexOf("Engine.hostHeight(engine, sampleX, sampleZ, true)"));
}
@Test
public void temporaryNaturalBiomeAnswersAreNotMemoized() throws IOException {
Engine temporary = engineAnsweringNaturalFallback(true, 12, -8);
Engine stable = engineAnsweringNaturalFallback(false, 12, -8);
assertFalse(CustomBiomeSource.isBiomeCacheable(temporary, 12, -8));
assertTrue(CustomBiomeSource.isBiomeCacheable(stable, 12, -8));
String source = Files.readString(Path.of(System.getProperty("iris.customBiomeSource")));
int structureStart = source.indexOf(
"private Holder<Biome> getStructureNoiseBiomeWithActiveGenerationLease(");
int structureEnd = source.indexOf("@Override\n public Pair<BlockPos, Holder<Biome>> findBiomeHorizontal(",
structureStart);
assertCacheGuarded(source.substring(structureStart, structureEnd), "structureBiomeCache");
int firstVisible = source.indexOf(
"Holder<Biome> getVisibleNoiseBiomeWithActiveGenerationLease(");
int visibleStart = source.indexOf(
"Holder<Biome> getVisibleNoiseBiomeWithActiveGenerationLease(", firstVisible + 1);
int visibleEnd = source.indexOf("private boolean isBiomeCacheable(", visibleStart);
assertCacheGuarded(source.substring(visibleStart, visibleEnd), "noiseBiomeCache");
String predicate = source.substring(
visibleEnd,
source.indexOf("private GenerationSessionLease tryAcquireGenerationLease(", visibleEnd)
);
assertTrue(predicate.contains("QuartPos.toBlock(quartX)"));
assertTrue(predicate.contains("QuartPos.toBlock(quartZ)"));
assertTrue(predicate.contains("return !engine.answersFromNaturalTerrain(blockX, blockZ)"));
assertFalse(predicate.contains("getDimensionStackContext()"));
}
@Test
public void importedFeaturesCollectOnlyHostOwnedStackCells() throws IOException {
Path biomeSource = Path.of(System.getProperty("iris.customBiomeSource"));
String source = Files.readString(biomeSource.getParent().resolve("ImportedFeatureStage.java"));
assertTrue(source.contains("stackContext.getLayerAt("));
assertTrue(source.contains("layer.terrainContext().isSelfReferencing()"));
assertTrue(source.contains("section.getBiomes().get(quartX, quartY, quartZ)"));
}
private static void assertCacheGuarded(String method, String cacheName) {
int predicate = method.indexOf("boolean cacheable = isBiomeCacheable(");
int read = method.indexOf(cacheName + ".get(", predicate);
int readGuard = method.lastIndexOf("if (cacheable)", read);
int bypass = method.indexOf("if (!cacheable)", read);
int write = method.indexOf(cacheName + ".putIfAbsent(", bypass);
assertTrue(predicate >= 0);
assertTrue(read > predicate);
assertTrue(readGuard >= predicate);
assertTrue(bypass > read);
assertTrue(write > bypass);
}
private static Engine engineAnsweringNaturalFallback(
boolean naturalFallback,
int expectedX,
int expectedZ
) {
return (Engine) Proxy.newProxyInstance(
Engine.class.getClassLoader(),
new Class<?>[]{Engine.class},
(proxy, method, arguments) -> {
if (!method.getName().equals("answersFromNaturalTerrain")) {
throw new UnsupportedOperationException(method.getName());
}
assertEquals(Integer.valueOf(expectedX), arguments[0]);
assertEquals(Integer.valueOf(expectedZ), arguments[1]);
return naturalFallback;
}
);
}
}
@@ -78,6 +78,8 @@ public class IrisChunkGeneratorFailureContractTest {
< placement.indexOf("for (NativePlacementGroup group"));
assertTrue(placement.indexOf("repairVacuumFoundations")
> placement.indexOf("for (NativePlacementGroup group"));
assertTrue(placement.contains("nativeStructureProtection(staticObjects)"));
assertTrue(placement.contains("Engine.hostHeight(engine, x, z, true)"));
assertFalse(placement.contains("IrisLogging.reportError"));
}
@@ -542,8 +544,8 @@ public class IrisChunkGeneratorFailureContractTest {
String spawn = source.substring(spawnStart, spawnEnd);
assertTrue(spawn.contains("customBiomeSource.getVanillaSpawnBiome(visibleBiome)"));
assertTrue(spawn.contains("customBiomeSource.getVisibleSurfaceBiome("));
assertTrue(spawn.contains("NaturalSpawner.spawnMobsForChunkGeneration("));
assertTrue(spawn.contains("region.getBiome(center.getWorldPosition().atY(region.getMaxY()))"));
assertTrue(spawn.contains("new LegacyRandomSource(RandomSupport.generateUniqueSeed())"));
assertTrue(spawn.contains("random.setDecorationSeed(region.getSeed()"));
assertFalse(spawn.contains("delegate.spawnOriginalMobs"));
@@ -135,6 +135,24 @@ public class NativeStructureWorldgenAccessTest {
assertEquals(1, recording.blockTicks.count());
}
@Test
public void virtualizedHeightsHideInRegionTerrainFromTheDelegate() {
RecordingDelegate recording = new RecordingDelegate();
ChunkPos generationCenter = generationCenter();
NativeStructureWorldgenAccess access = NativeStructureWorldgenAccess.create(
recording.world(), generationCenter,
(x, z) -> SURFACE_FIRST_FREE_Y,
(x, z) -> FLOOR_FIRST_FREE_Y,
true,
position -> false);
int x = generationCenter.getMiddleBlockX();
int z = generationCenter.getMiddleBlockZ();
assertEquals(SURFACE_FIRST_FREE_Y, access.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z));
assertEquals(FLOOR_FIRST_FREE_Y, access.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, x, z));
assertEquals(0, recording.heightReads);
}
@Test
public void protectedCellsRemainReadableButRejectWritesTilesAndTicks() {
RecordingDelegate recording = new RecordingDelegate();
@@ -142,7 +160,9 @@ public class NativeStructureWorldgenAccessTest {
NativeStructureWorldgenAccess access = NativeStructureWorldgenAccess.create(
recording.world(), generationCenter(),
(x, z) -> SURFACE_FIRST_FREE_Y,
(x, z) -> FLOOR_FIRST_FREE_Y, position::equals);
(x, z) -> FLOOR_FIRST_FREE_Y,
false,
position::equals);
assertSame(Blocks.DIRT, access.getBlockState(position).getBlock());
assertFalse(access.ensureCanWrite(position));
@@ -211,7 +231,9 @@ public class NativeStructureWorldgenAccessTest {
return NativeStructureWorldgenAccess.create(
recording.world(), generationCenter(),
(x, z) -> SURFACE_FIRST_FREE_Y,
(x, z) -> FLOOR_FIRST_FREE_Y, position -> false);
(x, z) -> FLOOR_FIRST_FREE_Y,
false,
position -> false);
}
private static ChunkPos generationCenter() {
@@ -332,6 +332,18 @@ public class NativeStructureFactoryTest {
assertTrue(failureStart < source.indexOf("NativeStructureOwnershipStore.record("));
}
@Test
public void manualNativeStructureGenerationUsesTheRootTerrainHeight() throws Exception {
String source = Files.readString(
Path.of(System.getProperty("iris.nativeStructureStartInjectorSource")),
StandardCharsets.UTF_8);
assertTrue(source.contains(
"Engine.hostHeight(context.engine(), x, z, true)"));
assertFalse(source.contains(
"context.engine().getHeight(x, z, true)"));
}
@Test
public void failedManualStartPublicationClearsRecordedOwnershipAndPreservesTheFailure() throws Exception {
String source = Files.readString(