Skip to content

Commit 44a5cbf

Browse files
authored
Merge pull request #233 from SkyBlade1978/master-26.2-neo
Master 26.2 neo
2 parents 66a0ee8 + 98cf34a commit 44a5cbf

53 files changed

Lines changed: 3154 additions & 513 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
Version 4.0.6.2602002
2+
3+
* Adopt target-qualified four-component versions so Minecraft and loader compatibility can be identified from the mod version.
4+
* Preserve generated worlds made with Mineralogy 1.10, 1.12, or 5.x by reading their saved mod metadata and exact legacy configuration before creating the OreSpawn world profile.
5+
* Write human-readable, idempotent upgrade reports for legacy OreSpawn and Mineralogy imports while retaining source files and existing chunks unchanged.
6+
* Audit automated runtime logs and dynamic-fluid generation so logged worldgen failures cannot pass merely because the process exits normally.
7+
* Qualify existing OreSpawn 4.0.5 global and per-world profiles without changing explicit Custom values or provider definitions.
8+
* Fix provider top and filler materials being generated one block below exposed ground.
9+
* Apply underwater materials from the corrected ground and ceiling materials to roof undersides.
10+
* Preserve trees, vegetation, structures and block entities by running surface replacement before late features.
11+
* Honour exact biome-to-geome weights on dynamic biome registries.
12+
* Stagger close Stable Layers geome transitions by layer instead of changing a whole rock column at one boundary.
13+
* Recalibrate Stable Layers edge-detail presets so Average retains natural variation at later rock contacts.
14+
* Existing chunks are not rewritten; the correction applies while generating new chunks.
15+
116
Version 3.3.1
217

318
* Fix several bugs

README.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,22 @@ Important files:
3939
| `<world>/serverconfig/orespawn-worldgen.json` | Complete settings snapshot for one world |
4040
| `config/<modid>-orespawn.json` | Optional modpack override for one provider |
4141
| `config/orespawn-guide/README.md` | Guide exported automatically on first load |
42+
| `config/orespawn-upgrade-report.txt` | Human summary produced when legacy OreSpawn rules are imported |
43+
| `<world>/serverconfig/orespawn-upgrade-report.txt` | Human summary produced when a generated legacy Mineralogy world is pinned to its saved settings |
4244

4345
Profile edits affect newly generated chunks. Ore and flat-bedrock retrogen are
4446
separate opt-in features; OreSpawn never retro-generates rock strata.
47+
Stable Layers honours exact biome-ID geome influences on dynamic biome
48+
registries and spreads close geome transitions across layers rather than
49+
changing an entire vertical rock column at one boundary.
50+
51+
When an already-generated world has saved Mineralogy 1.10, 1.12, or 5.x mod
52+
metadata but no OreSpawn world profile, OreSpawn reads the matching published
53+
configuration contract and records the exact engine, numeric settings, rock
54+
order, and white/blacklists in the new world profile. Saved-world identity
55+
wins over stale files in the installation. A fresh world is never reclassified
56+
merely because an old `mineralogy.cfg` or `mineralogy-common.toml` remains in
57+
the instance.
4558

