This commit is contained in:
Brian Neumann-Fopiano
2026-08-15 12:04:28 -04:00
parent 57072d5bcd
commit 59f33bd921
4 changed files with 500 additions and 133 deletions
+21 -7
View File
@@ -49,12 +49,12 @@ tasks.named('test').configure {
dependsOn(':core:compileJava')
}
String probePack = providers.gradleProperty('probePack')
.orElse('/Users/brianfopiano/Developer/RemoteGit/[Minecraft Server]/consumers/plugin-consumers/instances/purpur-26.2/plugins/Iris/packs/overworld')
.get()
String probeRadius = providers.gradleProperty('probeRadius').orElse('2').get()
String probeCenterChunkX = providers.gradleProperty('probeCenterChunkX').orElse('0').get()
String probeCenterChunkZ = providers.gradleProperty('probeCenterChunkZ').orElse('0').get()
Provider<String> probePack = providers.gradleProperty('probePack')
Provider<String> probeDimension = providers.gradleProperty('probeDimension')
Provider<String> probeWarmupChunks = providers.gradleProperty('probeWarmupChunks').orElse('256')
Provider<String> probeMeasuredChunks = providers.gradleProperty('probeMeasuredChunks').orElse('1024')
Provider<String> probeCenterChunkX = providers.gradleProperty('probeCenterChunkX').orElse('2048')
Provider<String> probeCenterChunkZ = providers.gradleProperty('probeCenterChunkZ').orElse('2048')
tasks.register('genProbe', JavaExec) {
group = 'verification'
@@ -62,8 +62,22 @@ tasks.register('genProbe', JavaExec) {
mainClass = 'art.arcane.iris.probe.GenerationProbe'
classpath = sourceSets.main.runtimeClasspath
jvmArgs('--add-modules', 'jdk.incubator.vector')
args(probePack, probeRadius, probeCenterChunkX, probeCenterChunkZ)
dependsOn(':core:compileJava')
doFirst {
if (!probePack.isPresent() || probePack.get().isBlank()) {
throw new GradleException('genProbe requires -PprobePack=/absolute/path/to/pack')
}
if (!probeDimension.isPresent() || probeDimension.get().isBlank()) {
throw new GradleException('genProbe requires -PprobeDimension=<dimension-key>')
}
args(
probePack.get(),
probeDimension.get(),
probeWarmupChunks.get(),
probeMeasuredChunks.get(),
probeCenterChunkX.get(),
probeCenterChunkZ.get())
}
}
String deserializationPack = providers.gradleProperty('deserializationPack')
@@ -43,20 +43,23 @@ import art.arcane.iris.util.project.hunk.Hunk;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HexFormat;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.stream.Stream;
public final class GenerationProbe {
private static final String DIMENSION_KEY = "overworld";
private static final long SEED = 1337L;
private static final int BIOME_STEP = 4;
private static final int SIGNATURE_SAMPLE_STEP = 8;
private static final List<Throwable> REPORTED = Collections.synchronizedList(new ArrayList<>());
private static final class InertPreservation implements PreservationRegistry {
@@ -123,57 +126,173 @@ public final class GenerationProbe {
private static final class InertPlatformHooks implements EnginePlatformHooks {
}
public static void main(String[] args) throws Exception {
StubPlatform.bindGenerationStateHandlers();
StubPlatform.verbose(true);
StubPlatform.errorSink(REPORTED::add);
IrisServices.register(PreservationRegistry.class, new InertPreservation());
IrisServices.register(EngineWorldManagerProvider.class, (EngineWorldManagerProvider) (Engine engine) -> new InertWorldManager());
IrisServices.register(EngineEffectsProvider.class, (EngineEffectsProvider) InertEffects::new);
IrisServices.register(EnginePlatformHooks.class, new InertPlatformHooks());
File packSource = new File(args[0]);
int radius = args.length > 1 ? Integer.parseInt(args[1]) : 2;
int centerChunkX = args.length > 2 ? Integer.parseInt(args[2]) : 0;
int centerChunkZ = args.length > 3 ? Integer.parseInt(args[3]) : 0;
if (!packSource.isDirectory()) {
System.out.println("[genprobe] pack folder not found: " + packSource.getAbsolutePath());
System.exit(1);
record ProbeConfiguration(File packSource, String dimensionKey, int warmupChunks, int measuredChunks,
int centerChunkX, int centerChunkZ) {
ProbeConfiguration {
if (packSource == null) {
throw new IllegalArgumentException("Pack folder is required.");
}
if (dimensionKey == null || dimensionKey.isBlank() || dimensionKey.chars().anyMatch(Character::isWhitespace)) {
throw new IllegalArgumentException("Dimension key must be non-blank and contain no whitespace.");
}
if (warmupChunks < 1) {
throw new IllegalArgumentException("Warmup chunk count must be at least 1.");
}
if (measuredChunks < 1) {
throw new IllegalArgumentException("Measured chunk count must be at least 1.");
}
}
File workRoot = Files.createTempDirectory("iris-genprobe").toFile();
File pack = clonePack(packSource, workRoot);
System.out.println("[genprobe] pack: " + packSource.getAbsolutePath());
System.out.println("[genprobe] work copy: " + pack.getAbsolutePath());
System.out.println("[genprobe] center chunk: " + centerChunkX + "," + centerChunkZ);
System.out.println("[genprobe] radius: " + radius + " (" + ((2 * radius + 1) * (2 * radius + 1)) + " chunks)");
PackValidationResult validation = PackValidator.validate(pack);
for (String warning : validation.getWarnings()) {
System.out.println("[genprobe] pack warning: " + warning);
static ProbeConfiguration parse(String[] args) {
if (args.length != 6) {
throw new IllegalArgumentException("Expected: <pack> <dimension> <warmupChunks> <measuredChunks> <centerChunkX> <centerChunkZ>");
}
if (!validation.isLoadable()) {
for (String error : validation.getBlockingErrors()) {
System.out.println("[genprobe] pack error: " + error);
return new ProbeConfiguration(
new File(args[0]),
args[1],
Integer.parseInt(args[2]),
Integer.parseInt(args[3]),
Integer.parseInt(args[4]),
Integer.parseInt(args[5]));
}
System.out.println("[genprobe] FAIL: pack validation blocked generation");
System.exit(1);
return;
}
System.out.println("[genprobe] pack validation: PASS");
IrisPlatforms.bind(new StubPlatform());
record ChunkCoordinate(int x, int z) {
}
Engine engine;
record TimingSummary(long medianNanos, long p95Nanos, long maxNanos, long totalNanos) {
static TimingSummary from(List<Long> samples) {
if (samples.isEmpty()) {
throw new IllegalArgumentException("At least one timing sample is required.");
}
List<Long> sorted = new ArrayList<>(samples);
sorted.sort(Comparator.naturalOrder());
int size = sorted.size();
long median;
if ((size & 1) == 0) {
long lower = sorted.get((size / 2) - 1);
long upper = sorted.get(size / 2);
median = lower + ((upper - lower) / 2L);
} else {
median = sorted.get(size / 2);
}
int p95Index = Math.max(0, (int) Math.ceil(size * 0.95D) - 1);
long total = 0L;
for (long sample : samples) {
total += sample;
}
return new TimingSummary(median, sorted.get(p95Index), sorted.get(size - 1), total);
}
}
record GenerationResult(int successfulChunks, int failedChunks, long firstChunkNanos,
TimingSummary measuredTimings, String signature) {
}
record ProbeResult(String status, String dimensionKey, int warmupChunks, int measuredChunks,
int successfulChunks, int failedChunks, long engineReadyNanos, long firstChunkNanos,
TimingSummary measuredTimings, String signature) {
String machineLine() {
double measuredSeconds = measuredTimings.totalNanos() / 1_000_000_000D;
double chunksPerSecond = measuredSeconds == 0D ? 0D : measuredChunks / measuredSeconds;
return String.format(Locale.ROOT,
"IRIS_GENPROBE_RESULT version=1 status=%s dimension=%s warmup_chunks=%d measured_chunks=%d successful_chunks=%d failed_chunks=%d engine_ready_ms=%.3f first_chunk_ms=%.3f measured_median_ms=%.3f measured_p95_ms=%.3f measured_max_ms=%.3f measured_total_ms=%.3f measured_cps=%.3f signature=%s",
status,
dimensionKey,
warmupChunks,
measuredChunks,
successfulChunks,
failedChunks,
nanosToMillis(engineReadyNanos),
nanosToMillis(firstChunkNanos),
nanosToMillis(measuredTimings.medianNanos()),
nanosToMillis(measuredTimings.p95Nanos()),
nanosToMillis(measuredTimings.maxNanos()),
nanosToMillis(measuredTimings.totalNanos()),
chunksPerSecond,
signature);
}
}
public static void main(String[] args) {
ProbeConfiguration configuration;
try {
IrisData data = IrisData.get(pack);
IrisDimension dimension = data.getDimensionLoader().load(DIMENSION_KEY);
if (dimension == null) {
System.out.println("[genprobe] FAIL: dimension '" + DIMENSION_KEY + "' did not load from " + pack.getAbsolutePath());
System.exit(1);
configuration = ProbeConfiguration.parse(args);
} catch (Throwable e) {
System.out.println("[genprobe] FAIL: " + e.getMessage());
e.printStackTrace(System.out);
System.exit(2);
return;
}
int exitCode;
try {
ProbeResult result = run(configuration);
System.out.println(result.machineLine());
exitCode = result.failedChunks() == 0 ? 0 : 1;
} catch (Throwable e) {
System.out.println("[genprobe] FAIL: probe execution failed");
e.printStackTrace(System.out);
TimingSummary unavailable = new TimingSummary(0L, 0L, 0L, 0L);
ProbeResult result = new ProbeResult(
"FAIL",
configuration.dimensionKey(),
configuration.warmupChunks(),
configuration.measuredChunks(),
0,
configuration.warmupChunks() + configuration.measuredChunks(),
0L,
0L,
unavailable,
"unavailable");
System.out.println(result.machineLine());
exitCode = 1;
}
System.exit(exitCode);
}
static List<ChunkCoordinate> scheduleCoordinates(int count, int centerChunkX, int centerChunkZ) {
if (count < 1) {
throw new IllegalArgumentException("Chunk count must be at least 1.");
}
int width = (int) Math.ceil(Math.sqrt(count));
int height = (count + width - 1) / width;
int startX = centerChunkX - (width / 2);
int startZ = centerChunkZ - (height / 2);
List<ChunkCoordinate> coordinates = new ArrayList<>(count);
for (int index = 0; index < count; index++) {
coordinates.add(new ChunkCoordinate(startX + (index % width), startZ + (index / width)));
}
return coordinates;
}
private static ProbeResult run(ProbeConfiguration configuration) throws Throwable {
if (!configuration.packSource().isDirectory()) {
throw new IllegalArgumentException("Pack folder not found: " + configuration.packSource().getAbsolutePath());
}
File workRoot = Files.createTempDirectory("iris-genprobe-").toFile();
IrisData data = null;
Engine engine = null;
Throwable executionFailure = null;
try {
File pack = clonePack(configuration.packSource(), workRoot);
configureProbeRuntime(workRoot);
validatePack(pack);
System.out.println("[genprobe] pack: " + configuration.packSource().getAbsolutePath());
System.out.println("[genprobe] dimension: " + configuration.dimensionKey());
System.out.println("[genprobe] warmup chunks: " + configuration.warmupChunks());
System.out.println("[genprobe] measured chunks: " + configuration.measuredChunks());
System.out.println("[genprobe] center chunk: " + configuration.centerChunkX() + "," + configuration.centerChunkZ());
long engineStart = System.nanoTime();
data = IrisData.get(pack);
IrisDimension dimension = data.getDimensionLoader().load(configuration.dimensionKey());
if (dimension == null) {
throw new IllegalStateException("Dimension '" + configuration.dimensionKey()
+ "' did not load from " + pack.getAbsolutePath());
}
IrisWorld world = IrisWorld.builder()
.platformIdentity("iris:probe")
.name("probe")
@@ -184,70 +303,188 @@ public final class GenerationProbe {
.build();
EngineTarget target = new EngineTarget(world, dimension, data);
engine = new IrisEngine(target, IrisEngine.InitializationMode.RUNTIME);
} catch (Throwable e) {
System.out.println("[genprobe] FAIL: engine construction threw before generation could start");
e.printStackTrace(System.out);
printDistinctCauses("construction-time reported errors", drainReported());
System.exit(1);
return;
}
long engineReadyNanos = System.nanoTime() - engineStart;
List<Throwable> initNoise = settleAndDrain();
printDistinctCauses("engine-init reported errors (non-fatal, async)", initNoise);
int height = engine.getTarget().getHeight();
System.out.println("[genprobe] engine up: dim=" + engine.getDimension().getLoadKey()
System.out.println("[genprobe] engine ready: dim=" + engine.getDimension().getLoadKey()
+ " seed=" + engine.getSeedManager().getSeed()
+ " minY=" + engine.getMinHeight() + " maxY=" + engine.getMaxHeight());
+ " minY=" + engine.getMinHeight() + " maxY=" + engine.getMaxHeight()
+ " timeMs=" + String.format(Locale.ROOT, "%.3f", nanosToMillis(engineReadyNanos)));
Map<String, Integer> distinct = new LinkedHashMap<>();
Map<String, String> firstChunk = new LinkedHashMap<>();
int ok = 0;
int failed = 0;
GenerationResult generation = generate(engine, configuration);
String status = generation.failedChunks() == 0 ? "PASS" : "FAIL";
printGenerationFailures(generation);
return new ProbeResult(
status,
configuration.dimensionKey(),
configuration.warmupChunks(),
configuration.measuredChunks(),
generation.successfulChunks(),
generation.failedChunks(),
engineReadyNanos,
generation.firstChunkNanos(),
generation.measuredTimings(),
generation.signature());
} catch (Throwable e) {
executionFailure = e;
throw e;
} finally {
Throwable cleanupFailure = closeProbe(engine, data, workRoot);
if (cleanupFailure != null) {
if (executionFailure != null) {
executionFailure.addSuppressed(cleanupFailure);
} else {
throw cleanupFailure;
}
}
}
}
for (int cz = centerChunkZ - radius; cz <= centerChunkZ + radius; cz++) {
for (int cx = centerChunkX - radius; cx <= centerChunkX + radius; cx++) {
String at = cx + "," + cz;
private static void configureProbeRuntime(File workRoot) {
REPORTED.clear();
StubPlatform.bindGenerationStateHandlers();
StubPlatform.verbose(false);
StubPlatform.errorSink(REPORTED::add);
IrisServices.register(PreservationRegistry.class, new InertPreservation());
IrisServices.register(EngineWorldManagerProvider.class,
(EngineWorldManagerProvider) (Engine engine) -> new InertWorldManager());
IrisServices.register(EngineEffectsProvider.class, (EngineEffectsProvider) InertEffects::new);
IrisServices.register(EnginePlatformHooks.class, new InertPlatformHooks());
IrisPlatforms.unbind();
IrisPlatforms.bind(new StubPlatform(new File(workRoot, "platform-data")));
}
private static void validatePack(File pack) {
PackValidationResult validation = PackValidator.validateForDatapackBootstrap(pack);
for (String warning : validation.getWarnings()) {
System.out.println("[genprobe] pack warning: " + warning);
}
if (!validation.isLoadable()) {
for (String error : validation.getBlockingErrors()) {
System.out.println("[genprobe] pack error: " + error);
}
throw new IllegalStateException("Pack validation blocked generation.");
}
System.out.println("[genprobe] offline pack validation: PASS");
}
private static GenerationResult generate(Engine engine, ProbeConfiguration configuration) {
int totalChunks = configuration.warmupChunks() + configuration.measuredChunks();
List<ChunkCoordinate> coordinates = scheduleCoordinates(
totalChunks, configuration.centerChunkX(), configuration.centerChunkZ());
List<Long> measuredTimings = new ArrayList<>(configuration.measuredChunks());
MessageDigest signature = sha256();
int successfulChunks = 0;
int failedChunks = 0;
long firstChunkNanos = 0L;
Map<String, Integer> distinctFailures = new LinkedHashMap<>();
Map<String, String> firstFailureChunk = new LinkedHashMap<>();
int height = engine.getTarget().getHeight();
for (int index = 0; index < totalChunks; index++) {
ChunkCoordinate coordinate = coordinates.get(index);
drainReported();
List<Throwable> failures = new ArrayList<>();
Hunk<PlatformBlockState> blocks = Hunk.newArrayHunk(16, height, 16);
Hunk<PlatformBiome> biomes = Hunk.newArrayHunk(16, height, 16);
long started = System.nanoTime();
try {
engine.generate(cx << 4, cz << 4, blocks, biomes, false);
engine.generate(coordinate.x() << 4, coordinate.z() << 4, blocks, biomes, false);
} catch (Throwable e) {
failures.add(e);
}
long elapsed = System.nanoTime() - started;
failures.addAll(drainReported());
if (index == 0) {
firstChunkNanos = elapsed;
}
if (index >= configuration.warmupChunks()) {
measuredTimings.add(elapsed);
}
if (failures.isEmpty()) {
ok++;
System.out.println("[genprobe] chunk " + at + " OK " + hashChunk(blocks, biomes, height));
successfulChunks++;
updateSignature(signature, coordinate, blocks, biomes, height);
} else {
failed++;
System.out.println("[genprobe] chunk " + at + " FAILED (" + failures.size() + " error(s))");
failedChunks++;
recordFailures(coordinate, failures, distinctFailures, firstFailureChunk);
}
int completed = index + 1;
if (completed == configuration.warmupChunks()) {
System.out.println("[genprobe] warmup complete: " + configuration.warmupChunks() + " chunks");
} else if (completed > configuration.warmupChunks()
&& (completed == totalChunks || (completed - configuration.warmupChunks()) % 128 == 0)) {
System.out.println("[genprobe] measured progress: "
+ (completed - configuration.warmupChunks()) + "/" + configuration.measuredChunks());
}
}
if (!distinctFailures.isEmpty()) {
System.out.println("[genprobe] DISTINCT ROOT CAUSES (" + distinctFailures.size() + "):");
for (Map.Entry<String, Integer> entry : distinctFailures.entrySet()) {
System.out.println(" x" + entry.getValue() + " (first at chunk "
+ firstFailureChunk.get(entry.getKey()) + ") " + entry.getKey());
}
}
return new GenerationResult(
successfulChunks,
failedChunks,
firstChunkNanos,
TimingSummary.from(measuredTimings),
HexFormat.of().formatHex(signature.digest()).substring(0, 16));
}
private static void recordFailures(ChunkCoordinate coordinate, List<Throwable> failures,
Map<String, Integer> distinctFailures,
Map<String, String> firstFailureChunk) {
String coordinateLabel = coordinate.x() + "," + coordinate.z();
System.out.println("[genprobe] chunk " + coordinateLabel + " FAILED (" + failures.size() + " error(s))");
for (Throwable failure : failures) {
failure.printStackTrace(System.out);
String key = causeKey(failure);
distinct.merge(key, 1, Integer::sum);
firstChunk.putIfAbsent(key, at);
distinctFailures.merge(key, 1, Integer::sum);
firstFailureChunk.putIfAbsent(key, coordinateLabel);
}
}
private static void printGenerationFailures(GenerationResult generation) {
System.out.println("[genprobe] generated OK: " + generation.successfulChunks()
+ ", failed: " + generation.failedChunks());
System.out.println("[genprobe] first chunk ms: "
+ String.format(Locale.ROOT, "%.3f", nanosToMillis(generation.firstChunkNanos())));
System.out.println("[genprobe] measured median/p95/max ms: "
+ String.format(Locale.ROOT, "%.3f/%.3f/%.3f",
nanosToMillis(generation.measuredTimings().medianNanos()),
nanosToMillis(generation.measuredTimings().p95Nanos()),
nanosToMillis(generation.measuredTimings().maxNanos())));
}
private static void updateSignature(MessageDigest digest, ChunkCoordinate coordinate,
Hunk<PlatformBlockState> blocks, Hunk<PlatformBiome> biomes, int height) {
updateDigest(digest, coordinate.x() + "," + coordinate.z());
int verticalStep = Math.max(1, height / 16);
for (int x = 0; x < 16; x += SIGNATURE_SAMPLE_STEP) {
for (int z = 0; z < 16; z += SIGNATURE_SAMPLE_STEP) {
for (int y = 0; y < height; y += verticalStep) {
PlatformBlockState state = blocks.get(x, y, z);
PlatformBiome biome = biomes.get(x, y, z);
updateDigest(digest, state == null ? "minecraft:air" : state.key());
updateDigest(digest, biome == null ? "null" : biome.key());
}
}
}
}
System.out.println("[genprobe] generated OK: " + ok + ", failed: " + failed);
if (!distinct.isEmpty()) {
System.out.println("[genprobe] DISTINCT ROOT CAUSES (" + distinct.size() + "):");
for (Map.Entry<String, Integer> entry : distinct.entrySet()) {
System.out.println(" x" + entry.getValue() + " (first at chunk " + firstChunk.get(entry.getKey()) + ") " + entry.getKey());
}
}
System.out.println("[genprobe] RESULT: " + (failed == 0 ? "PASS" : "FAIL"));
System.exit(failed == 0 ? 0 : 1);
private static void updateDigest(MessageDigest digest, String value) {
digest.update(value.getBytes(StandardCharsets.UTF_8));
digest.update((byte) 0);
}
private static File clonePack(File source, File workRoot) throws Exception {
File destination = new File(workRoot, source.getName());
File destination = new File(workRoot, "pack");
Process clone = new ProcessBuilder("cp", "-Rc", source.getAbsolutePath(), destination.getAbsolutePath())
.inheritIO()
.start();
@@ -262,6 +499,53 @@ public final class GenerationProbe {
return destination;
}
private static Throwable closeProbe(Engine engine, IrisData data, File workRoot) {
Throwable failure = null;
if (engine != null) {
try {
engine.close();
} catch (Throwable e) {
failure = e;
}
}
if (data != null) {
try {
data.close();
} catch (Throwable e) {
failure = appendFailure(failure, e);
}
}
try {
deleteRecursively(workRoot.toPath());
} catch (Throwable e) {
failure = appendFailure(failure, e);
}
return failure;
}
private static Throwable appendFailure(Throwable failure, Throwable additional) {
if (failure == null) {
return additional;
}
if (failure != additional) {
failure.addSuppressed(additional);
}
return failure;
}
private static void deleteRecursively(Path root) throws Exception {
if (!Files.exists(root)) {
return;
}
List<Path> paths;
try (Stream<Path> walk = Files.walk(root)) {
paths = walk.sorted(Comparator.reverseOrder()).toList();
}
for (Path path : paths) {
Files.deleteIfExists(path);
}
}
private static List<Throwable> settleAndDrain() throws InterruptedException {
List<Throwable> drained = new ArrayList<>();
long quietSince = System.currentTimeMillis();
@@ -330,31 +614,6 @@ public final class GenerationProbe {
return trace.length > 0 ? trace[0].toString() : "<no frames>";
}
private static String hashChunk(Hunk<PlatformBlockState> blocks, Hunk<PlatformBiome> biomes, int height) {
MessageDigest blockDigest = sha256();
MessageDigest biomeDigest = sha256();
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < height; y++) {
PlatformBlockState state = blocks.get(x, y, z);
String key = state == null ? "minecraft:air" : state.key();
blockDigest.update((key + "\n").getBytes(StandardCharsets.UTF_8));
}
}
}
for (int x = 0; x < 16; x += BIOME_STEP) {
for (int z = 0; z < 16; z += BIOME_STEP) {
for (int y = 0; y < height; y += BIOME_STEP) {
PlatformBiome biome = biomes.get(x, y, z);
String key = biome == null ? "null" : biome.key();
biomeDigest.update((key + "\n").getBytes(StandardCharsets.UTF_8));
}
}
}
return HexFormat.of().formatHex(blockDigest.digest()).substring(0, 16)
+ " " + HexFormat.of().formatHex(biomeDigest.digest()).substring(0, 16);
}
private static MessageDigest sha256() {
try {
return MessageDigest.getInstance("SHA-256");
@@ -362,4 +621,8 @@ public final class GenerationProbe {
throw new IllegalStateException(e);
}
}
private static double nanosToMillis(long nanos) {
return nanos / 1_000_000D;
}
}
@@ -78,6 +78,15 @@ public final class StubPlatform implements IrisPlatform {
private final StubScheduler scheduler = new StubScheduler();
private final StubStructureHooks structureHooks = new StubStructureHooks();
private final StubBiomeWriter biomeWriter = new StubBiomeWriter();
private final File dataFolder;
public StubPlatform() {
this(new File(System.getProperty("java.io.tmpdir"), "iris-probe"));
}
public StubPlatform(File dataFolder) {
this.dataFolder = dataFolder;
}
private static final class StubBlockState implements PlatformBlockState {
private static final ConcurrentHashMap<String, StubBlockState> CACHE = new ConcurrentHashMap<>();
@@ -649,7 +658,7 @@ public final class StubPlatform implements IrisPlatform {
@Override
public File dataFolder() {
return new File(System.getProperty("java.io.tmpdir"), "iris-probe");
return dataFolder;
}
@Override
@@ -0,0 +1,81 @@
package art.arcane.iris.probe;
import org.junit.Test;
import java.io.File;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public final class GenerationProbeTest {
@Test
public void parsesExplicitBenchmarkInputs() {
GenerationProbe.ProbeConfiguration configuration = GenerationProbe.ProbeConfiguration.parse(new String[]{
"/tmp/pack",
"underworld",
"256",
"1024",
"2048",
"-2048"
});
assertEquals(new File("/tmp/pack"), configuration.packSource());
assertEquals("underworld", configuration.dimensionKey());
assertEquals(256, configuration.warmupChunks());
assertEquals(1024, configuration.measuredChunks());
assertEquals(2048, configuration.centerChunkX());
assertEquals(-2048, configuration.centerChunkZ());
}
@Test
public void rejectsImplicitOrInvalidBenchmarkInputs() {
assertThrows(IllegalArgumentException.class,
() -> GenerationProbe.ProbeConfiguration.parse(new String[]{"/tmp/pack"}));
assertThrows(IllegalArgumentException.class,
() -> new GenerationProbe.ProbeConfiguration(new File("/tmp/pack"), " ", 1, 1, 0, 0));
assertThrows(IllegalArgumentException.class,
() -> new GenerationProbe.ProbeConfiguration(new File("/tmp/pack"), "overworld", 0, 1, 0, 0));
assertThrows(IllegalArgumentException.class,
() -> new GenerationProbe.ProbeConfiguration(new File("/tmp/pack"), "overworld", 1, 0, 0, 0));
}
@Test
public void schedulesTheExactNumberOfUniqueChunksAroundTheCenter() {
List<GenerationProbe.ChunkCoordinate> coordinates = GenerationProbe.scheduleCoordinates(1280, 2048, -2048);
Set<GenerationProbe.ChunkCoordinate> distinct = new HashSet<>(coordinates);
assertEquals(1280, coordinates.size());
assertEquals(1280, distinct.size());
assertTrue(coordinates.stream().allMatch(coordinate -> Math.abs(coordinate.x() - 2048) <= 18));
assertTrue(coordinates.stream().allMatch(coordinate -> Math.abs(coordinate.z() + 2048) <= 18));
}
@Test
public void reportsMedianNearestRankP95MaximumAndTotal() {
GenerationProbe.TimingSummary odd = GenerationProbe.TimingSummary.from(List.of(50L, 10L, 30L, 20L, 40L));
GenerationProbe.TimingSummary even = GenerationProbe.TimingSummary.from(List.of(40L, 10L, 30L, 20L));
assertEquals(30L, odd.medianNanos());
assertEquals(50L, odd.p95Nanos());
assertEquals(50L, odd.maxNanos());
assertEquals(150L, odd.totalNanos());
assertEquals(25L, even.medianNanos());
}
@Test
public void emitsAStableMachineReadableResultLine() {
GenerationProbe.TimingSummary timings = new GenerationProbe.TimingSummary(
10_000_000L, 20_000_000L, 30_000_000L, 40_000_000L);
GenerationProbe.ProbeResult result = new GenerationProbe.ProbeResult(
"PASS", "underworld", 2, 4, 6, 0,
5_000_000L, 6_000_000L, timings, "0123456789abcdef");
assertEquals(
"IRIS_GENPROBE_RESULT version=1 status=PASS dimension=underworld warmup_chunks=2 measured_chunks=4 successful_chunks=6 failed_chunks=0 engine_ready_ms=5.000 first_chunk_ms=6.000 measured_median_ms=10.000 measured_p95_ms=20.000 measured_max_ms=30.000 measured_total_ms=40.000 measured_cps=100.000 signature=0123456789abcdef",
result.machineLine());
}
}