Probe fidelity options

- Print measured-window stage totals from EngineMetrics as IRIS_GENPROBE_STAGES
- Time each context prefill task into its prefill timer
- Add gated generation counters for generator evaluations, corner starcasts and hydrology lookups
- Add -PprobeHydrologyPreplan to plan the probe area's hydrology tiles before warmup
- Add -PprobeHistoryRouter to generate through a generation-history router and registry contract
- Add -PprobeProductionBuffers to generate into production-equivalent block and biome hunks
- Print the mantle radius as IRIS_GENPROBE_MANTLE
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-21 19:20:17 -04:00
1 parent e2430500cf
commit b6b01c88f5
15 files changed
+1020 -113

No files matched your search

@@ -97,22 +97,23 @@ public class ChunkContext {
}
List<Runnable> fillTasks = new ArrayList<>(6);
if (resolvedPlan.height) {
fillTasks.add(() -> height.fillRounded(roundedHeight));
fillTasks.add(new PrefillFillTask(() -> height.fillRounded(roundedHeight),
capturePrefillMetric ? metrics.getContextPrefillHeight() : null));
}
if (resolvedPlan.biome) {
fillTasks.add(new PrefillFillTask(biome));
fillTasks.add(new PrefillFillTask(biome::fill, capturePrefillMetric ? metrics.getContextPrefillBiome() : null));
}
if (resolvedPlan.rock) {
fillTasks.add(new PrefillFillTask(rock));
fillTasks.add(new PrefillFillTask(rock::fill, capturePrefillMetric ? metrics.getContextPrefillRock() : null));
}
if (resolvedPlan.fluid) {
fillTasks.add(new PrefillFillTask(fluid));
fillTasks.add(new PrefillFillTask(fluid::fill, capturePrefillMetric ? metrics.getContextPrefillFluid() : null));
}
if (resolvedPlan.region) {
fillTasks.add(new PrefillFillTask(region));
fillTasks.add(new PrefillFillTask(region::fill, capturePrefillMetric ? metrics.getContextPrefillRegion() : null));
}
if (resolvedPlan.cave) {
fillTasks.add(new PrefillFillTask(cave));
fillTasks.add(new PrefillFillTask(cave::fill, capturePrefillMetric ? metrics.getContextPrefillCave() : null));
}
if (!shouldPrefillAsync(resolvedPlan, fillTasks.size())) {
@@ -297,15 +298,23 @@ public class ChunkContext {
}
private static final class PrefillFillTask implements Runnable {
private final ChunkedDataCache<?> dataCache;
private final Runnable fill;
private final EngineMetrics.StageTimer timer;
private PrefillFillTask(ChunkedDataCache<?> dataCache) {
this.dataCache = dataCache;
private PrefillFillTask(Runnable fill, EngineMetrics.StageTimer timer) {
this.fill = fill;
this.timer = timer;
}
@Override
public void run() {
dataCache.fill();
if (timer == null) {
fill.run();
return;
}
long startedNanos = System.nanoTime();
fill.run();
timer.put((System.nanoTime() - startedNanos) / 1_000_000D);
}
}
}
@@ -1,6 +1,7 @@
package art.arcane.iris.generation.hydrology;
import art.arcane.iris.generation.runtime.GenerationClosedException;
import art.arcane.iris.generation.runtime.GenerationCounter;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.generation.concurrent.MultiBurst;
import art.arcane.iris.generation.stream.ProvisionalSampling;
@@ -499,6 +500,9 @@ public final class HydrologyTileCache implements AutoCloseable {
}
public Optional<HydrologyColumnSample> columnAt(int blockX, int blockZ) {
if (GenerationCounter.ENABLED) {
GenerationCounter.HYDROLOGY_LOOKUPS.increment();
}
return chunkColumns(blockX, blockZ).columnAt(blockX, blockZ);
}
@@ -507,6 +511,9 @@ public final class HydrologyTileCache implements AutoCloseable {
if (!waitsForbidden()) {
return HydrologyColumnSnapshot.ready(columnAt(blockX, blockZ).orElse(null));
}
if (GenerationCounter.ENABLED) {
GenerationCounter.HYDROLOGY_LOOKUPS.increment();
}
int chunkX = Math.floorDiv(blockX, CHUNK_SIZE);
int chunkZ = Math.floorDiv(blockZ, CHUNK_SIZE);
ChunkColumns local = localChunks(cacheEpoch.get()).get(chunkX, chunkZ);
@@ -24,63 +24,65 @@ import lombok.Data;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.atomic.DoubleAdder;
import java.util.concurrent.atomic.LongAdder;
@Data
public class EngineMetrics {
private final AtomicRollingSequence total;
private final AtomicRollingSequence updates;
private final AtomicRollingSequence terrain;
private final AtomicRollingSequence biome;
private final AtomicRollingSequence parallax;
private final AtomicRollingSequence parallaxInsert;
private final AtomicRollingSequence post;
private final AtomicRollingSequence perfection;
private final AtomicRollingSequence api;
private final AtomicRollingSequence decoration;
private final AtomicRollingSequence cave;
private final AtomicRollingSequence ravine;
private final AtomicRollingSequence deposit;
private final AtomicRollingSequence carveResolve;
private final AtomicRollingSequence carveApply;
private final AtomicRollingSequence noiseHeightInterpolate;
private final AtomicRollingSequence noiseHeightGenerator;
private final AtomicRollingSequence contextPrefill;
private final AtomicRollingSequence contextPrefillHeight;
private final AtomicRollingSequence contextPrefillBiome;
private final AtomicRollingSequence contextPrefillRock;
private final AtomicRollingSequence contextPrefillFluid;
private final AtomicRollingSequence contextPrefillRegion;
private final AtomicRollingSequence contextPrefillCave;
private final AtomicRollingSequence pregenWaitPermit;
private final AtomicRollingSequence pregenWaitAdaptive;
private final StageTimer total;
private final StageTimer updates;
private final StageTimer terrain;
private final StageTimer biome;
private final StageTimer parallax;
private final StageTimer parallaxInsert;
private final StageTimer post;
private final StageTimer perfection;
private final StageTimer api;
private final StageTimer decoration;
private final StageTimer cave;
private final StageTimer ravine;
private final StageTimer deposit;
private final StageTimer carveResolve;
private final StageTimer carveApply;
private final StageTimer noiseHeightInterpolate;
private final StageTimer noiseHeightGenerator;
private final StageTimer contextPrefill;
private final StageTimer contextPrefillHeight;
private final StageTimer contextPrefillBiome;
private final StageTimer contextPrefillRock;
private final StageTimer contextPrefillFluid;
private final StageTimer contextPrefillRegion;
private final StageTimer contextPrefillCave;
private final StageTimer pregenWaitPermit;
private final StageTimer pregenWaitAdaptive;
public EngineMetrics(int mem) {
this.total = new AtomicRollingSequence(mem);
this.terrain = new AtomicRollingSequence(mem);
this.api = new AtomicRollingSequence(mem);
this.biome = new AtomicRollingSequence(mem);
this.perfection = new AtomicRollingSequence(mem);
this.parallax = new AtomicRollingSequence(mem);
this.parallaxInsert = new AtomicRollingSequence(mem);
this.post = new AtomicRollingSequence(mem);
this.decoration = new AtomicRollingSequence(mem);
this.updates = new AtomicRollingSequence(mem);
this.cave = new AtomicRollingSequence(mem);
this.ravine = new AtomicRollingSequence(mem);
this.deposit = new AtomicRollingSequence(mem);
this.carveResolve = new AtomicRollingSequence(mem);
this.carveApply = new AtomicRollingSequence(mem);
this.noiseHeightInterpolate = new AtomicRollingSequence(mem);
this.noiseHeightGenerator = new AtomicRollingSequence(mem);
this.contextPrefill = new AtomicRollingSequence(mem);
this.contextPrefillHeight = new AtomicRollingSequence(mem);
this.contextPrefillBiome = new AtomicRollingSequence(mem);
this.contextPrefillRock = new AtomicRollingSequence(mem);
this.contextPrefillFluid = new AtomicRollingSequence(mem);
this.contextPrefillRegion = new AtomicRollingSequence(mem);
this.contextPrefillCave = new AtomicRollingSequence(mem);
this.pregenWaitPermit = new AtomicRollingSequence(mem);
this.pregenWaitAdaptive = new AtomicRollingSequence(mem);
this.total = new StageTimer(mem);
this.terrain = new StageTimer(mem);
this.api = new StageTimer(mem);
this.biome = new StageTimer(mem);
this.perfection = new StageTimer(mem);
this.parallax = new StageTimer(mem);
this.parallaxInsert = new StageTimer(mem);
this.post = new StageTimer(mem);
this.decoration = new StageTimer(mem);
this.updates = new StageTimer(mem);
this.cave = new StageTimer(mem);
this.ravine = new StageTimer(mem);
this.deposit = new StageTimer(mem);
this.carveResolve = new StageTimer(mem);
this.carveApply = new StageTimer(mem);
this.noiseHeightInterpolate = new StageTimer(mem);
this.noiseHeightGenerator = new StageTimer(mem);
this.contextPrefill = new StageTimer(mem);
this.contextPrefillHeight = new StageTimer(mem);
this.contextPrefillBiome = new StageTimer(mem);
this.contextPrefillRock = new StageTimer(mem);
this.contextPrefillFluid = new StageTimer(mem);
this.contextPrefillRegion = new StageTimer(mem);
this.contextPrefillCave = new StageTimer(mem);
this.pregenWaitPermit = new StageTimer(mem);
this.pregenWaitAdaptive = new StageTimer(mem);
}
public KMap<String, Double> pull() {
@@ -134,4 +136,61 @@ public class EngineMetrics {
averages.entrySet().removeIf(entry -> !Double.isFinite(entry.getValue()) || entry.getValue() < 0D);
return Map.copyOf(averages);
}
public Map<String, StageTotal> stageTotals() {
Map<String, StageTotal> totals = new LinkedHashMap<>();
totals.put("total", total.snapshot());
totals.put("terrain", terrain.snapshot());
totals.put("biome", biome.snapshot());
totals.put("parallax", parallax.snapshot());
totals.put("parallax.insert", parallaxInsert.snapshot());
totals.put("post", post.snapshot());
totals.put("perfection", perfection.snapshot());
totals.put("decoration", decoration.snapshot());
totals.put("api", api.snapshot());
totals.put("updates", updates.snapshot());
totals.put("cave", cave.snapshot());
totals.put("ravine", ravine.snapshot());
totals.put("deposit", deposit.snapshot());
totals.put("carve.resolve", carveResolve.snapshot());
totals.put("carve.apply", carveApply.snapshot());
totals.put("noise.height.interpolate", noiseHeightInterpolate.snapshot());
totals.put("noise.height.generator", noiseHeightGenerator.snapshot());
totals.put("context.prefill", contextPrefill.snapshot());
totals.put("context.prefill.height", contextPrefillHeight.snapshot());
totals.put("context.prefill.biome", contextPrefillBiome.snapshot());
totals.put("context.prefill.rock", contextPrefillRock.snapshot());
totals.put("context.prefill.fluid", contextPrefillFluid.snapshot());
totals.put("context.prefill.region", contextPrefillRegion.snapshot());
totals.put("context.prefill.cave", contextPrefillCave.snapshot());
totals.put("pregen.wait.permit", pregenWaitPermit.snapshot());
totals.put("pregen.wait.adaptive", pregenWaitAdaptive.snapshot());
return totals;
}
public static final class StageTimer extends AtomicRollingSequence {
private final DoubleAdder totalMillis = new DoubleAdder();
private final LongAdder samples = new LongAdder();
public StageTimer(int size) {
super(size);
}
@Override
public void put(double millis) {
super.put(millis);
totalMillis.add(millis);
samples.increment();
}
public StageTotal snapshot() {
return new StageTotal(totalMillis.sum(), samples.sum());
}
}
public record StageTotal(double millis, long samples) {
public StageTotal since(StageTotal earlier) {
return new StageTotal(millis - earlier.millis(), samples - earlier.samples());
}
}
}
@@ -0,0 +1,34 @@
package art.arcane.iris.generation.runtime;
import java.util.concurrent.atomic.LongAdder;
public enum GenerationCounter {
GENERATOR_EVALUATIONS("generator_evaluations"),
CORNER_STARCASTS("corner_starcasts"),
HYDROLOGY_LOOKUPS("hydrology_lookups");
public static final boolean ENABLED = Boolean.getBoolean("iris.generation.counters");
private final String key;
private final LongAdder count = new LongAdder();
GenerationCounter(String key) {
this.key = key;
}
public String key() {
return key;
}
public void add(long amount) {
count.add(amount);
}
public void increment() {
count.increment();
}
public long sum() {
return count.sum();
}
}
@@ -867,6 +867,9 @@ public class IrisComplex implements DataProvider {
break;
}
generator.getHeights(activeXs, activeZs, seed, noise, activeCount);
if (GenerationCounter.ENABLED) {
GenerationCounter.GENERATOR_EVALUATIONS.add(activeCount);
}
for (int sample = 0; sample < activeCount; sample++) {
int index = active[sample];
if (!Double.isFinite(noise[sample])) {
@@ -1838,6 +1841,9 @@ public class IrisComplex implements DataProvider {
long sharedKey = SharedCornerBounds.key(gx, gz);
long packed = shared.get(sharedKey, interpolatorIndex);
if (packed == Long.MIN_VALUE) {
if (GenerationCounter.ENABLED) {
GenerationCounter.CORNER_STARCASTS.increment();
}
packed = computePackedBounds(cache, engine, interpolator, generators, gx, gz);
if (!Double.isFinite(boundsLow(packed)) || !Double.isFinite(boundsHigh(packed))) {
// A non-finite corner is a transient sampling fault, not a fact about the terrain:
@@ -2080,6 +2086,9 @@ public class IrisComplex implements DataProvider {
}
return height / generators.length;
}
if (GenerationCounter.ENABLED) {
GenerationCounter.GENERATOR_EVALUATIONS.add(generators.length);
}
for (IrisGenerator generator : generators) {
double noise = generator.getHeight(x, z, seed);
if (!Double.isFinite(noise)) {