Files
Iris/probe/build.gradle
T
Brian Neumann-Fopiano 3dc958edcf Stabilized shutdown and Studio
- Shutdown protection initializes before enable and drains inline when retention fails
- Cleanup preserves class loader retention and closes native providers in order
- Error reports retain context and handle empty stack traces
- Studio maps retain partial coverage and suspend hidden views
- Hydrology previews refresh when plans and rasters complete
- Terrain scratch hashing spreads lattice samples across cache slots
- Plugin and probe artifacts include LWJGL libraries and native resources
- Regression tests cover shutdown, error reporting, map progress, and terrain scratch
2026-10-26 23:29:21 -04:00

460 lines
22 KiB
Groovy

plugins {
id 'application'
}
group = 'art.arcane'
version = rootProject.version
application {
mainClass = 'art.arcane.iris.probe.ClassloadProbe'
}
configurations {
studioProbeApi
}
dependencies {
implementation(project(':core'))
compileOnly(libs.spigot)
testImplementation('junit:junit:4.13.2')
testImplementation(libs.mockito.core)
testImplementation(libs.gson)
testImplementation(testFixtures(project(':core')))
testImplementation('com.github.VolmitSoftware.VolmLib:volmlib-shared:master-SNAPSHOT') {
transitive = false
}
compileOnly('com.github.VolmitSoftware.VolmLib:volmlib-shared:master-SNAPSHOT') {
transitive = false
}
runtimeOnly(libs.paralithic)
runtimeOnly(libs.gson)
runtimeOnly(libs.lru)
runtimeOnly(libs.commons.io)
runtimeOnly(libs.commons.lang3)
runtimeOnly(libs.fastutil)
runtimeOnly(libs.caffeine)
runtimeOnly(libs.lz4)
runtimeOnly('com.fulcrumgenomics:jlibdeflate:0.2.0')
runtimeOnly(libs.zip)
runtimeOnly(libs.guava)
runtimeOnly(libs.oshi)
runtimeOnly(libs.byteBuddy.core)
runtimeOnly(libs.byteBuddy.agent)
runtimeOnly(libs.lwjgl)
runtimeOnly(libs.lwjgl.opengl)
runtimeOnly(libs.lwjgl.egl)
runtimeOnly(libs.lwjgl.jawt)
runtimeOnly(variantOf(libs.lwjgl) { classifier('natives-linux') })
runtimeOnly(variantOf(libs.lwjgl.opengl) { classifier('natives-linux') })
studioProbeApi(libs.spigot) {
transitive = false
}
}
tasks.named('run', JavaExec).configure {
args(project(':core').layout.buildDirectory.dir('classes/java/main').get().asFile.absolutePath)
jvmArgs('--add-modules', 'jdk.incubator.vector')
dependsOn(':core:compileJava')
}
tasks.named('test').configure {
dependsOn(':core:compileJava')
}
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')
Provider<String> probeMulticore = providers.gradleProperty('probeMulticore').orElse('false')
Provider<String> probeStudio = providers.gradleProperty('probeStudio').orElse('false')
Provider<String> explicitProbeMulticore = providers.gradleProperty('probeMulticore')
Provider<String> explicitProbeStudio = providers.gradleProperty('probeStudio')
Provider<String> probeRequiredCoverage = providers.gradleProperty('probeRequiredCoverage')
Provider<String> probeSeed = providers.gradleProperty('probeSeed')
Provider<String> probeMinimumChunkX = providers.gradleProperty('probeMinimumChunkX')
Provider<String> probeMaximumChunkX = providers.gradleProperty('probeMaximumChunkX')
Provider<String> probeMinimumChunkZ = providers.gradleProperty('probeMinimumChunkZ')
Provider<String> probeMaximumChunkZ = providers.gradleProperty('probeMaximumChunkZ')
Provider<String> probeParallelism = providers.gradleProperty('probeParallelism')
Provider<String> probeShuffleSeed = providers.gradleProperty('probeShuffleSeed')
Provider<String> probeJfr = providers.gradleProperty('probeJfr')
Provider<String> probeOutput = providers.gradleProperty('probeOutput')
Provider<String> probeStrictGeometry = providers.gradleProperty('probeStrictGeometry').orElse('false')
Provider<String> probeWindows = providers.gradleProperty('probeWindows')
tasks.register('hydrologyProbe', JavaExec) {
group = 'verification'
description = 'Plans hydrology over a window range of a real pack and reports cold first-chunk latency, plan cost, yield, containment, rising water, seams and fluid-tick checks per window.'
mainClass = 'art.arcane.iris.probe.HydrologyProbe'
classpath = sourceSets.main.runtimeClasspath
jvmArgs('--add-modules', 'jdk.incubator.vector')
minHeapSize = '2g'
maxHeapSize = '6g'
dependsOn(':core:compileJava')
doFirst {
Map<String, Provider<String>> required = [
probePack: probePack,
probeSeed: probeSeed,
probeWindows: probeWindows
]
required.each { String name, Provider<String> value ->
if (!value.isPresent() || value.get().isBlank()) {
throw new GradleException("hydrologyProbe requires -P${name}=<value>")
}
}
args(probePack.get(), probeDimension.getOrElse('-'), probeSeed.get(), probeWindows.get(),
providers.gradleProperty('probeThreads').getOrElse('8'), probeStudio.get())
}
}
tasks.register('riverTransectProbe', JavaExec) {
group = 'verification'
description = 'Plans hydrology over a window range of a real pack and renders each window as a plan, river cross-sections and pond outlines.'
mainClass = 'art.arcane.iris.probe.RiverTransectProbe'
classpath = sourceSets.main.runtimeClasspath
jvmArgs('--add-modules', 'jdk.incubator.vector', '-Djava.awt.headless=true')
minHeapSize = '2g'
maxHeapSize = '6g'
dependsOn(':core:compileJava')
doFirst {
Map<String, Provider<String>> required = [
probePack: probePack,
probeSeed: probeSeed,
probeWindows: probeWindows,
probeOutput: probeOutput
]
required.each { String name, Provider<String> value ->
if (!value.isPresent() || value.get().isBlank()) {
throw new GradleException("riverTransectProbe requires -P${name}=<value>")
}
}
java.nio.file.Path output = new File(probeOutput.get()).canonicalFile.toPath()
if (output.startsWith(rootProject.projectDir.canonicalFile.toPath())) {
throw new GradleException('probeOutput must point outside the repository')
}
args(probePack.get(), probeDimension.getOrElse('-'), probeSeed.get(), probeWindows.get(), output.toString(), probeStudio.get())
}
}
tasks.register('hydrologyBlockProbe', JavaExec) {
group = 'verification'
description = 'Generates up to 64 real pack chunks and checks the planned water surface, containment, fluid-tick stability and cane support, with block images.'
mainClass = 'art.arcane.iris.probe.HydrologyBlockProbe'
classpath = sourceSets.main.runtimeClasspath
jvmArgs('--add-modules', 'jdk.incubator.vector', '-Djava.awt.headless=true')
maxHeapSize = '4g'
dependsOn(':core:compileJava')
doFirst {
Map<String, Provider<String>> required = [
probePack: probePack,
probeDimension: probeDimension,
probeSeed: probeSeed,
probeMinimumChunkX: probeMinimumChunkX,
probeMaximumChunkX: probeMaximumChunkX,
probeMinimumChunkZ: probeMinimumChunkZ,
probeMaximumChunkZ: probeMaximumChunkZ,
probeOutput: probeOutput
]
required.each { String name, Provider<String> value ->
if (!value.isPresent() || value.get().isBlank()) {
throw new GradleException("hydrologyBlockProbe requires -P${name}=<value>")
}
}
java.nio.file.Path output = new File(probeOutput.get()).canonicalFile.toPath()
if (output.startsWith(rootProject.projectDir.canonicalFile.toPath())) {
throw new GradleException('probeOutput must point outside the repository')
}
args(probePack.get(), probeDimension.get(), probeSeed.get(), probeMinimumChunkX.get(), probeMaximumChunkX.get(),
probeMinimumChunkZ.get(), probeMaximumChunkZ.get(), probeRequiredCoverage.getOrElse('-'), output.toString(), probeStudio.get())
}
}
tasks.register('terrain3DProbe', JavaExec) {
group = 'verification'
description = 'Generates real pack chunks, verifies volumetric terrain spans and required biome overhang coverage, and exports actual block cross-sections.'
mainClass = 'art.arcane.iris.probe.Terrain3DProbe'
classpath = sourceSets.main.runtimeClasspath
jvmArgs('--add-modules', 'jdk.incubator.vector', '-Djava.awt.headless=true')
dependsOn(':core:compileJava')
doFirst {
Map<String, Provider<String>> required = [
probePack: probePack,
probeDimension: probeDimension,
probeSeed: probeSeed,
probeMinimumChunkX: probeMinimumChunkX,
probeMaximumChunkX: probeMaximumChunkX,
probeMinimumChunkZ: probeMinimumChunkZ,
probeMaximumChunkZ: probeMaximumChunkZ,
probeRequiredCoverage: probeRequiredCoverage,
probeOutput: probeOutput
]
required.each { String name, Provider<String> value ->
if (!value.isPresent() || value.get().isBlank()) {
throw new GradleException("terrain3DProbe requires -P${name}=<value>")
}
}
args(probePack.get(), probeDimension.get(), probeSeed.get(), probeMinimumChunkX.get(),
probeMaximumChunkX.get(), probeMinimumChunkZ.get(), probeMaximumChunkZ.get(),
probeRequiredCoverage.get(), probeOutput.get(), probeStrictGeometry.get(), probeStudio.get())
}
}
tasks.register('genProbe', JavaExec) {
group = 'verification'
description = 'Constructs a real Iris engine over a real pack and verifies generation hashes and optional native backend usage.'
mainClass = 'art.arcane.iris.probe.GenerationProbe'
classpath = sourceSets.main.runtimeClasspath
jvmArgs('--add-modules', 'jdk.incubator.vector')
minHeapSize = '2g'
maxHeapSize = '6g'
if (providers.gradleProperty('probeNativeAccess').orElse('false').get().toBoolean()) {
jvmArgs('--enable-native-access=ALL-UNNAMED')
}
if (providers.gradleProperty('probeNativeBillow').isPresent()) {
systemProperty('volmlib.noise.nativeBillow', providers.gradleProperty('probeNativeBillow').get())
}
systemProperty('volmlib.noise.nativeBillowDiagnostics', providers.gradleProperty('probeNativeDiagnostics').orElse('false').get())
systemProperty('iris.probe.expectedBackend', providers.gradleProperty('probeExpectedBackend').orElse('').get())
systemProperty('iris.probe.requireNativeSamples', providers.gradleProperty('probeRequireNativeSamples').orElse('false').get())
if (providers.gradleProperty('probeNativeLibrary').isPresent()) {
systemProperty('volmlib.native.library', providers.gradleProperty('probeNativeLibrary').get())
}
if (providers.gradleProperty('probeNativePolicy').isPresent()) {
systemProperty('volmlib.native', providers.gradleProperty('probeNativePolicy').get())
}
systemProperty('iris.probe.threads', providers.gradleProperty('probeThreads').orElse('1').get())
systemProperty('iris.probe.pregenProfile', providers.gradleProperty('probePregenProfile').orElse('false').get())
systemProperty('iris.probe.hydrologyPreplan', providers.gradleProperty('probeHydrologyPreplan').orElse('false').get())
systemProperty('iris.probe.historyRouter', providers.gradleProperty('probeHistoryRouter').orElse('false').get())
systemProperty('iris.probe.historyDigest', providers.gradleProperty('probeHistoryDigest').orElse('false').get())
systemProperty('iris.probe.productionBuffers', providers.gradleProperty('probeProductionBuffers').orElse('false').get())
String probeCounters = providers.gradleProperty('probeCounters').orElse('false').get()
systemProperty('iris.generation.counters', probeCounters)
systemProperty('volmlib.cache.countContention', probeCounters)
dependsOn(':core:compileJava')
doFirst {
if (!probePack.isPresent() || probePack.get().isBlank()) {
throw new GradleException('genProbe requires -PprobePack=/absolute/path/to/pack')
}
Provider<String> probeDumpFile = providers.gradleProperty('probeDumpFile')
if (probeDumpFile.isPresent()) {
File dump = new File(probeDumpFile.get())
if (!dump.isAbsolute() || dump.isDirectory()) {
throw new GradleException('probeDumpFile must be an absolute file path')
}
java.nio.file.Path normalizedDump = dump.canonicalFile.toPath()
if (normalizedDump.startsWith(rootProject.projectDir.canonicalFile.toPath())) {
throw new GradleException('probeDumpFile must point outside the repository')
}
if (normalizedDump.parent == null || !normalizedDump.parent.toFile().isDirectory()) {
throw new GradleException('probeDumpFile parent directory must already exist')
}
systemProperty('iris.probe.dumpFile', normalizedDump.toString())
}
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(),
probeMulticore.get(),
probeStudio.get(),
probeSeed.orElse('1337').get())
}
}
tasks.register('genDiff', JavaExec) {
group = 'verification'
description = 'Compares two genProbe chunk dumps and reports block, surface, biome and per-category parity.'
mainClass = 'art.arcane.iris.probe.GenerationDiff'
classpath = sourceSets.main.runtimeClasspath
maxHeapSize = '4g'
doFirst {
Provider<String> baseline = providers.gradleProperty('diffBaseline')
Provider<String> candidate = providers.gradleProperty('diffCandidate')
if (!baseline.isPresent() || !candidate.isPresent()) {
throw new GradleException('genDiff requires -PdiffBaseline=<dump> and -PdiffCandidate=<dump>')
}
args(baseline.get(), candidate.get(), providers.gradleProperty('diffWorstChunks').orElse('10').get())
}
}
tasks.register('generationOrderProbe', JavaExec) {
group = 'verification'
description = 'Compares complete real-pack chunk block and biome hashes across forward, reverse, shuffled and bounded-parallel generation orders.'
mainClass = 'art.arcane.iris.probe.GenerationOrderProbe'
classpath = sourceSets.main.runtimeClasspath
jvmArgs('--add-modules', 'jdk.incubator.vector')
dependsOn(':core:compileJava')
doFirst {
Map<String, Provider<String>> required = [
probePack: probePack,
probeDimension: probeDimension,
probeSeed: probeSeed,
probeMinimumChunkX: probeMinimumChunkX,
probeMaximumChunkX: probeMaximumChunkX,
probeMinimumChunkZ: probeMinimumChunkZ,
probeMaximumChunkZ: probeMaximumChunkZ,
probeParallelism: probeParallelism,
probeShuffleSeed: probeShuffleSeed,
probeMulticore: explicitProbeMulticore,
probeStudio: explicitProbeStudio
]
required.each { String name, Provider<String> value ->
if (!value.isPresent() || value.get().isBlank()) {
throw new GradleException("generationOrderProbe requires -P${name}=<value>")
}
}
args(
probePack.get(),
probeDimension.get(),
probeSeed.get(),
probeMinimumChunkX.get(),
probeMaximumChunkX.get(),
probeMinimumChunkZ.get(),
probeMaximumChunkZ.get(),
probeParallelism.get(),
probeShuffleSeed.get(),
explicitProbeMulticore.get(),
explicitProbeStudio.get())
}
}
tasks.register('cavePoolProbe', JavaExec) {
group = 'verification'
description = 'Generates real pack chunks with cave pools and reports containment, pool sizes and generation-order hashes.'
mainClass = 'art.arcane.iris.probe.CavePoolProbe'
classpath = sourceSets.main.runtimeClasspath
jvmArgs('--add-modules', 'jdk.incubator.vector')
minHeapSize = '2g'
maxHeapSize = '8g'
dependsOn(':core:compileJava')
doFirst {
Provider<String> threads = providers.gradleProperty('probeThreads').orElse('8')
Provider<String> work = providers.gradleProperty('probeWork').orElse(System.getProperty('java.io.tmpdir'))
Map<String, Provider<String>> required = [
probePack: probePack,
probeDimension: probeDimension,
probeSeed: probeSeed,
probeMinimumChunkX: probeMinimumChunkX,
probeMaximumChunkX: probeMaximumChunkX,
probeMinimumChunkZ: probeMinimumChunkZ,
probeMaximumChunkZ: probeMaximumChunkZ
]
required.each { String name, Provider<String> value ->
if (!value.isPresent() || value.get().isBlank()) {
throw new GradleException("cavePoolProbe requires -P${name}=<value>")
}
}
args(probePack.get(), probeDimension.get(), probeSeed.get(), probeMinimumChunkX.get(), probeMaximumChunkX.get(),
probeMinimumChunkZ.get(), probeMaximumChunkZ.get(), threads.get(), work.get())
}
}
tasks.register('seaInletProbe', JavaExec) {
group = 'verification'
description = 'Plans and generates real pack chunks with and without sea inlets and reports containment, seal, roof, sea level, ocean connection and order determinism.'
mainClass = 'art.arcane.iris.probe.SeaInletProbe'
classpath = sourceSets.main.runtimeClasspath
jvmArgs('--add-modules', 'jdk.incubator.vector', '-Djava.awt.headless=true')
minHeapSize = '2g'
maxHeapSize = '8g'
dependsOn(':core:compileJava')
doFirst {
Provider<String> packOff = providers.gradleProperty('probePackOff')
Provider<String> windows = providers.gradleProperty('probeWindows')
Provider<String> mode = providers.gradleProperty('probeMode').orElse('gate')
if (!probePack.isPresent() || !packOff.isPresent() || !probeDimension.isPresent() || !windows.isPresent() || !probeOutput.isPresent()) {
throw new GradleException('seaInletProbe requires probePack, probePackOff, probeDimension, probeWindows=x0..x1,z0..z1 and probeOutput')
}
String[] axes = windows.get().split(',')
String[] xs = axes[0].split('\\.\\.')
String[] zs = axes[1].split('\\.\\.')
args(probePack.get(), packOff.get(), probeDimension.get(), probeSeed.orElse('1337').get(), xs[0], xs[1], zs[0], zs[1], mode.get(),
probeOutput.get(), providers.gradleProperty('probeThreads').orElse('6').get())
}
}
tasks.register('transitionProbe', JavaExec) {
group = 'verification'
description = 'Exercises a bounded two-pack Studio cutover, transition routing, receipts and semantics with a vanilla-biome pack.'
mainClass = 'art.arcane.iris.probe.TransitionProbe'
workingDir = rootProject.projectDir
classpath = sourceSets.main.runtimeClasspath
jvmArgs('--add-modules', 'jdk.incubator.vector')
timeout = java.time.Duration.ofMinutes(12)
doFirst {
Provider<String> replacement = providers.gradleProperty('probeReplacementPack')
if (!probePack.isPresent() || !replacement.isPresent() || !probeDimension.isPresent() || !probeOutput.isPresent()) {
throw new GradleException('transitionProbe requires probePack, probeReplacementPack, probeDimension and probeOutput')
}
java.nio.file.Path output = new File(probeOutput.get()).canonicalFile.toPath()
if (output.startsWith(rootProject.projectDir.canonicalFile.toPath())) {
throw new GradleException('probeOutput must point outside the repository')
}
args(probePack.get(), replacement.get(), probeDimension.get(), probeSeed.getOrElse('1337'),
providers.gradleProperty('probeTransitionWidth').getOrElse('32'),
providers.gradleProperty('probeTransitionChunks').getOrElse('2'), output.toString(),
providers.gradleProperty('probeTransitionReverse').getOrElse('false'))
}
}
['transitionProbe', 'hydrologyProbe', 'genProbe', 'generationOrderProbe', 'riverTransectProbe', 'terrain3DProbe',
'hydrologyBlockProbe', 'cavePoolProbe', 'seaInletProbe'].each { String taskName ->
tasks.named(taskName, JavaExec).configure { JavaExec task ->
classpath += configurations.studioProbeApi
doFirst {
if (probeJfr.isPresent()) {
String requestedPath = probeJfr.get()
if (requestedPath.isBlank()) {
throw new GradleException('probeJfr must be a nonblank absolute path')
}
File recording = new File(requestedPath)
if (!recording.isAbsolute()) {
throw new GradleException('probeJfr must be an absolute path')
}
if (recording.isDirectory()) {
throw new GradleException('probeJfr must name a recording file, not a directory')
}
java.nio.file.Path normalized = recording.canonicalFile.toPath()
java.nio.file.Path repository = rootProject.projectDir.canonicalFile.toPath()
if (normalized.startsWith(repository)) {
throw new GradleException('probeJfr must point outside the repository')
}
File parent = normalized.toFile().parentFile
if (parent == null || !parent.isDirectory()) {
throw new GradleException('probeJfr parent directory must already exist')
}
String settings = providers.gradleProperty('probeJfrSettings').orElse('profile').get()
if (settings.isBlank() || settings.contains(',')) {
throw new GradleException('probeJfrSettings must name a JFR configuration or an absolute .jfc path')
}
task.jvmArgs(
'-XX:FlightRecorderOptions=stackdepth=256',
"-XX:StartFlightRecording=filename=${normalized},settings=${settings},dumponexit=true"
)
}
}
}
}
String deserializationPack = providers.gradleProperty('deserializationPack')
.orElse(project.file('src/main/resources/deserialization-pack').absolutePath)
.get()
tasks.register('deserializationProbe', JavaExec) {
group = 'verification'
description = 'Deserializes entity, spawner and loot pack content through the real Iris Gson loaders on a Bukkit-free JVM to keep Bukkit-typed pack-model fields dead.'
mainClass = 'art.arcane.iris.probe.DeserializationProbe'
classpath = sourceSets.main.runtimeClasspath
jvmArgs('--add-modules', 'jdk.incubator.vector')
args(deserializationPack)
dependsOn(':core:compileJava')
}