From df11ad2db0d947cc9763b710ddd1fa1319ab86cd Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Wed, 21 Oct 2026 23:15:40 -0400 Subject: [PATCH 1/4] Pack style carve golden - Added a four-profile golden scenario with pack cave styles sharing warp and density fields --- .../IrisCaveCarver3DCellKernelTest.java | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DCellKernelTest.java b/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DCellKernelTest.java index 9dfd04fd4..e0a4f8f57 100644 --- a/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DCellKernelTest.java +++ b/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DCellKernelTest.java @@ -83,6 +83,11 @@ public class IrisCaveCarver3DCellKernelTest { assertScenario(Scenario.FLOOR_FLUID_LAYERED, 0xb4c0fb23e071617dL, 68747); } + @Test + public void packStyleProfilesSharingFieldsMatchGoldenWriteSequence() throws Exception { + assertScenario(Scenario.PACK_SHARED, 0xdbe20a1ca87f9cc7L, 269035); + } + @Test public void cellKernelDecidesCellsWithoutColumnEvaluation() throws Exception { IrisCaveCarver3D.CellCounters before = IrisCaveCarver3D.cellCounters(); @@ -196,7 +201,31 @@ public class IrisCaveCarver3DCellKernelTest { return profile; } + private static IrisCaveProfile packProfile(NoiseStyle warp, double warpZoom, double warpStrength, NoiseStyle base, + double baseZoom, double threshold, IrisCaveFieldModule... modules) { + IrisCaveProfile profile = baseProfile(true, false); + profile.setWarpStyle(new IrisGeneratorStyle(warp).zoomed(warpZoom)); + profile.setWarpStrength(warpStrength); + profile.setBaseDensityStyle(new IrisGeneratorStyle(base).zoomed(baseZoom)); + profile.setDetailDensityStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.5D)); + profile.setBaseWeight(0.82D); + profile.setDetailWeight(0.15D); + profile.setDensityThreshold(new IrisStyledRange(threshold - 0.06D, threshold + 0.05D, + new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.78D))); + profile.setThresholdBias(0.12D); + profile.setSampleStep(2); + profile.setModules(new KList<>(modules)); + return profile; + } + + private static IrisCaveFieldModule packModule(NoiseStyle style, double zoom, double weight, double threshold, + double minY, double maxY, boolean invert) { + return new IrisCaveFieldModule(new IrisGeneratorStyle(style).zoomed(zoom), weight, threshold, + new IrisRange(minY, maxY), invert); + } + private enum Scenario { + PACK_SHARED, WARP_MODULES, WARP_TERRAIN, STYLED_BLEND, @@ -207,6 +236,20 @@ public class IrisCaveCarver3DCellKernelTest { FLOOR_FLUID_LAYERED; IrisCaveProfile[] profiles() { + if (this == PACK_SHARED) { + return new IrisCaveProfile[]{ + packProfile(NoiseStyle.FRACTAL_WATER, 0.44D, 3.2D, NoiseStyle.PERLIN_IRIS, 0.72D, -0.02D, + packModule(NoiseStyle.FRACTAL_FBM_SIMPLEX, 0.24D, 0.08D, 0.03D, 10D, 140D, false), + packModule(NoiseStyle.SIMPLEX_VASCULAR, 1.02D, 0.05D, 0.07D, 4D, 90D, true)), + packProfile(NoiseStyle.FRACTAL_WATER, 0.44D, 3.2D, NoiseStyle.FRACTAL_BILLOW_SIMPLEX, 0.656D, 0.01D, + packModule(NoiseStyle.CELLULAR_BILINEAR, 1.32D, 0.06D, 0.02D, 20D, 150D, false)), + packProfile(NoiseStyle.FRACTAL_WATER, 0.44D, 3.2D, NoiseStyle.PERLIN_IRIS, 0.72D, 0.04D, + packModule(NoiseStyle.FRACTAL_FBM_SIMPLEX, 0.24D, 0.08D, 0.03D, 10D, 140D, false), + packModule(NoiseStyle.SIMPLEX_VASCULAR, 1.02D, 0.05D, 0.07D, 4D, 90D, true)), + packProfile(NoiseStyle.PERLIN_IRIS, 0.35D, 2.4D, NoiseStyle.IRIS_DOUBLE, 0.68D, -0.04D, + packModule(NoiseStyle.VASCULAR_THIN, 1.28D, 0.04D, 0.05D, 8D, 120D, false)) + }; + } if (this == FLOOR_FLUID_LAYERED) { return new IrisCaveProfile[]{ baseProfile(true, true).setAllowFluid(true).setFluidRequiresFloor(true).setFluidMinDepthBelowSurface(4), @@ -228,7 +271,7 @@ public class IrisCaveCarver3DCellKernelTest { .setFluidMinDepthBelowSurface(6).setAllowLava(false); case NO_WARP_MODULES -> baseProfile(false, true); case LATTICE -> baseProfile(true, true).setSampleStep(3); - case FLOOR_FLUID_LAYERED -> baseProfile(true, true); + case FLOOR_FLUID_LAYERED, PACK_SHARED -> baseProfile(true, true); }; } @@ -239,6 +282,7 @@ public class IrisCaveCarver3DCellKernelTest { int worldZ = (chunkZ << 4) + (index & 15); double blend = 0.5D + 0.6D * Math.sin(worldX * 0.11D) * Math.cos(worldZ * 0.07D); weights[index] = switch (this) { + case PACK_SHARED -> 0.5D + 0.6D * Math.sin(worldX * 0.13D + layer * 1.7D) * Math.cos(worldZ * 0.09D - layer); case STYLED_BLEND, FLUID_BOUNDARY -> blend; case FLOOR_FLUID_LAYERED -> layer == 0 ? blend : 1D - blend; default -> 1D; From 4dec940311145ec95668cff4e92eae215c63c42b Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Wed, 21 Oct 2026 23:21:45 -0400 Subject: [PATCH 2/4] Compiled cave density programs - Carver base, detail, warp and module fields evaluate through compiled noise programs - Near-parity tests inject replacement densities as compiled programs --- .../mantle/CaveFieldModuleState.java | 10 ++-- .../generation/mantle/IrisCaveCarver3D.java | 57 ++++++++++--------- .../IrisCaveCarver3DNearParityTest.java | 37 ++++++------ 3 files changed, 53 insertions(+), 51 deletions(-) diff --git a/core/src/main/java/art/arcane/iris/generation/mantle/CaveFieldModuleState.java b/core/src/main/java/art/arcane/iris/generation/mantle/CaveFieldModuleState.java index 66e60be24..c963f2af2 100644 --- a/core/src/main/java/art/arcane/iris/generation/mantle/CaveFieldModuleState.java +++ b/core/src/main/java/art/arcane/iris/generation/mantle/CaveFieldModuleState.java @@ -20,10 +20,10 @@ package art.arcane.iris.generation.mantle; import art.arcane.iris.generation.cave.IrisCaveFieldModule; import art.arcane.iris.pack.value.IrisRange; -import art.arcane.volmlib.util.noise.CNG; +import art.arcane.volmlib.util.noise.program.NoiseProgram; final class CaveFieldModuleState { - final CNG density; + final NoiseProgram density; final int minY; final int maxY; final double weight; @@ -32,7 +32,7 @@ final class CaveFieldModuleState { final double minContribution; final double maxContribution; - CaveFieldModuleState(IrisCaveFieldModule module, CNG density) { + CaveFieldModuleState(IrisCaveFieldModule module, NoiseProgram density) { IrisRange range = module.getVerticalRange(); this.density = density; this.minY = (int) Math.floor(range.getMin()); @@ -47,12 +47,12 @@ final class CaveFieldModuleState { } double sample(double x, int y, double z) { - double sampled = density.noiseFastSigned3D(x, y, z); + double sampled = density.sampleSigned3D(x, y, z); return invert ? (threshold - sampled) * weight : (sampled - threshold) * weight; } double sample(double x, double y, double z) { - double sampled = density.noiseFastSigned3D(x, y, z); + double sampled = density.sampleSigned3D(x, y, z); return invert ? (threshold - sampled) * weight : (sampled - threshold) * weight; } } diff --git a/core/src/main/java/art/arcane/iris/generation/mantle/IrisCaveCarver3D.java b/core/src/main/java/art/arcane/iris/generation/mantle/IrisCaveCarver3D.java index 9c5551908..2566a19bf 100644 --- a/core/src/main/java/art/arcane/iris/generation/mantle/IrisCaveCarver3D.java +++ b/core/src/main/java/art/arcane/iris/generation/mantle/IrisCaveCarver3D.java @@ -26,6 +26,7 @@ import art.arcane.iris.pack.value.IrisRange; import art.arcane.iris.generation.noise.IrisGeneratorStyle; import art.arcane.iris.generation.noise.IrisStyledRange; import art.arcane.volmlib.util.noise.CNG; +import art.arcane.volmlib.util.noise.program.NoiseProgram; import art.arcane.volmlib.util.mantle.runtime.MantleChunk; import art.arcane.volmlib.util.math.M; import art.arcane.volmlib.util.math.PowerOfTwoCoordinates; @@ -73,9 +74,9 @@ public class IrisCaveCarver3D { private final Engine engine; private final IrisData data; private final IrisCaveProfile profile; - private final CNG baseDensity; - private final CNG detailDensity; - private final CNG warpDensity; + private final NoiseProgram baseDensity; + private final NoiseProgram detailDensity; + private final NoiseProgram warpDensity; private final CNG surfaceBreakDensity; private final RNG thresholdRng; private final CaveFieldModuleState[] modules; @@ -123,9 +124,9 @@ public class IrisCaveCarver3D { List moduleStates = new ArrayList<>(); RNG baseRng = new RNG(engine.getSeedManager().getCarve()); - this.baseDensity = profile.getBaseDensityStyle().create(baseRng.nextParallelRNG(934_447), data); - this.detailDensity = profile.getDetailDensityStyle().create(baseRng.nextParallelRNG(612_991), data); - this.warpDensity = profile.getWarpStyle().create(baseRng.nextParallelRNG(770_713), data); + this.baseDensity = NoiseProgram.compileSigned3D(profile.getBaseDensityStyle().create(baseRng.nextParallelRNG(934_447), data)); + this.detailDensity = NoiseProgram.compileSigned3D(profile.getDetailDensityStyle().create(baseRng.nextParallelRNG(612_991), data)); + this.warpDensity = NoiseProgram.compileSigned3D(profile.getWarpStyle().create(baseRng.nextParallelRNG(770_713), data)); this.surfaceBreakDensity = profile.getSurfaceBreakStyle().create(baseRng.nextParallelRNG(341_219), data); this.thresholdRng = baseRng.nextParallelRNG(489_112); this.baseWeight = profile.getBaseWeight(); @@ -143,7 +144,7 @@ public class IrisCaveCarver3D { int index = 0; for (IrisCaveFieldModule module : profile.getModules()) { CNG moduleDensity = module.getStyle().create(baseRng.nextParallelRNG(1_000_003L + (index * 65_537L)), data); - CaveFieldModuleState state = new CaveFieldModuleState(module, moduleDensity); + CaveFieldModuleState state = new CaveFieldModuleState(module, NoiseProgram.compileSigned3D(moduleDensity)); moduleStates.add(state); weight += Math.abs(state.weight); index++; @@ -2644,7 +2645,7 @@ public class IrisCaveCarver3D { } private boolean classifyDensityPointNoWarpNoModules(int x, int y, int z, double thresholdLimit) { - double density = baseDensity.noiseFastSigned3D(x, y, z) * baseWeight; + double density = baseDensity.sampleSigned3D(x, y, z) * baseWeight; if ((density + detailMinContribution) > thresholdLimit) { return false; } @@ -2652,7 +2653,7 @@ public class IrisCaveCarver3D { return true; } - density += detailDensity.noiseFastSigned3D(x, y, z) * detailWeight; + density += detailDensity.sampleSigned3D(x, y, z) * detailWeight; return density <= thresholdLimit; } @@ -2670,7 +2671,7 @@ public class IrisCaveCarver3D { return classifyDensityPointNoWarpNoModules(x, y, z, thresholdLimit); } - double density = baseDensity.noiseFastSigned3D(x, y, z) * baseWeight; + double density = baseDensity.sampleSigned3D(x, y, z) * baseWeight; if ((density + detailMinContribution + remainingMin[0]) > thresholdLimit) { return false; } @@ -2678,7 +2679,7 @@ public class IrisCaveCarver3D { return true; } - density += detailDensity.noiseFastSigned3D(x, y, z) * detailWeight; + density += detailDensity.sampleSigned3D(x, y, z) * detailWeight; if ((density + remainingMin[0]) > thresholdLimit) { return false; } @@ -2720,7 +2721,7 @@ public class IrisCaveCarver3D { double warpedX = x + (warpA * warpStrength); double warpedY = y + (warpB * warpStrength); double warpedZ = z + ((warpA - warpB) * 0.5D * warpStrength); - double density = baseDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * baseWeight; + double density = baseDensity.sampleSigned3D(warpedX, warpedY, warpedZ) * baseWeight; if ((density + detailMinContribution) > thresholdLimit) { return false; } @@ -2728,7 +2729,7 @@ public class IrisCaveCarver3D { return true; } - density += detailDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * detailWeight; + density += detailDensity.sampleSigned3D(warpedX, warpedY, warpedZ) * detailWeight; return density <= thresholdLimit; } @@ -2756,7 +2757,7 @@ public class IrisCaveCarver3D { double warpedX = x + (warpA * warpStrength); double warpedY = y + (warpB * warpStrength); double warpedZ = z + ((warpA - warpB) * 0.5D * warpStrength); - double density = baseDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * baseWeight; + double density = baseDensity.sampleSigned3D(warpedX, warpedY, warpedZ) * baseWeight; if ((density + detailMinContribution + remainingMin[0]) > thresholdLimit) { return false; } @@ -2764,7 +2765,7 @@ public class IrisCaveCarver3D { return true; } - density += detailDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * detailWeight; + density += detailDensity.sampleSigned3D(warpedX, warpedY, warpedZ) * detailWeight; if ((density + remainingMin[0]) > thresholdLimit) { return false; } @@ -3020,8 +3021,8 @@ public class IrisCaveCarver3D { } private double sampleDensityNoWarpNoModules(int x, int y, int z) { - double density = baseDensity.noiseFastSigned3D(x, y, z) * baseWeight; - density += detailDensity.noiseFastSigned3D(x, y, z) * detailWeight; + double density = baseDensity.sampleSigned3D(x, y, z) * baseWeight; + density += detailDensity.sampleSigned3D(x, y, z) * detailWeight; return density * inverseNormalization; } @@ -3032,11 +3033,11 @@ public class IrisCaveCarver3D { } CaveFieldModuleState[] localModules = scratch.activeModules; - double density = baseDensity.noiseFastSigned3D(x, y, z) * baseWeight; - density += detailDensity.noiseFastSigned3D(x, y, z) * detailWeight; + double density = baseDensity.sampleSigned3D(x, y, z) * baseWeight; + density += detailDensity.sampleSigned3D(x, y, z) * detailWeight; for (int moduleIndex = 0; moduleIndex < activeModuleCount; moduleIndex++) { CaveFieldModuleState module = localModules[moduleIndex]; - double moduleDensity = module.density.noiseFastSigned3D(x, y, z) - module.threshold; + double moduleDensity = module.density.sampleSigned3D(x, y, z) - module.threshold; if (module.invert) { moduleDensity = -moduleDensity; } @@ -3057,8 +3058,8 @@ public class IrisCaveCarver3D { double warpedX = x + (warpA * warpStrength); double warpedY = y + (warpB * warpStrength); double warpedZ = z + ((warpA - warpB) * 0.5D * warpStrength); - double density = baseDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * baseWeight; - density += detailDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * detailWeight; + double density = baseDensity.sampleSigned3D(warpedX, warpedY, warpedZ) * baseWeight; + density += detailDensity.sampleSigned3D(warpedX, warpedY, warpedZ) * detailWeight; return density * inverseNormalization; } @@ -3082,11 +3083,11 @@ public class IrisCaveCarver3D { double warpedX = x + (warpA * warpStrength); double warpedY = y + (warpB * warpStrength); double warpedZ = z + ((warpA - warpB) * 0.5D * warpStrength); - double density = baseDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * baseWeight; - density += detailDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * detailWeight; + double density = baseDensity.sampleSigned3D(warpedX, warpedY, warpedZ) * baseWeight; + density += detailDensity.sampleSigned3D(warpedX, warpedY, warpedZ) * detailWeight; for (int moduleIndex = 0; moduleIndex < activeModuleCount; moduleIndex++) { CaveFieldModuleState module = localModules[moduleIndex]; - double moduleDensity = module.density.noiseFastSigned3D(warpedX, warpedY, warpedZ) - module.threshold; + double moduleDensity = module.density.sampleSigned3D(warpedX, warpedY, warpedZ) - module.threshold; if (module.invert) { moduleDensity = -moduleDensity; } @@ -3110,8 +3111,8 @@ public class IrisCaveCarver3D { scratch.warpCacheX[slot] = sx; scratch.warpCacheY[slot] = sy; scratch.warpCacheZ[slot] = sz; - scratch.warpCacheA[slot] = warpDensity.noiseFastSigned3D(sx, sy, sz); - scratch.warpCacheB[slot] = warpDensity.noiseFastSigned3D(sx + 31.37D, sy - 17.21D, sz + 23.91D); + scratch.warpCacheA[slot] = warpDensity.sampleSigned3D(sx, sy, sz); + scratch.warpCacheB[slot] = warpDensity.sampleSigned3D(sx + 31.37D, sy - 17.21D, sz + 23.91D); return slot; } @@ -3227,7 +3228,7 @@ public class IrisCaveCarver3D { private boolean isAquiferCandidate(CaveCarveScratch scratch, int x, int y, int z, double localThreshold) { double depthFactor = Math.max(0D, Math.min(1.5D, (fluidHeight - y) / 48D)); double cutoff = 0.35D + (depthFactor * 0.2D); - if (detailDensity.noiseFastSigned3D(x, y * 0.5D, z) <= cutoff) { + if (detailDensity.sampleSigned3D(x, y * 0.5D, z) <= cutoff) { return false; } return !fluidRequiresFloor || hasAquiferCupSupport(scratch, x, y, z, localThreshold); diff --git a/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DNearParityTest.java b/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DNearParityTest.java index ced1764da..28edd359d 100644 --- a/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DNearParityTest.java +++ b/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DNearParityTest.java @@ -13,6 +13,7 @@ import art.arcane.iris.generation.noise.IrisStyledRange; import art.arcane.iris.world.IrisWorld; import art.arcane.iris.generation.noise.NoiseStyle; import art.arcane.volmlib.util.noise.CNG; +import art.arcane.volmlib.util.noise.program.NoiseProgram; import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.mantle.runtime.Mantle; import art.arcane.volmlib.util.mantle.runtime.MantleChunk; @@ -148,7 +149,7 @@ public class IrisCaveCarver3DNearParityTest { sampledY.add((int) y); return x * 0.0001D + y * 0.01D + z * 0.0002D; }).when(density).noiseFastSigned3D(anyDouble(), anyDouble(), anyDouble()); - baseDensityField.set(carver, density); + baseDensityField.set(carver, NoiseProgram.compileSigned3D(density)); Method prepare = IrisCaveCarver3D.class.getDeclaredMethod("prepareVerticalDensityPlane", CaveCarveScratch.class, int.class, int.class, int.class); prepare.setAccessible(true); @@ -168,7 +169,7 @@ public class IrisCaveCarver3DNearParityTest { doReturn(1).when(dimension).getCaveDensitySamplingStep(); IrisCaveCarver3D scalar = new IrisCaveCarver3D(engine, createProfile(false, false).setDetailWeight(0D).setAllowFluid(false)); - baseDensityField.set(scalar, density); + baseDensityField.set(scalar, NoiseProgram.compileSigned3D(density)); CaveCarveScratch scalarScratch = new CaveCarveScratch(); for (int index = 0; index < 256; index++) { scalarScratch.planeColumnIndices[index] = index; @@ -292,10 +293,10 @@ public class IrisCaveCarver3DNearParityTest { IrisCaveCarver3D carver = new IrisCaveCarver3D(engine, profile); CNG base = mock(CNG.class, withSettings().stubOnly()); doReturn(1D).when(base).noiseFastSigned3D(anyDouble(), anyDouble(), anyDouble()); - baseDensityField.set(carver, base); + baseDensityField.set(carver, NoiseProgram.compileSigned3D(base)); CNG module = mock(CNG.class, withSettings().stubOnly()); doReturn(-1D).when(module).noiseFastSigned3D(anyDouble(), anyDouble(), anyDouble()); - moduleDensityField.set(((CaveFieldModuleState[]) modulesField.get(carver))[0], module); + moduleDensityField.set(((CaveFieldModuleState[]) modulesField.get(carver))[0], NoiseProgram.compileSigned3D(module)); WriterCapture capture = createWriterCapture(128); carver.carve(capture.writer, -1, 2, fullWeights(), 0D, 0D, null, filledHeights(110)); assertEquals(512, capture.carvedCells.size()); @@ -364,7 +365,7 @@ public class IrisCaveCarver3DNearParityTest { } return -1D; }).when(density).noiseFastSigned3D(anyDouble(), anyDouble(), anyDouble()); - baseDensityField.set(carver, density); + baseDensityField.set(carver, NoiseProgram.compileSigned3D(density)); assertThrows(IllegalStateException.class, () -> carver.carve(capture.writer, 0, 0, fullWeights(), 0D, 0D, null, filledHeights(110))); } else { @@ -439,7 +440,7 @@ public class IrisCaveCarver3DNearParityTest { }).when(detail).noiseFastSigned3D(anyDouble(), anyDouble(), anyDouble()); Field detailField = IrisCaveCarver3D.class.getDeclaredField("detailDensity"); detailField.setAccessible(true); - detailField.set(carver, detail); + detailField.set(carver, NoiseProgram.compileSigned3D(detail)); WriterCapture capture = createWriterCapture(128); try (ForkJoinPool pool = new ForkJoinPool(4)) { assertEquals(0, pool.submit(() -> carver.carve(capture.writer, 0, 0, fullWeights(), @@ -772,7 +773,7 @@ public class IrisCaveCarver3DNearParityTest { rejectedDensity.put("11:10:10", -1D); rejectedDensity.put("9:10:10", -1D); CoordinateDensityCNG rejectedNoise = new CoordinateDensityCNG(rejectedDensity); - baseDensityField.set(carver, rejectedNoise); + baseDensityField.set(carver, NoiseProgram.compileSigned3D(rejectedNoise)); boolean rejected = (boolean) aquiferCupSupportMethod.invoke( carver, new CaveCarveScratch(), 10, 10, 10, 0D); @@ -781,7 +782,7 @@ public class IrisCaveCarver3DNearParityTest { assertEquals(List.of("10:9:10", "10:8:10", "11:10:10", "9:10:10"), rejectedNoise.sampledCoordinates); CoordinateDensityCNG acceptedNoise = new CoordinateDensityCNG(Map.of()); - baseDensityField.set(carver, acceptedNoise); + baseDensityField.set(carver, NoiseProgram.compileSigned3D(acceptedNoise)); boolean accepted = (boolean) aquiferCupSupportMethod.invoke( carver, new CaveCarveScratch(), 10, 10, 10, 0D); @@ -1100,7 +1101,7 @@ public class IrisCaveCarver3DNearParityTest { AtomicLong isolatedSamples = new AtomicLong(); IrisCaveCarver3D shared = new IrisCaveCarver3D(engine, createProfile(warp, modules).setAdaptiveSampling(true), new CaveDensityGrid(4096)); - baseDensityField.set(shared, new CountingCNG((CNG) baseDensityField.get(shared), sharedSamples)); + baseDensityField.set(shared, NoiseProgram.compileSigned3D(new CountingCNG((NoiseProgram) baseDensityField.get(shared), sharedSamples))); for (int chunkX = -1; chunkX <= 1; chunkX++) { for (int chunkZ = -1; chunkZ <= 1; chunkZ++) { WriterCapture sharedCapture = createWriterCapture(96); @@ -1108,7 +1109,7 @@ public class IrisCaveCarver3DNearParityTest { null, filledHeights(90)); IrisCaveCarver3D isolated = new IrisCaveCarver3D(engine, createProfile(warp, modules).setAdaptiveSampling(true)); - baseDensityField.set(isolated, new CountingCNG((CNG) baseDensityField.get(isolated), isolatedSamples)); + baseDensityField.set(isolated, NoiseProgram.compileSigned3D(new CountingCNG((NoiseProgram) baseDensityField.get(isolated), isolatedSamples))); WriterCapture isolatedCapture = createWriterCapture(96); int isolatedCarved = isolated.carve(isolatedCapture.writer, chunkX, chunkZ, fullWeights(), 0D, 0D, null, filledHeights(90)); @@ -1241,7 +1242,7 @@ public class IrisCaveCarver3DNearParityTest { new IrisCaveCarver3D(engine, profile).carve(expected.writer, -1, 2, fullWeights(), 0D, 0D, null, filledHeights(110)); IrisCaveCarver3D carver = new IrisCaveCarver3D(engine, profile); - CNG original = (CNG) baseDensityField.get(carver); + NoiseProgram original = (NoiseProgram) baseDensityField.get(carver); CNG density = mock(CNG.class, withSettings().stubOnly()); AtomicBoolean entered = new AtomicBoolean(); WriterCapture nested = createWriterCapture(128); @@ -1252,9 +1253,9 @@ public class IrisCaveCarver3DNearParityTest { double x = invocation.getArgument(0); double y = invocation.getArgument(1); double z = invocation.getArgument(2); - return original.noiseFastSigned3D(x, y, z); + return original.sampleSigned3D(x, y, z); }).when(density).noiseFastSigned3D(anyDouble(), anyDouble(), anyDouble()); - baseDensityField.set(carver, density); + baseDensityField.set(carver, NoiseProgram.compileSigned3D(density)); WriterCapture actual = createWriterCapture(128); carver.carve(actual.writer, -1, 2, fullWeights(), 0D, 0D, null, filledHeights(110)); assertTrue(entered.get()); @@ -1292,10 +1293,10 @@ public class IrisCaveCarver3DNearParityTest { } private void countSkippableSamples(IrisCaveCarver3D carver, AtomicLong samples) throws Exception { - detailDensityField.set(carver, new CountingCNG((CNG) detailDensityField.get(carver), samples)); + detailDensityField.set(carver, NoiseProgram.compileSigned3D(new CountingCNG((NoiseProgram) detailDensityField.get(carver), samples))); CaveFieldModuleState[] modules = (CaveFieldModuleState[]) modulesField.get(carver); for (CaveFieldModuleState module : modules) { - moduleDensityField.set(module, new CountingCNG((CNG) moduleDensityField.get(module), samples)); + moduleDensityField.set(module, NoiseProgram.compileSigned3D(new CountingCNG((NoiseProgram) moduleDensityField.get(module), samples))); } } @@ -1893,10 +1894,10 @@ public class IrisCaveCarver3DNearParityTest { } private static final class CountingCNG extends CNG { - private final CNG delegate; + private final NoiseProgram delegate; private final AtomicLong samples; - private CountingCNG(CNG delegate, AtomicLong samples) { + private CountingCNG(NoiseProgram delegate, AtomicLong samples) { super(new RNG(41_117L)); this.delegate = delegate; this.samples = samples; @@ -1905,7 +1906,7 @@ public class IrisCaveCarver3DNearParityTest { @Override public double noiseFastSigned3D(double x, double y, double z) { samples.incrementAndGet(); - return delegate.noiseFastSigned3D(x, y, z); + return delegate.sampleSigned3D(x, y, z); } } From b510f0dbfe3bb7dd3130c23240351841e5e6660f Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Wed, 21 Oct 2026 23:28:01 -0400 Subject: [PATCH 3/4] Batched shared cave lattice - Cell kernel passes fill a per-pass density lattice in field-major batches before classifying rows - Lattice density columns are shared between profiles with identical density fields - Warp samples are shared between profiles with the same warp field and batch along lattice columns - Added lattice read, sample and warp counters - Added bit-exact lattice column and field sharing tests --- .../generation/mantle/CaveCarveScratch.java | 27 +- .../generation/mantle/CaveDensityGrid.java | 46 ++- .../generation/mantle/CaveDensityLattice.java | 74 +++++ .../iris/generation/mantle/CaveFieldKey.java | 51 ++++ .../generation/mantle/IrisCaveCarver3D.java | 273 +++++++++++++++--- .../generation/runtime/GenerationCounter.java | 5 +- .../mantle/IrisCaveCarver3DLatticeTest.java | 163 +++++++++++ .../IrisCaveCarver3DNearParityTest.java | 10 +- 8 files changed, 587 insertions(+), 62 deletions(-) create mode 100644 core/src/main/java/art/arcane/iris/generation/mantle/CaveDensityLattice.java create mode 100644 core/src/main/java/art/arcane/iris/generation/mantle/CaveFieldKey.java create mode 100644 core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DLatticeTest.java diff --git a/core/src/main/java/art/arcane/iris/generation/mantle/CaveCarveScratch.java b/core/src/main/java/art/arcane/iris/generation/mantle/CaveCarveScratch.java index aa3c3f690..5fd6a127c 100644 --- a/core/src/main/java/art/arcane/iris/generation/mantle/CaveCarveScratch.java +++ b/core/src/main/java/art/arcane/iris/generation/mantle/CaveCarveScratch.java @@ -68,8 +68,7 @@ final class CaveCarveScratch { final int[] tileTopY = new int[4]; final long[] rowCarveBits = new long[4]; final long[] cellPassBits = new long[4]; - final double[] rowLatticeDensity = new double[9]; - final CaveDensityGrid.Column[] cellCornerColumns = new CaveDensityGrid.Column[9]; + final CaveDensityLattice cellLattice = new CaveDensityLattice(); final double[] cellThresholdMin = new double[4]; final double[] cellThresholdMax = new double[4]; final double[] cellThresholdMaxBoost = new double[4]; @@ -98,11 +97,33 @@ final class CaveCarveScratch { int adaptiveGeometryStep = -1; int adaptiveGeometryAxisCells = -1; boolean fullWeightsInitialized; + int[] latticeY = new int[0]; + int[] latticeSnapY = new int[0]; + double[] latticeWarpA = new double[0]; + double[] latticeWarpB = new double[0]; + double[] latticeWarpedX = new double[0]; + double[] latticeWarpedY = new double[0]; + double[] latticeWarpedZ = new double[0]; + double[] latticeDensity = new double[0]; void releaseReferences() { Arrays.fill(sectionMatter, null); Arrays.fill(sectionSlices, null); Arrays.fill(densityColumns, null); - Arrays.fill(cellCornerColumns, null); + cellLattice.releaseReferences(); + } + + void ensureLatticeBatch(int size) { + if (latticeY.length >= size) { + return; + } + latticeY = new int[size]; + latticeSnapY = new int[size]; + latticeWarpA = new double[size]; + latticeWarpB = new double[size]; + latticeWarpedX = new double[size]; + latticeWarpedY = new double[size]; + latticeWarpedZ = new double[size]; + latticeDensity = new double[size]; } } diff --git a/core/src/main/java/art/arcane/iris/generation/mantle/CaveDensityGrid.java b/core/src/main/java/art/arcane/iris/generation/mantle/CaveDensityGrid.java index 824523a64..17a40ba85 100644 --- a/core/src/main/java/art/arcane/iris/generation/mantle/CaveDensityGrid.java +++ b/core/src/main/java/art/arcane/iris/generation/mantle/CaveDensityGrid.java @@ -5,6 +5,7 @@ import art.arcane.iris.generation.cache.ConcurrentClockCache; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.util.Arrays; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReferenceArray; @@ -22,42 +23,63 @@ final class CaveDensityGrid { private static final AtomicInteger OWNERS = new AtomicInteger(); private final ConcurrentClockCache columns; + private final ConcurrentClockCache warpColumns; + private final ConcurrentHashMap owners = new ConcurrentHashMap<>(); CaveDensityGrid(int capacity) { columns = new ConcurrentClockCache<>(capacity); + warpColumns = new ConcurrentClockCache<>(capacity); } - static int nextOwner() { - return OWNERS.getAndIncrement(); + Owner owner(Object fieldKey) { + return owners.computeIfAbsent(fieldKey, ignored -> new Owner(OWNERS.getAndIncrement())); } - Column column(Object owner, int ownerId, int height, int latticeX, int latticeZ) { - long key = (long) (ownerId & 0xFFFF) << 48 | (latticeX & COORDINATE_MASK) << 24 | (latticeZ & COORDINATE_MASK); - Column cached = columns.get(key); - if (cached == null) { - cached = columns.putIfAbsent(key, new Column(owner, latticeX, latticeZ, height)); - } - return cached.matches(owner, latticeX, latticeZ) ? cached : null; + Column column(Owner owner, int height, int latticeX, int latticeZ) { + return column(columns, owner, height, latticeX, latticeZ); + } + + Column warpColumn(Owner owner, int height, int latticeX, int latticeZ) { + return column(warpColumns, owner, height, latticeX, latticeZ); } void clear() { columns.clear(); + warpColumns.clear(); + owners.clear(); + } + + private static Column column(ConcurrentClockCache cache, Owner owner, int height, int latticeX, int latticeZ) { + long key = (long) (owner.id & 0xFFFF) << 48 | (latticeX & COORDINATE_MASK) << 24 | (latticeZ & COORDINATE_MASK); + Column cached = cache.get(key); + if (cached == null) { + cached = cache.putIfAbsent(key, new Column(owner, latticeX, latticeZ, height)); + } + return cached.matches(owner, latticeX, latticeZ) ? cached : null; + } + + static final class Owner { + private final int id; + + private Owner(int id) { + this.id = id; + } } static final class Column { - private final Object owner; + private final Owner owner; private final int latticeX; private final int latticeZ; private final AtomicReferenceArray segments; - private Column(Object owner, int latticeX, int latticeZ, int height) { + private Column(Owner owner, int latticeX, int latticeZ, int height) { this.owner = owner; this.latticeX = latticeX; this.latticeZ = latticeZ; segments = new AtomicReferenceArray<>(Math.ceilDiv(Math.max(1, height), SEGMENT_SIZE)); } - boolean matches(Object owner, int latticeX, int latticeZ) { + boolean matches(Owner owner, int latticeX, int latticeZ) { return this.owner == owner && this.latticeX == latticeX && this.latticeZ == latticeZ; } diff --git a/core/src/main/java/art/arcane/iris/generation/mantle/CaveDensityLattice.java b/core/src/main/java/art/arcane/iris/generation/mantle/CaveDensityLattice.java new file mode 100644 index 000000000..10aadb38d --- /dev/null +++ b/core/src/main/java/art/arcane/iris/generation/mantle/CaveDensityLattice.java @@ -0,0 +1,74 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package art.arcane.iris.generation.mantle; + +import java.util.Arrays; + +final class CaveDensityLattice { + static final int CORNERS = 9; + + final CaveDensityGrid.Column[] densityColumns = new CaveDensityGrid.Column[CORNERS]; + final CaveDensityGrid.Column[] warpColumns = new CaveDensityGrid.Column[CORNERS]; + private double[] values = new double[0]; + private byte[] rowCells = new byte[0]; + private short[] rowCorners = new short[0]; + private int minimumY; + private int rows; + + void reset(int minimumY, int rows) { + this.minimumY = minimumY; + this.rows = rows; + if (rowCells.length < rows) { + rowCells = new byte[rows]; + rowCorners = new short[rows]; + } + if (values.length < rows * CORNERS) { + values = new double[rows * CORNERS]; + } + Arrays.fill(rowCells, 0, rows, (byte) 0); + Arrays.fill(rowCorners, 0, rows, (short) 0); + } + + void markRow(int y, int cells, int corners) { + int row = y - minimumY; + rowCells[row] = (byte) cells; + rowCorners[row] = (short) corners; + } + + int cells(int y) { + return rowCells[y - minimumY]; + } + + boolean needs(int corner, int y) { + return (rowCorners[y - minimumY] & (1 << corner)) != 0; + } + + double density(int corner, int y) { + return values[corner * rows + (y - minimumY)]; + } + + void store(int corner, int y, double density) { + values[corner * rows + (y - minimumY)] = density; + } + + void releaseReferences() { + Arrays.fill(densityColumns, null); + Arrays.fill(warpColumns, null); + } +} diff --git a/core/src/main/java/art/arcane/iris/generation/mantle/CaveFieldKey.java b/core/src/main/java/art/arcane/iris/generation/mantle/CaveFieldKey.java new file mode 100644 index 000000000..7278e09db --- /dev/null +++ b/core/src/main/java/art/arcane/iris/generation/mantle/CaveFieldKey.java @@ -0,0 +1,51 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package art.arcane.iris.generation.mantle; + +import art.arcane.volmlib.util.noise.program.NoiseProgram; +import art.arcane.volmlib.util.noise.program.PrimitiveKind; + +record CaveFieldKey(long signature, long probe, NoiseProgram identity) { + private static final double[][] PROBE_POINTS = { + {0.5D, 3.25D, -7.75D}, + {-1031.125D, 77.5D, 2049.875D}, + {16D, 255D, -16D}, + {123_456.375D, -40.25D, -98_765.625D} + }; + + static CaveFieldKey of(NoiseProgram program) { + if (!shareable(program)) { + return new CaveFieldKey(program.structuralSignature(), 0L, program); + } + long probe = 0xCBF29CE484222325L; + for (double[] point : PROBE_POINTS) { + probe = (probe ^ Double.doubleToRawLongBits(program.sampleSigned3D(point[0], point[1], point[2]))) * 0x100000001B3L; + } + return new CaveFieldKey(program.structuralSignature(), probe, null); + } + + private static boolean shareable(NoiseProgram program) { + for (int node = 0; node < program.nodeCount(); node++) { + if (program.nodeOpaque(node) || program.nodeKernel(node).kind() == PrimitiveKind.GENERIC) { + return false; + } + } + return true; + } +} diff --git a/core/src/main/java/art/arcane/iris/generation/mantle/IrisCaveCarver3D.java b/core/src/main/java/art/arcane/iris/generation/mantle/IrisCaveCarver3D.java index 2566a19bf..fa348442e 100644 --- a/core/src/main/java/art/arcane/iris/generation/mantle/IrisCaveCarver3D.java +++ b/core/src/main/java/art/arcane/iris/generation/mantle/IrisCaveCarver3D.java @@ -20,6 +20,7 @@ package art.arcane.iris.generation.mantle; import art.arcane.iris.pack.loading.IrisData; import art.arcane.iris.generation.runtime.Engine; +import art.arcane.iris.generation.runtime.GenerationCounter; import art.arcane.iris.generation.cave.IrisCaveFieldModule; import art.arcane.iris.generation.cave.IrisCaveProfile; import art.arcane.iris.pack.value.IrisRange; @@ -27,6 +28,7 @@ import art.arcane.iris.generation.noise.IrisGeneratorStyle; import art.arcane.iris.generation.noise.IrisStyledRange; import art.arcane.volmlib.util.noise.CNG; import art.arcane.volmlib.util.noise.program.NoiseProgram; +import art.arcane.volmlib.util.noise.program.NoiseScratch; import art.arcane.volmlib.util.mantle.runtime.MantleChunk; import art.arcane.volmlib.util.math.M; import art.arcane.volmlib.util.math.PowerOfTwoCoordinates; @@ -70,6 +72,10 @@ public class IrisCaveCarver3D { private static final LongAdder OPEN_CELLS = new LongAdder(); private static final LongAdder MIXED_CELLS = new LongAdder(); private static final LongAdder EXACT_ROWS = new LongAdder(); + private static final double WARP_B_OFFSET_X = 31.37D; + private static final double WARP_B_OFFSET_Y = 17.21D; + private static final double WARP_B_OFFSET_Z = 23.91D; + private static final ThreadLocal NOISE_SCRATCH = ThreadLocal.withInitial(NoiseScratch::new); private final Engine engine; private final IrisData data; @@ -106,7 +112,8 @@ public class IrisCaveCarver3D { private final ThreadLocal carving = ThreadLocal.withInitial(() -> false); private final ThreadLocal scratchCache = ThreadLocal.withInitial(CaveCarveScratch::new); private final CaveDensityGrid densityGrid; - private final int densityGridOwner = CaveDensityGrid.nextOwner(); + private final CaveDensityGrid.Owner densityOwner; + private final CaveDensityGrid.Owner warpOwner; public IrisCaveCarver3D(Engine engine, IrisCaveProfile profile) { this(engine, profile, new CaveDensityGrid(STANDALONE_DENSITY_GRID_COLUMNS)); @@ -161,6 +168,21 @@ public class IrisCaveCarver3D { verticalDensityStep = parallelDensity ? Math.max(1, engine.getDimension().getCaveDensitySamplingStep()) : 1; worldMinimumY = engine.getWorld().minHeight(); densityMaximumY = engine.getWorld().getHeight() - 1; + CaveFieldKey warpKey = CaveFieldKey.of(warpDensity); + warpOwner = densityGrid.owner(new WarpFieldKey(warpKey)); + densityOwner = densityGrid.owner(new DensityFieldKey(hasWarp, warpKey, Double.doubleToRawLongBits(warpStrength), + CaveFieldKey.of(baseDensity), Double.doubleToRawLongBits(baseWeight), CaveFieldKey.of(detailDensity), + Double.doubleToRawLongBits(detailWeight), Double.doubleToRawLongBits(inverseNormalization), + moduleFieldKeys(modules))); + } + + private static List moduleFieldKeys(CaveFieldModuleState[] modules) { + List keys = new ArrayList<>(modules.length); + for (CaveFieldModuleState module : modules) { + keys.add(new ModuleFieldKey(CaveFieldKey.of(module.density), module.minY, module.maxY, + Double.doubleToRawLongBits(module.weight), Double.doubleToRawLongBits(module.threshold), module.invert)); + } + return List.copyOf(keys); } public static CellCounters cellCounters() { @@ -986,6 +1008,9 @@ public class IrisCaveCarver3D { fluidMaxY, matterByY, cellKernel)); if (cellKernel) { + if (classified == null) { + fillCellLattice(scratch, scratch, x0, z0, minY, passMaxY); + } return emitCellPass(chunk, scratch, classified, x0, z0, minY, passMaxY, minSection, maxSection, activeColumnIndices, activeColumnTopY, activeColumnCount, surfaceFluidBoundaries, surfaceBreakThresholdBoost, verticalEdgeFade, fluidMaxY, matterByY, fluidSupportPlan); @@ -1175,18 +1200,194 @@ public class IrisCaveCarver3D { for (int row = 1; row < rows; row++) { rowActiveCount[row] += rowActiveCount[row - 1]; } - int latticeX = x0 >> CaveDensityGrid.LATTICE_SHIFT; - int latticeZ = z0 >> CaveDensityGrid.LATTICE_SHIFT; int usedCorners = 0; for (int cell = 0; cell < 4; cell++) { if (plan.cellTopY[cell] >= minY) { usedCorners |= CELL_CORNER_MASKS[cell]; } } - for (int corner = 0; corner < 9; corner++) { - plan.cellCornerColumns[corner] = (usedCorners & (1 << corner)) == 0 ? null - : densityGrid.column(this, densityGridOwner, densityMaximumY + 1, - latticeX + (corner / 3), latticeZ + (corner % 3)); + prepareCellLattice(plan, x0 >> CaveDensityGrid.LATTICE_SHIFT, z0 >> CaveDensityGrid.LATTICE_SHIFT, minY, passMaxY, + usedCorners); + } + + private void prepareCellLattice(CaveCarveScratch plan, int latticeX, int latticeZ, int minY, int passMaxY, int usedCorners) { + CaveDensityLattice lattice = plan.cellLattice; + lattice.reset(minY, passMaxY - minY + 1); + for (int corner = 0; corner < CaveDensityLattice.CORNERS; corner++) { + boolean used = (usedCorners & (1 << corner)) != 0; + int cornerX = latticeX + (corner / 3); + int cornerZ = latticeZ + (corner % 3); + lattice.densityColumns[corner] = used ? densityGrid.column(densityOwner, densityMaximumY + 1, cornerX, cornerZ) : null; + lattice.warpColumns[corner] = used && hasWarp + ? densityGrid.warpColumn(warpOwner, densityMaximumY + 2, cornerX, cornerZ) : null; + } + for (int y = minY; y <= passMaxY; y++) { + if (plan.rowActiveCount[y - minY] < ADAPTIVE_MIN_PLANE_COLUMNS) { + continue; + } + int cells = 0; + int corners = 0; + for (int cell = 0; cell < 4; cell++) { + if (isCellActive(plan, cell, y)) { + cells |= 1 << cell; + corners |= CELL_CORNER_MASKS[cell]; + } + } + lattice.markRow(y, cells, corners); + } + } + + private void fillCellLattice(CaveCarveScratch scratch, CaveCarveScratch plan, int x0, int z0, int fromY, int toY) { + CaveDensityLattice lattice = plan.cellLattice; + scratch.ensureLatticeBatch(toY - fromY + 1); + int[] latticeY = scratch.latticeY; + double[] density = scratch.latticeDensity; + long reads = 0L; + long samples = 0L; + for (int corner = 0; corner < CaveDensityLattice.CORNERS; corner++) { + CaveDensityGrid.Column column = lattice.densityColumns[corner]; + int count = 0; + for (int y = fromY; y <= toY; y++) { + if (!lattice.needs(corner, y)) { + continue; + } + reads++; + double cached = column == null ? Double.NaN : column.get(y); + if (Double.isNaN(cached)) { + latticeY[count++] = y; + } else { + lattice.store(corner, y, cached); + } + } + if (count == 0) { + continue; + } + samples += count; + sampleLatticeColumn(scratch, lattice.warpColumns[corner], x0 + (corner / 3) * ADAPTIVE_DEEP_SAMPLE_STEP, + z0 + (corner % 3) * ADAPTIVE_DEEP_SAMPLE_STEP, count); + for (int index = 0; index < count; index++) { + lattice.store(corner, latticeY[index], density[index]); + if (column != null) { + column.set(latticeY[index], density[index]); + } + } + } + if (GenerationCounter.ENABLED) { + GenerationCounter.CAVE_LATTICE_READS.add(reads); + GenerationCounter.CAVE_LATTICE_SAMPLES.add(samples); + } + } + + private void sampleLatticeColumn(CaveCarveScratch scratch, CaveDensityGrid.Column warpColumn, int x, int z, int count) { + int[] latticeY = scratch.latticeY; + double[] density = scratch.latticeDensity; + if (!hasWarp) { + for (int index = 0; index < count; index++) { + density[index] = sampleDensityNoWarpNoModules(x, latticeY[index], z); + } + return; + } + resolveLatticeWarp(scratch, warpColumn, x, z, count); + double[] warpedX = scratch.latticeWarpedX; + double[] warpedY = scratch.latticeWarpedY; + double[] warpedZ = scratch.latticeWarpedZ; + for (int index = 0; index < count; index++) { + density[index] = baseDensity.sampleSigned3D(warpedX[index], warpedY[index], warpedZ[index]) * baseWeight; + } + for (int index = 0; index < count; index++) { + density[index] += detailDensity.sampleSigned3D(warpedX[index], warpedY[index], warpedZ[index]) * detailWeight; + } + for (CaveFieldModuleState module : modules) { + for (int index = 0; index < count; index++) { + int y = latticeY[index]; + if (y < module.minY || y > module.maxY) { + continue; + } + double moduleDensity = module.density.sampleSigned3D(warpedX[index], warpedY[index], warpedZ[index]) - module.threshold; + if (module.invert) { + moduleDensity = -moduleDensity; + } + density[index] += moduleDensity * module.weight; + } + } + for (int index = 0; index < count; index++) { + density[index] = density[index] * inverseNormalization; + } + } + + private void resolveLatticeWarp(CaveCarveScratch scratch, CaveDensityGrid.Column warpColumn, int x, int z, int count) { + int[] latticeY = scratch.latticeY; + int[] snapY = scratch.latticeSnapY; + double[] warpA = scratch.latticeWarpA; + double[] warpB = scratch.latticeWarpB; + int snapCount = 0; + for (int index = 0; index < count; index++) { + int snap = snapWarp(latticeY[index]); + if (snapCount == 0 || snapY[snapCount - 1] != snap) { + snapY[snapCount++] = snap; + } + } + long computed = 0L; + int runStart = -1; + for (int snapIndex = 0; snapIndex <= snapCount; snapIndex++) { + boolean missing = false; + if (snapIndex < snapCount) { + int snap = snapY[snapIndex]; + double cachedA = warpColumn == null ? Double.NaN : warpColumn.get(snap); + double cachedB = warpColumn == null ? Double.NaN : warpColumn.get(snap + 1); + missing = Double.isNaN(cachedA) || Double.isNaN(cachedB); + warpA[snapIndex] = cachedA; + warpB[snapIndex] = cachedB; + } + if (runStart >= 0 && (!missing || snapY[snapIndex] != snapY[snapIndex - 1] + warpResolution)) { + computeLatticeWarpRun(scratch, warpColumn, x, z, runStart, snapIndex); + computed += snapIndex - runStart; + runStart = -1; + } + if (missing && runStart < 0) { + runStart = snapIndex; + } + } + if (GenerationCounter.ENABLED) { + GenerationCounter.CAVE_WARP_SAMPLES.add(computed); + } + double[] warpedX = scratch.latticeWarpedX; + double[] warpedY = scratch.latticeWarpedY; + double[] warpedZ = scratch.latticeWarpedZ; + int snapIndex = 0; + for (int index = 0; index < count; index++) { + int y = latticeY[index]; + int snap = snapWarp(y); + while (snapY[snapIndex] != snap) { + snapIndex++; + } + double a = warpA[snapIndex]; + double b = warpB[snapIndex]; + warpedX[index] = x + (a * warpStrength); + warpedY[index] = y + (b * warpStrength); + warpedZ[index] = z + ((a - b) * 0.5D * warpStrength); + } + } + + private void computeLatticeWarpRun(CaveCarveScratch scratch, CaveDensityGrid.Column warpColumn, int x, int z, int from, int to) { + int[] snapY = scratch.latticeSnapY; + double[] warpA = scratch.latticeWarpA; + double[] warpB = scratch.latticeWarpB; + int length = to - from; + if (length == 1) { + warpA[from] = warpDensity.sampleSigned3D(x, snapY[from], z); + } else { + double[] column = scratch.latticeWarpedX; + warpDensity.fillColumn3D(NOISE_SCRATCH.get(), x, z, snapY[from], warpResolution, length, column); + System.arraycopy(column, 0, warpA, from, length); + } + for (int snapIndex = from; snapIndex < to; snapIndex++) { + int snap = snapY[snapIndex]; + warpB[snapIndex] = warpDensity.sampleSigned3D(x + WARP_B_OFFSET_X, snap - WARP_B_OFFSET_Y, z + WARP_B_OFFSET_Z); + if (warpColumn != null) { + warpColumn.set(snap, warpA[snapIndex]); + warpColumn.set(snap + 1, warpB[snapIndex]); + } } } @@ -1269,6 +1470,7 @@ public class IrisCaveCarver3D { private void classifyCellRows(DensityPass pass, int minimumY, int maximumY, byte[] classified) { CaveCarveScratch scratch = new CaveCarveScratch(); CaveCarveScratch plan = pass.sourceScratch(); + fillCellLattice(scratch, plan, pass.x(), pass.z(), minimumY, maximumY); long[] rowBits = scratch.rowCarveBits; for (int y = minimumY; y <= maximumY; y++) { int fadeIndex = y - pass.minimumY(); @@ -1314,7 +1516,7 @@ public class IrisCaveCarver3D { ) { Arrays.fill(rowBits, 0L); if (plan.rowActiveCount[y - minY] >= ADAPTIVE_MIN_PLANE_COLUMNS) { - classifyCellRow(scratch, plan, x0, z0, y, minY, surfaceBreakThresholdBoost, verticalEdgeFade, rowBits); + classifyCellRow(scratch, plan, y, minY, surfaceBreakThresholdBoost, verticalEdgeFade, rowBits); return; } int[] planeColumnIndices = scratch.planeColumnIndices; @@ -1347,32 +1549,17 @@ public class IrisCaveCarver3D { private void classifyCellRow( CaveCarveScratch scratch, CaveCarveScratch plan, - int x0, - int z0, int y, int minY, double surfaceBreakThresholdBoost, double[] verticalEdgeFade, long[] rowBits ) { - int activeCells = 0; - int corners = 0; - for (int cell = 0; cell < 4; cell++) { - if (isCellActive(plan, cell, y)) { - activeCells |= 1 << cell; - corners |= CELL_CORNER_MASKS[cell]; - } - } + CaveDensityLattice lattice = plan.cellLattice; + int activeCells = lattice.cells(y); if (activeCells == 0) { return; } - double[] density = scratch.rowLatticeDensity; - for (int corner = 0; corner < 9; corner++) { - if ((corners & (1 << corner)) != 0) { - density[corner] = cornerDensity(scratch, plan.cellCornerColumns[corner], - x0 + (corner / 3) * ADAPTIVE_DEEP_SAMPLE_STEP, y, z0 + (corner % 3) * ADAPTIVE_DEEP_SAMPLE_STEP); - } - } int fadeIndex = y - minY; double fade = verticalEdgeFade[fadeIndex]; double closure = plan.surfaceClosureThreshold[fadeIndex]; @@ -1381,10 +1568,10 @@ public class IrisCaveCarver3D { continue; } int corner = ((cell >> 1) * 3) + (cell & 1); - double d00 = density[corner]; - double d01 = density[corner + 1]; - double d10 = density[corner + 3]; - double d11 = density[corner + 4]; + double d00 = lattice.density(corner, y); + double d01 = lattice.density(corner + 1, y); + double d10 = lattice.density(corner + 3, y); + double d11 = lattice.density(corner + 4, y); double low = plan.cellThresholdMin[cell] - fade; if (y >= plan.cellCeilingFadeStart[cell]) { low = Math.min(low, closure); @@ -1525,19 +1712,6 @@ public class IrisCaveCarver3D { && (y < plan.columnProtectStartY[column] || y > plan.columnProtectEndY[column]); } - private double cornerDensity(CaveCarveScratch scratch, CaveDensityGrid.Column column, int x, int y, int z) { - if (column == null) { - return samplePlaneDensity(scratch, x, y, z); - } - double cached = column.get(y); - if (!Double.isNaN(cached)) { - return cached; - } - double density = samplePlaneDensity(scratch, x, y, z); - column.set(y, density); - return density; - } - private double cellThreshold(CaveCarveScratch plan, int column, int y, int minY, double surfaceBreakThresholdBoost, double[] verticalEdgeFade) { return CaveCellKernel.threshold(cellLocalThreshold(plan, column, y, minY, surfaceBreakThresholdBoost, verticalEdgeFade), @@ -2991,8 +3165,8 @@ public class IrisCaveCarver3D { int latticeZ = z >> CaveDensityGrid.LATTICE_SHIFT; int slot = ((latticeX & 3) << 2) | (latticeZ & 3); CaveDensityGrid.Column column = parallelDensity ? scratch.densityColumns[slot] : null; - if (column == null || !column.matches(this, latticeX, latticeZ)) { - column = densityGrid.column(this, densityGridOwner, densityMaximumY + 1, latticeX, latticeZ); + if (column == null || !column.matches(densityOwner, latticeX, latticeZ)) { + column = densityGrid.column(densityOwner, densityMaximumY + 1, latticeX, latticeZ); if (parallelDensity) { scratch.densityColumns[slot] = column; } @@ -3401,4 +3575,15 @@ public class IrisCaveCarver3D { return verticalEdgeFade; } + + private record WarpFieldKey(CaveFieldKey field) { + } + + private record ModuleFieldKey(CaveFieldKey field, int minY, int maxY, long weight, long threshold, boolean invert) { + } + + private record DensityFieldKey(boolean warp, CaveFieldKey warpField, long warpStrength, CaveFieldKey base, long baseWeight, + CaveFieldKey detail, long detailWeight, long inverseNormalization, + List modules) { + } } diff --git a/core/src/main/java/art/arcane/iris/generation/runtime/GenerationCounter.java b/core/src/main/java/art/arcane/iris/generation/runtime/GenerationCounter.java index 70c3aa3d2..df740c7f2 100644 --- a/core/src/main/java/art/arcane/iris/generation/runtime/GenerationCounter.java +++ b/core/src/main/java/art/arcane/iris/generation/runtime/GenerationCounter.java @@ -5,7 +5,10 @@ import java.util.concurrent.atomic.LongAdder; public enum GenerationCounter { GENERATOR_EVALUATIONS("generator_evaluations"), CORNER_STARCASTS("corner_starcasts"), - HYDROLOGY_LOOKUPS("hydrology_lookups"); + HYDROLOGY_LOOKUPS("hydrology_lookups"), + CAVE_LATTICE_READS("cave_lattice_reads"), + CAVE_LATTICE_SAMPLES("cave_lattice_samples"), + CAVE_WARP_SAMPLES("cave_warp_samples"); public static final boolean ENABLED = Boolean.getBoolean("iris.generation.counters"); diff --git a/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DLatticeTest.java b/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DLatticeTest.java new file mode 100644 index 000000000..db789b87f --- /dev/null +++ b/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DLatticeTest.java @@ -0,0 +1,163 @@ +package art.arcane.iris.generation.mantle; + +import art.arcane.iris.generation.cave.IrisCaveFieldModule; +import art.arcane.iris.generation.cave.IrisCaveProfile; +import art.arcane.iris.generation.noise.IrisGeneratorStyle; +import art.arcane.iris.generation.noise.IrisStyledRange; +import art.arcane.iris.generation.noise.NoiseStyle; +import art.arcane.iris.generation.runtime.Engine; +import art.arcane.iris.generation.runtime.EngineMetrics; +import art.arcane.iris.generation.runtime.SeedManager; +import art.arcane.iris.generation.terrain.IrisDimension; +import art.arcane.iris.pack.loading.IrisData; +import art.arcane.iris.pack.value.IrisRange; +import art.arcane.iris.world.IrisWorld; +import art.arcane.volmlib.util.collection.KList; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Random; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.withSettings; + +public class IrisCaveCarver3DLatticeTest { + private static final long CARVE_SEED = 7_340_119_802_557L; + private static final int WORLD_HEIGHT = 384; + + @Test + public void batchedLatticeColumnsMatchScalarPlaneDensityBitForBit() throws Exception { + Engine engine = createEngine(); + CaveDensityGrid grid = new CaveDensityGrid(256); + IrisCaveProfile[] profiles = { + profile(NoiseStyle.FRACTAL_WATER, 0.44D, 3.2D, NoiseStyle.PERLIN_IRIS, 0.72D, + module(NoiseStyle.FRACTAL_FBM_SIMPLEX, 0.24D, 10D, 140D, false), + module(NoiseStyle.CELLULAR_BILINEAR, 1.32D, 4D, 300D, true)), + profile(NoiseStyle.PERLIN_IRIS, 0.35D, 2.4D, NoiseStyle.IRIS_DOUBLE, 0.68D, + module(NoiseStyle.VASCULAR_THIN, 1.28D, 8D, 220D, false)), + profile(NoiseStyle.SIMPLEX, 0.5D, 0D, NoiseStyle.FRACTAL_BILLOW_SIMPLEX, 0.656D) + }; + Method sampleLatticeColumn = IrisCaveCarver3D.class.getDeclaredMethod("sampleLatticeColumn", + CaveCarveScratch.class, CaveDensityGrid.Column.class, int.class, int.class, int.class); + sampleLatticeColumn.setAccessible(true); + Method samplePlaneDensity = IrisCaveCarver3D.class.getDeclaredMethod("samplePlaneDensity", + CaveCarveScratch.class, int.class, int.class, int.class); + samplePlaneDensity.setAccessible(true); + Field warpOwnerField = IrisCaveCarver3D.class.getDeclaredField("warpOwner"); + warpOwnerField.setAccessible(true); + Random random = new Random(91_337L); + for (IrisCaveProfile profile : profiles) { + IrisCaveCarver3D carver = new IrisCaveCarver3D(engine, profile, grid); + CaveDensityGrid.Owner warpOwner = (CaveDensityGrid.Owner) warpOwnerField.get(carver); + CaveCarveScratch batch = new CaveCarveScratch(); + CaveCarveScratch reference = new CaveCarveScratch(); + for (int trial = 0; trial < 48; trial++) { + int latticeX = random.nextInt(4001) - 2000; + int latticeZ = random.nextInt(4001) - 2000; + int x = latticeX << CaveDensityGrid.LATTICE_SHIFT; + int z = latticeZ << CaveDensityGrid.LATTICE_SHIFT; + int count = selectRows(random, batch); + CaveDensityGrid.Column warpColumn = trial % 3 == 0 ? null + : grid.warpColumn(warpOwner, WORLD_HEIGHT + 1, latticeX, latticeZ); + for (int pass = 0; pass < 2; pass++) { + sampleLatticeColumn.invoke(carver, batch, warpColumn, x, z, count); + for (int index = 0; index < count; index++) { + int y = batch.latticeY[index]; + double expected = (double) samplePlaneDensity.invoke(carver, reference, x, y, z); + assertEquals("x=" + x + " y=" + y + " z=" + z + " pass=" + pass, + Double.doubleToRawLongBits(expected), Double.doubleToRawLongBits(batch.latticeDensity[index])); + } + } + } + } + } + + @Test + public void identicalFieldsShareGridOwnersAcrossProfiles() throws Exception { + Engine engine = createEngine(); + CaveDensityGrid grid = new CaveDensityGrid(256); + Field densityOwnerField = IrisCaveCarver3D.class.getDeclaredField("densityOwner"); + densityOwnerField.setAccessible(true); + Field warpOwnerField = IrisCaveCarver3D.class.getDeclaredField("warpOwner"); + warpOwnerField.setAccessible(true); + IrisCaveCarver3D first = new IrisCaveCarver3D(engine, profile(NoiseStyle.FRACTAL_WATER, 0.44D, 3.2D, + NoiseStyle.PERLIN_IRIS, 0.72D, module(NoiseStyle.SIMPLEX_VASCULAR, 1.02D, 4D, 90D, true)), grid); + IrisCaveCarver3D sameDensity = new IrisCaveCarver3D(engine, profile(NoiseStyle.FRACTAL_WATER, 0.44D, 3.2D, + NoiseStyle.PERLIN_IRIS, 0.72D, module(NoiseStyle.SIMPLEX_VASCULAR, 1.02D, 4D, 90D, true)) + .setDensityThreshold(new IrisStyledRange(0.2D, 0.2D, new IrisGeneratorStyle(NoiseStyle.FLAT))), grid); + IrisCaveCarver3D sameWarp = new IrisCaveCarver3D(engine, profile(NoiseStyle.FRACTAL_WATER, 0.44D, 1.7D, + NoiseStyle.FRACTAL_BILLOW_SIMPLEX, 0.656D), grid); + IrisCaveCarver3D otherWarp = new IrisCaveCarver3D(engine, profile(NoiseStyle.FRACTAL_WATER, 0.46D, 3.2D, + NoiseStyle.PERLIN_IRIS, 0.72D, module(NoiseStyle.SIMPLEX_VASCULAR, 1.02D, 4D, 90D, true)), grid); + IrisCaveCarver3D otherModule = new IrisCaveCarver3D(engine, profile(NoiseStyle.FRACTAL_WATER, 0.44D, 3.2D, + NoiseStyle.PERLIN_IRIS, 0.72D, module(NoiseStyle.SIMPLEX_VASCULAR, 1.02D, 4D, 91D, true)), grid); + assertSame(densityOwnerField.get(first), densityOwnerField.get(sameDensity)); + assertSame(warpOwnerField.get(first), warpOwnerField.get(sameDensity)); + assertSame(warpOwnerField.get(first), warpOwnerField.get(sameWarp)); + assertSame(warpOwnerField.get(first), warpOwnerField.get(otherModule)); + assertNotSame(densityOwnerField.get(first), densityOwnerField.get(sameWarp)); + assertNotSame(densityOwnerField.get(first), densityOwnerField.get(otherModule)); + assertNotSame(densityOwnerField.get(first), densityOwnerField.get(otherWarp)); + assertNotSame(warpOwnerField.get(first), warpOwnerField.get(otherWarp)); + } + + private static int selectRows(Random random, CaveCarveScratch scratch) { + int start = random.nextInt(40); + int end = start + 20 + random.nextInt(WORLD_HEIGHT - start - 20); + scratch.ensureLatticeBatch(end - start + 1); + int count = 0; + for (int y = start; y <= end; y++) { + if (random.nextInt(5) != 0) { + scratch.latticeY[count++] = y; + } + } + return count; + } + + private static IrisCaveProfile profile(NoiseStyle warp, double warpZoom, double warpStrength, NoiseStyle base, + double baseZoom, IrisCaveFieldModule... modules) { + IrisCaveProfile profile = new IrisCaveProfile(); + profile.setEnabled(true); + profile.setVerticalRange(new IrisRange(4D, 360D)); + profile.setWarpStyle(new IrisGeneratorStyle(warp).zoomed(warpZoom)); + profile.setWarpStrength(warpStrength); + profile.setBaseDensityStyle(new IrisGeneratorStyle(base).zoomed(baseZoom)); + profile.setDetailDensityStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.5D)); + profile.setSurfaceBreakStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.82D)); + profile.setBaseWeight(0.82D); + profile.setDetailWeight(0.15D); + profile.setDensityThreshold(new IrisStyledRange(-0.1D, 0.02D, new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.78D))); + profile.setSampleStep(2); + profile.setModules(new KList<>(modules)); + return profile; + } + + private static IrisCaveFieldModule module(NoiseStyle style, double zoom, double minY, double maxY, boolean invert) { + return new IrisCaveFieldModule(new IrisGeneratorStyle(style).zoomed(zoom), 0.07D, 0.04D, new IrisRange(minY, maxY), invert); + } + + private static Engine createEngine() { + Engine engine = mock(Engine.class, withSettings().stubOnly()); + IrisData data = mock(IrisData.class, withSettings().stubOnly()); + IrisDimension dimension = mock(IrisDimension.class, withSettings().stubOnly()); + SeedManager seedManager = mock(SeedManager.class, withSettings().stubOnly()); + doReturn(data).when(engine).getData(); + doReturn(dimension).when(engine).getDimension(); + doReturn(seedManager).when(engine).getSeedManager(); + doReturn(CARVE_SEED).when(seedManager).getCarve(); + doReturn(new EngineMetrics(16)).when(engine).getMetrics(); + doReturn(IrisWorld.builder().minHeight(-64).maxHeight(WORLD_HEIGHT - 64).build()).when(engine).getWorld(); + doReturn(WORLD_HEIGHT).when(engine).getHeight(); + doReturn(200).when(engine).getHeight(anyInt(), anyInt()); + doReturn(20).when(dimension).getCaveLavaHeight(); + doReturn(64).when(dimension).getFluidHeight(); + doReturn(1).when(dimension).getCaveDensitySamplingStep(); + return engine; + } +} diff --git a/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DNearParityTest.java b/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DNearParityTest.java index 28edd359d..cbaf46887 100644 --- a/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DNearParityTest.java +++ b/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DNearParityTest.java @@ -370,8 +370,8 @@ public class IrisCaveCarver3DNearParityTest { fullWeights(), 0D, 0D, null, filledHeights(110))); } else { carver.carve(capture.writer, 0, 0, fullWeights(), 0D, 0D, null, filledHeights(110)); + assertFalse(capture.carvedCells.isEmpty()); } - assertFalse(capture.carvedCells.isEmpty()); Field scratchField = IrisCaveCarver3D.class.getDeclaredField("scratchCache"); scratchField.setAccessible(true); ThreadLocal scratchCache = (ThreadLocal) scratchField.get(carver); @@ -386,6 +386,12 @@ public class IrisCaveCarver3DNearParityTest { for (CaveDensityGrid.Column column : scratch.densityColumns) { assertNull(column); } + for (CaveDensityGrid.Column column : scratch.cellLattice.densityColumns) { + assertNull(column); + } + for (CaveDensityGrid.Column column : scratch.cellLattice.warpColumns) { + assertNull(column); + } } } @@ -1269,7 +1275,7 @@ public class IrisCaveCarver3DNearParityTest { doAnswer(invocation -> { lookups.incrementAndGet(); return invocation.callRealMethod(); - }).when(grid).column(any(), anyInt(), anyInt(), anyInt(), anyInt()); + }).when(grid).column(any(), anyInt(), anyInt(), anyInt()); return grid; } From b9409fb5ec2ce8d7cf7e01143002feb62d500c25 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Thu, 22 Oct 2026 00:20:25 -0400 Subject: [PATCH 4/4] Smaller warp column cache - Warp lattice columns keep a quarter of the density column capacity --- .../java/art/arcane/iris/generation/mantle/CaveDensityGrid.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/art/arcane/iris/generation/mantle/CaveDensityGrid.java b/core/src/main/java/art/arcane/iris/generation/mantle/CaveDensityGrid.java index 17a40ba85..eb49c899c 100644 --- a/core/src/main/java/art/arcane/iris/generation/mantle/CaveDensityGrid.java +++ b/core/src/main/java/art/arcane/iris/generation/mantle/CaveDensityGrid.java @@ -28,7 +28,7 @@ final class CaveDensityGrid { CaveDensityGrid(int capacity) { columns = new ConcurrentClockCache<>(capacity); - warpColumns = new ConcurrentClockCache<>(capacity); + warpColumns = new ConcurrentClockCache<>(Math.max(1, capacity >> 2)); } Owner owner(Object fieldKey) {