4659
To move a configured single-player world to a dedicated server, copy the
4760
world's `serverconfig/orespawn-worldgen.json` with the world and install the
@@ -84,9 +97,12 @@ Use Java 25 from the repository root:
8497
```
8598

8699
`build` runs the standard `check` lifecycle. In addition to the JUnit suite,
87-
that lifecycle packages a test-only provider mod, loads its custom biome in
88-
normal noise terrain, and verifies both fresh generation and reopening the
89-
same saved world. The fixture is not included in OreSpawn's published jars.
100+
that lifecycle packages a test-only provider mod and verifies exposed,
101+
underwater, filler, and ceiling surfaces in open and ceiling normal-noise
102+
dimensions. It also proves later vegetation, structures, and block entities
103+
survive, verifies identifier-weighted geology in a dynamic custom biome, then
104+
reopens and checks the exact saved world. The fixture is not included in
105+
OreSpawn's published jars.
90106

91107
Import or refresh the project with Eclipse Buildship. NeoGradle supplies the
92108
Eclipse model and run configurations through the `eclipse` task; this branch

build.gradle

Lines changed: 205 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,12 @@ runs {
108108
}
109109

110110
['Fresh', 'Reload'].each { String phase ->
111-
register("biomeIntegration${phase}") {
111+
register("surfaceIntegration${phase}") {
112112
runType 'gameTestServer'
113-
workingDirectory layout.buildDirectory.dir('biome-integration-run')
114-
systemProperty 'neoforge.enabledGameTestNamespaces', 'cakeworldprobe'
113+
workingDirectory layout.buildDirectory.dir('surface-integration-run')
114+
systemProperty 'neoforge.enabledGameTestNamespaces', 'surfaceprobe'
115115
systemProperty 'forge.logging.console.level', 'info'
116-
systemProperty 'cakeworld.biomeIntegrationPhase', phase.toLowerCase(Locale.ROOT)
116+
systemProperty 'surfaceprobe.integrationPhase', phase.toLowerCase(Locale.ROOT)
117117
modSource project.sourceSets.main
118118
}
119119
}
@@ -201,79 +201,249 @@ tasks.withType(JavaCompile).configureEach {
201201
tasks.named('javadoc', Javadoc).configure {
202202
options.encoding = 'UTF-8'
203203
options.addStringOption('Xdoclint:none', '-quiet')
204+
options.addBooleanOption('-no-fonts', true)
204205
}
205206

206207
tasks.named('test', Test).configure {
207208
useJUnitPlatform()
209+
// Unit tests inspect target files relative to the checkout, but they do
210+
// not need NeoForge's rolling runtime files. A console-only logger keeps
211+
// them from contending with Eclipse/client logs in this working directory.
212+
systemProperty 'log4j.configurationFile', file('src/test/resources/log4j2-test.xml').absolutePath
213+
// Loaded only through an isolated URLClassLoader by the parity test. This
214+
// is deliberately not a Gradle dependency and cannot leak into Eclipse or
215+
// a published OreSpawn jar.
216+
File mineralogy5Oracle = file('../../MinecraftMineralogy 118/MinecraftMineralogy/build/libs/Mineralogy-1.18.2-5.4.0.jar')
217+
if (mineralogy5Oracle.isFile()) {
218+
systemProperty 'orespawn.mineralogy5Oracle', mineralogy5Oracle.absolutePath
219+
}
220+
}
221+
222+
// Several registry-focused tests initialize the real global config singleton.
223+
// Keep that target-native coverage without creating or changing a developer's
224+
// checkout config as a side effect of `test` or `build`.
225+
def unitTestWorldgenConfig = file('config/orespawn-worldgen.json')
226+
def unitTestWorldgenConfigWasPresent = false
227+
byte[] unitTestWorldgenConfigBytes = null
228+
tasks.named('test', Test).configure {
229+
doFirst {
230+
unitTestWorldgenConfigWasPresent = unitTestWorldgenConfig.isFile()
231+
unitTestWorldgenConfigBytes = unitTestWorldgenConfigWasPresent
232+
? unitTestWorldgenConfig.bytes : null
233+
}
234+
}
235+
def preserveDeveloperWorldgenConfig = tasks.register('preserveDeveloperWorldgenConfig') {
236+
doLast {
237+
if (unitTestWorldgenConfigWasPresent) {
238+
byte[] after = unitTestWorldgenConfig.isFile() ? unitTestWorldgenConfig.bytes : null
239+
if (after == null || !java.util.Arrays.equals(unitTestWorldgenConfigBytes, after)) {
240+
unitTestWorldgenConfig.parentFile.mkdirs()
241+
unitTestWorldgenConfig.bytes = unitTestWorldgenConfigBytes
242+
throw new GradleException('Unit tests changed config/orespawn-worldgen.json; the original was restored')
243+
}
244+
} else if (unitTestWorldgenConfig.isFile()) {
245+
delete unitTestWorldgenConfig
246+
}
247+
}
248+
}
249+
tasks.named('test') {
250+
finalizedBy preserveDeveloperWorldgenConfig
251+
}
252+
253+
// A NeoForge process is not green merely because it returns exit code zero.
254+
// The loader can log a worldgen/linkage failure and still shut down normally.
255+
// No ERROR/FATAL line has been accepted as harmless on NeoForge 26.2; add an
256+
// exception only after reproducing it in the matching loader control.
257+
def acceptedNeoForge262LogNoise = []
258+
259+
def runtimeCrashSnapshot = { File runDirectory ->
260+
File crashDirectory = new File(runDirectory, 'crash-reports')
261+
if (!crashDirectory.isDirectory()) return [] as Set
262+
return fileTree(crashDirectory) { include '**/*' }.files
263+
.findAll { it.isFile() }.collect { it.absolutePath } as Set
264+
}
265+
266+
def assertRuntimeLogsClean = { File runDirectory, String context, Set priorCrashes ->
267+
File crashDirectory = new File(runDirectory, 'crash-reports')
268+
if (crashDirectory.isDirectory()) {
269+
def crashes = fileTree(crashDirectory) { include '**/*' }.files
270+
.findAll { it.isFile() && !priorCrashes.contains(it.absolutePath) }
271+
if (!crashes.isEmpty()) {
272+
throw new GradleException("${context} produced crash report ${crashes.first()}")
273+
}
274+
}
275+
276+
File logsDirectory = new File(runDirectory, 'logs')
277+
if (!logsDirectory.isDirectory()) return
278+
def failures = []
279+
[new File(logsDirectory, 'latest.log'), new File(logsDirectory, 'debug.log')]
280+
.findAll { it.isFile() }.each { File log ->
281+
int lineNumber = 0
282+
log.eachLine('UTF-8') { String line ->
283+
lineNumber++
284+
boolean unexpectedSeverity = line ==~ /.*\/(?:ERROR|FATAL)\].*/
285+
boolean knownNoise = acceptedNeoForge262LogNoise.any { line =~ it }
286+
boolean fatalText = line.contains('Encountered an unexpected exception') ||
287+
line.contains('Exception stopping the server') ||
288+
line.contains('Migration audit failed') ||
289+
line.contains('java.lang.Error:') ||
290+
line.contains('NoSuchMethodError') ||
291+
line.contains('NoClassDefFoundError') ||
292+
line.contains('ExceptionInInitializerError') ||
293+
line.contains('Tried to assign a mutable BlockPos') ||
294+
line.contains('causing cascading worldgen lag')
295+
if ((unexpectedSeverity && !knownNoise) || fatalText) {
296+
failures.add("${log.name}:${lineNumber}: ${line}")
297+
}
298+
}
299+
}
300+
if (!failures.isEmpty()) {
301+
throw new GradleException("${context} logged unexpected errors:\n"
302+
+ failures.take(20).join('\n'))
303+
}
304+
}
305+
306+
tasks.register('runtimeLogScannerTest') {
307+
group = 'verification'
308+
description = 'Proves runtime log validation rejects real NeoForge failures.'
309+
doLast {
310+
File probe = file("${buildDir}/runtime-log-scanner-test")
311+
delete probe
312+
File logs = new File(probe, 'logs'); logs.mkdirs()
313+
new File(logs, 'latest.log').setText(
314+
'[Server thread/INFO] [neoforge]: Done\n', 'UTF-8')
315+
assertRuntimeLogsClean(probe, 'scanner-clean-log-probe', [] as Set)
316+
new File(logs, 'latest.log').setText(
317+
'[Server thread/WARN]: Tried to assign a mutable BlockPos to tick data...\n', 'UTF-8')
318+
boolean rejected = false
319+
try { assertRuntimeLogsClean(probe, 'scanner-mutable-position-probe', [] as Set) }
320+
catch (GradleException expected) { rejected = true }
321+
if (!rejected) throw new GradleException('Runtime log scanner accepted a mutable BlockPos leak')
322+
new File(logs, 'latest.log').setText(
323+
'[Server thread/DEBUG] [neoforge]: Minecraft loaded a new chunk while populating another, causing cascading worldgen lag.\n', 'UTF-8')
324+
rejected = false
325+
try { assertRuntimeLogsClean(probe, 'scanner-cascading-probe', [] as Set) }
326+
catch (GradleException expected) { rejected = true }
327+
if (!rejected) throw new GradleException('Runtime log scanner accepted cascading worldgen')
328+
new File(logs, 'latest.log').setText(
329+
'[Server thread/ERROR] [example]: Unexpected fixture failure\n', 'UTF-8')
330+
rejected = false
331+
try { assertRuntimeLogsClean(probe, 'scanner-severity-probe', [] as Set) }
332+
catch (GradleException expected) { rejected = true }
333+
if (!rejected) throw new GradleException('Runtime log scanner accepted an unexpected ERROR line')
334+
delete probe
335+
}
336+
}
337+
338+
tasks.named('check') {
339+
dependsOn 'runtimeLogScannerTest'
340+
}
341+
342+
tasks.register('verifyMineralogyOracleIsolation') {
343+
group = 'verification'
344+
description = 'Prevents published Mineralogy engines from leaking into Gradle configurations or ordinary Eclipse launches.'
345+
doLast {
346+
configurations.each { configuration ->
347+
if (configuration.canBeResolved &&
348+
configuration.files.any { it.name ==~ /Mineralogy-.*\.jar/ }) {
349+
throw new GradleException("Mineralogy oracle leaked into Gradle configuration ${configuration.name}")
350+
}
351+
}
352+
}
353+
}
354+
355+
tasks.named('check') {
356+
dependsOn 'verifyMineralogyOracleIsolation'
357+
}
358+
359+
['runClient', 'runServer', 'runGameTestServer', 'runClientData'].each { String taskName ->
360+
tasks.matching { it.name == taskName }.configureEach { JavaExec runTask ->
361+
doFirst {
362+
new File(runTask.workingDir, 'mods').mkdirs()
363+
runTask.ext.oreSpawnCrashSnapshot = runtimeCrashSnapshot(runTask.workingDir)
364+
}
365+
doLast {
366+
assertRuntimeLogsClean(runTask.workingDir, taskName,
367+
runTask.ext.oreSpawnCrashSnapshot as Set)
368+
}
369+
}
208370
}
209371

210-
def biomeIntegrationClasses = layout.buildDirectory.dir('biome-integration-fixture/classes')
211-
def compileBiomeIntegrationTestMod = tasks.register('compileBiomeIntegrationTestMod', JavaCompile) {
372+
def surfaceIntegrationClasses = layout.buildDirectory.dir('surface-integration-fixture/classes')
373+
def compileSurfaceIntegrationTestMod = tasks.register('compileSurfaceIntegrationTestMod', JavaCompile) {
212374
dependsOn tasks.named('classes')
213375
source fileTree('src/biomeIntegrationTest/java')
214376
classpath = files(sourceSets.main.output, sourceSets.main.compileClasspath)
215-
destinationDirectory.set(biomeIntegrationClasses)
377+
destinationDirectory.set(surfaceIntegrationClasses)
216378
javaCompiler.set(javaToolchains.compilerFor {
217379
languageVersion = JavaLanguageVersion.of(25)
218380
})
219-
options.release = 16
381+
options.release = 25
220382
options.encoding = 'UTF-8'
221383
}
222384

223-
def biomeIntegrationTestModJar = tasks.register('biomeIntegrationTestModJar', Jar) {
224-
dependsOn compileBiomeIntegrationTestMod
225-
archiveFileName = 'cakeworldprobe.jar'
226-
destinationDirectory = layout.buildDirectory.dir('biome-integration-fixture')
385+
def surfaceIntegrationTestModJar = tasks.register('surfaceIntegrationTestModJar', Jar) {
386+
dependsOn compileSurfaceIntegrationTestMod
387+
archiveFileName = 'surfaceprobe.jar'
388+
destinationDirectory = layout.buildDirectory.dir('surface-integration-fixture')
227389
manifest {
228-
attributes 'MixinConfigs': 'cakeworldprobe.mixins.json'
390+
attributes 'MixinConfigs': 'surfaceprobe.mixins.json'
229391
}
230-
from biomeIntegrationClasses
392+
from surfaceIntegrationClasses
231393
from 'src/biomeIntegrationTest/resources'
232394
}
233395

234-
def biomeIntegrationRunDirectory = layout.buildDirectory.dir('biome-integration-run')
235-
def prepareBiomeIntegrationTest = tasks.register('prepareBiomeIntegrationTest') {
236-
dependsOn biomeIntegrationTestModJar
396+
def surfaceIntegrationRunDirectory = layout.buildDirectory.dir('surface-integration-run')
397+
def prepareSurfaceIntegrationTest = tasks.register('prepareSurfaceIntegrationTest') {
398+
dependsOn surfaceIntegrationTestModJar
237399
doLast {
238-
delete biomeIntegrationRunDirectory
400+
delete surfaceIntegrationRunDirectory
239401
copy {
240-
from biomeIntegrationTestModJar.flatMap { it.archiveFile }
241-
into biomeIntegrationRunDirectory.map { it.dir('mods') }
402+
from surfaceIntegrationTestModJar.flatMap { it.archiveFile }
403+
into surfaceIntegrationRunDirectory.map { it.dir('mods') }
242404
}
243405
}
244406
}
245407

246408
tasks.configureEach {
247-
if (name == 'runBiomeIntegrationFresh') {
248-
dependsOn prepareBiomeIntegrationTest
249-
} else if (name == 'runBiomeIntegrationReload') {
250-
dependsOn 'runBiomeIntegrationFresh'
409+
if (name == 'runSurfaceIntegrationFresh') {
410+
dependsOn prepareSurfaceIntegrationTest
411+
} else if (name == 'runSurfaceIntegrationReload') {
412+
dependsOn 'runSurfaceIntegrationFresh'
413+
}
414+
if (name == 'runSurfaceIntegrationFresh' || name == 'runSurfaceIntegrationReload') {
415+
doFirst {
416+
ext.oreSpawnCrashSnapshot = runtimeCrashSnapshot(workingDir)
417+
}
418+
doLast {
419+
assertRuntimeLogsClean(workingDir, "NeoForge 26.2 ${name}",
420+
ext.oreSpawnCrashSnapshot as Set)
421+
}
251422
}
252423
}
253424

254-
def biomeIntegrationTest = tasks.register('biomeIntegrationTest') {
425+
def surfaceIntegrationTest = tasks.register('surfaceIntegrationTest') {
255426
group = 'verification'
256-
description = 'Verifies a provider-owned custom biome in fresh and reloaded normal terrain.'
257-
dependsOn 'runBiomeIntegrationReload'
427+
description = 'Verifies provider surfaces and dynamic-biome geology across fresh and reloaded normal terrain.'
428+
dependsOn 'runSurfaceIntegrationReload'
258429
doLast {
259-
File marker = biomeIntegrationRunDirectory.get().file(
260-
'gametestserver/gametestworld/cakeworld-biome-integration.properties').asFile
430+
File marker = surfaceIntegrationRunDirectory.get().file(
431+
'gametestserver/gametestworld/surfaceprobe-integration.properties').asFile
261432
if (!marker.isFile()) {
262-
throw new GradleException("Biome integration completion marker is missing: ${marker}")
433+
throw new GradleException("Surface integration completion marker is missing: ${marker}")
263434
}
264435
Properties result = new Properties()
265436
marker.withInputStream { result.load(it) }
266437
if (result.getProperty('reload_verified') != 'true') {
267-
throw new GradleException("Biome integration reload was not verified: ${marker}")
438+
throw new GradleException("Surface integration reload was not verified: ${marker}")
268439
}
269-
logger.lifecycle('Custom-biome integration verified: {} chunks, {} top blocks, {} filler blocks, fresh + reload',
270-
result.getProperty('matching_chunks'), result.getProperty('pink_surface'),
271-
result.getProperty('white_filler'))
440+
logger.lifecycle('Provider surfaces and dynamic-biome geology verified: {} dimensions, {} columns each, fresh + reload',
441+
result.getProperty('dimensions'), result.getProperty('columns_per_dimension'))
272442
}
273443
}
274444

275445
tasks.named('check') {
276-
dependsOn biomeIntegrationTest
446+
dependsOn surfaceIntegrationTest
277447
}
278448

279449
idea {

docs/AGENTS.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# OreSpawn Documentation Map
22

3-
This index is for navigating the documentation to learn how to integrate with and to use OreSpawn with a mod or modpack.
3+
This index is for navigating the documentation to learn how to integrate with
4+
and use OreSpawn with a mod or modpack.
45
Start with [DEVELOPER_GUIDE.md](DEVELOPER_GUIDE.md).
56

67
Use the focused guides for implementation details:
@@ -11,4 +12,6 @@ Use the focused guides for implementation details:
1112
- [BIOMES.md](BIOMES.md) and [DIMENSIONS.md](DIMENSIONS.md) for world integration;
1213
- [TEMPLATES.md](TEMPLATES.md) for selectable world styles;
1314
- [CONFIGURATION.md](CONFIGURATION.md) for configuration behavior;
15+
- [VERSIONS.md](VERSIONS.md) for the shared four-component target-qualified
16+
versioning and branch-release convention;
1417
- [README.md](README.md) for schemas, examples, and the complete documentation index.

docs/API.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,11 @@ OreSpawnApi.createSampler(server.overworld()).ifPresent(sampler -> {
133133
});
134134
```
135135

136-
`sampleColumn` performs one biome/geome classification and reuses it for every
137-
Y query. Sampling is read-only and is intended for gameplay decisions,
138-
diagnostics, and compatible generation outside OreSpawn's block loops.
136+
`sampleColumn` performs one biome/dominant-geome classification and reuses its
137+
transition scores for every Y query. `rockAt` therefore matches Stable Layers
138+
when a close geome transition is staggered by layer. Sampling is read-only and
139+
is intended for gameplay decisions, diagnostics, and compatible generation
140+
outside OreSpawn's block loops.
139141
Callbacks inside OreSpawn generation loops are intentionally unsupported.
140142

141143
Custom pattern mods create a NeoForge `DeferredRegister<OrePatternType>` using

0 commit comments

Comments
 (0)