Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
Version 4.0.6

* Fix provider top and filler materials being generated one block below exposed ground.
* Apply underwater materials from the corrected ground and ceiling materials to roof undersides.
* Preserve trees, vegetation, structures and block entities by running surface replacement before late features.
* Honour exact biome-to-geome weights on dynamic biome registries.
* Stagger close Stable Layers geome transitions by layer instead of changing a whole rock column at one boundary.
* Recalibrate Stable Layers edge-detail presets so Average retains natural variation at later rock contacts.
* Existing chunks are not rewritten; the correction applies while generating new chunks.

Version 3.3.1

* Fix several bugs
Expand Down
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ Important files:

Profile edits affect newly generated chunks. Ore and flat-bedrock retrogen are
separate opt-in features; OreSpawn never retro-generates rock strata.
Stable Layers honours exact biome-ID geome influences on dynamic biome
registries and spreads close geome transitions across layers rather than
changing an entire vertical rock column at one boundary.

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

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

Import or refresh the project with Eclipse Buildship. ForgeGradle 7's legacy
`eclipse` task produces Java-only metadata and must not be used for this branch.
Expand Down
70 changes: 35 additions & 35 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,15 @@ minecraft {
register('gameTestServer')

['Fresh', 'Reload'].each { String phase ->
register("biomeIntegration${phase}") {
register("surfaceIntegration${phase}") {
mainClass = 'net.minecraftforge.bootstrap.ForgeBootstrap'
args '--launchTarget', 'forge_userdev_server_gametest', '--gameDir', '.'
environment 'MCP_MAPPINGS', "official_${minecraft_version}"
workingDir = layout.buildDirectory.dir('biome-integration-run')
workingDir = layout.buildDirectory.dir('surface-integration-run')
systemProperty 'forge.enableGameTest', 'true'
systemProperty 'forge.enabledGameTestNamespaces', 'cakeworldprobe'
systemProperty 'forge.enabledGameTestNamespaces', 'surfaceprobe'
systemProperty 'forge.logging.console.level', 'info'
systemProperty 'cakeworld.biomeIntegrationPhase', phase.toLowerCase(Locale.ROOT)
systemProperty 'surfaceprobe.integrationPhase', phase.toLowerCase(Locale.ROOT)
mods {
create(mod_id) {
source sourceSets.main
Expand Down Expand Up @@ -224,79 +224,79 @@ tasks.withType(JavaCompile).configureEach {
tasks.named('javadoc', Javadoc).configure {
options.encoding = 'UTF-8'
options.addStringOption('Xdoclint:none', '-quiet')
options.addBooleanOption('-no-fonts', true)
}

tasks.named('test', Test).configure {
useJUnitPlatform()
}

def biomeIntegrationClasses = layout.buildDirectory.dir('biome-integration-fixture/classes')
def compileBiomeIntegrationTestMod = tasks.register('compileBiomeIntegrationTestMod', JavaCompile) {
def surfaceIntegrationClasses = layout.buildDirectory.dir('surface-integration-fixture/classes')
def compileSurfaceIntegrationTestMod = tasks.register('compileSurfaceIntegrationTestMod', JavaCompile) {
dependsOn tasks.named('classes')
source fileTree('src/biomeIntegrationTest/java')
classpath = files(sourceSets.main.output, sourceSets.main.compileClasspath)
destinationDirectory.set(biomeIntegrationClasses)
destinationDirectory.set(surfaceIntegrationClasses)
javaCompiler.set(javaToolchains.compilerFor {
languageVersion = JavaLanguageVersion.of(25)
})
options.release = 16
options.release = 25
options.encoding = 'UTF-8'
}

def biomeIntegrationTestModJar = tasks.register('biomeIntegrationTestModJar', Jar) {
dependsOn compileBiomeIntegrationTestMod
archiveFileName = 'cakeworldprobe.jar'
destinationDirectory = layout.buildDirectory.dir('biome-integration-fixture')
def surfaceIntegrationTestModJar = tasks.register('surfaceIntegrationTestModJar', Jar) {
dependsOn compileSurfaceIntegrationTestMod
archiveFileName = 'surfaceprobe.jar'
destinationDirectory = layout.buildDirectory.dir('surface-integration-fixture')
manifest {
attributes 'MixinConfigs': 'cakeworldprobe.mixins.json'
attributes 'MixinConfigs': 'surfaceprobe.mixins.json'
}
from biomeIntegrationClasses
from surfaceIntegrationClasses
from 'src/biomeIntegrationTest/resources'
}

def biomeIntegrationRunDirectory = layout.buildDirectory.dir('biome-integration-run')
def prepareBiomeIntegrationTest = tasks.register('prepareBiomeIntegrationTest') {
dependsOn biomeIntegrationTestModJar
def surfaceIntegrationRunDirectory = layout.buildDirectory.dir('surface-integration-run')
def prepareSurfaceIntegrationTest = tasks.register('prepareSurfaceIntegrationTest') {
dependsOn surfaceIntegrationTestModJar
doLast {
delete biomeIntegrationRunDirectory
delete surfaceIntegrationRunDirectory
copy {
from biomeIntegrationTestModJar.flatMap { it.archiveFile }
into biomeIntegrationRunDirectory.map { it.dir('mods') }
from surfaceIntegrationTestModJar.flatMap { it.archiveFile }
into surfaceIntegrationRunDirectory.map { it.dir('mods') }
}
}
}

tasks.configureEach {
if (name == 'runBiomeIntegrationFresh') {
dependsOn prepareBiomeIntegrationTest
} else if (name == 'runBiomeIntegrationReload') {
dependsOn 'runBiomeIntegrationFresh'
if (name == 'runSurfaceIntegrationFresh') {
dependsOn prepareSurfaceIntegrationTest
} else if (name == 'runSurfaceIntegrationReload') {
dependsOn 'runSurfaceIntegrationFresh'
}
}

def biomeIntegrationTest = tasks.register('biomeIntegrationTest') {
def surfaceIntegrationTest = tasks.register('surfaceIntegrationTest') {
group = 'verification'
description = 'Verifies a provider-owned custom biome in fresh and reloaded normal terrain.'
dependsOn 'runBiomeIntegrationReload'
description = 'Verifies provider surfaces and dynamic-biome geology across fresh and reloaded normal terrain.'
dependsOn 'runSurfaceIntegrationReload'
doLast {
File marker = biomeIntegrationRunDirectory.get().file(
'gametestserver/gametestworld/cakeworld-biome-integration.properties').asFile
File marker = surfaceIntegrationRunDirectory.get().file(
'gametestserver/gametestworld/surfaceprobe-integration.properties').asFile
if (!marker.isFile()) {
throw new GradleException("Biome integration completion marker is missing: ${marker}")
throw new GradleException("Surface integration completion marker is missing: ${marker}")
}
Properties result = new Properties()
marker.withInputStream { result.load(it) }
if (result.getProperty('reload_verified') != 'true') {
throw new GradleException("Biome integration reload was not verified: ${marker}")
throw new GradleException("Surface integration reload was not verified: ${marker}")
}
logger.lifecycle('Custom-biome integration verified: {} chunks, {} top blocks, {} filler blocks, fresh + reload',
result.getProperty('matching_chunks'), result.getProperty('pink_surface'),
result.getProperty('white_filler'))
logger.lifecycle('Provider surfaces and dynamic-biome geology verified: {} dimensions, {} columns each, fresh + reload',
result.getProperty('dimensions'), result.getProperty('columns_per_dimension'))
}
}

tasks.named('check') {
dependsOn biomeIntegrationTest
dependsOn surfaceIntegrationTest
}

// ForgeGradle 7 generates a launch for every source set, but its ordinary
Expand Down
7 changes: 4 additions & 3 deletions docs/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# OreSpawn Documentation For Coding Agents
# OreSpawn Documentation Map

This index is for coding agents working on other mods or modpacks that integrate
with OreSpawn. Start with [DEVELOPER_GUIDE.md](DEVELOPER_GUIDE.md).
This index is for navigating the documentation to learn how to integrate with
and use OreSpawn with a mod or modpack. Start with
[DEVELOPER_GUIDE.md](DEVELOPER_GUIDE.md).

Use the focused guides for implementation details:

Expand Down
40 changes: 21 additions & 19 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Submit declarations during `InterModEnqueueEvent`:

```java
WorldgenProvider provider = WorldgenProvider.builder("examplemod", 1)
.rock(new ResourceLocation("examplemod", "slate"), GeologyFamily.METAMORPHIC, rock -> rock
.rock(Identifier.parse("examplemod:slate"), GeologyFamily.METAMORPHIC, rock -> rock
.depth(12, 36)
.weight(1.2)
.oreReplaceable(true))
Expand All @@ -33,7 +33,7 @@ For a complete ore-only Java example, including dimensions, height curves,
patterns, and host tags, see `DEVELOPER_GUIDE.md`.

Definitions are immutable after `build()`. Registry references remain
`ResourceLocation` values until OreSpawn validates and bakes them. Provider
`Identifier` values until OreSpawn validates and bakes them. Provider
messages are processed through Forge IMC and frozen at load completion; direct
cross-mod mutation during parallel setup is unsupported.

Expand All @@ -56,17 +56,17 @@ FormationDefinition formations = FormationDefinition.builder()
.waviness(FormationPreset.LARGE)
.build();
FluidDepositDefinition brine = FluidDepositDefinition.builder(
new ResourceLocation("examplemod", "fluid_deposit/brine"),
new ResourceLocation("examplemod", "brine"))
.dimension(new ResourceLocation("minecraft", "overworld"), placement -> placement
Identifier.parse("examplemod:fluid_deposit/brine"),
Identifier.parse("examplemod:brine"))
.dimension(Identifier.parse("minecraft:overworld"), placement -> placement
.yRange(-48, 32)
.attempts(0.05)
.radius(4, 10)
.verticalRadius(2, 4)
.maxLobes(3)
.minSolidCover(2)
.minSolidShell(1)
.hostTag(new ResourceLocation("minecraft", "stone_ore_replaceables")))
.hostTag(Identifier.parse("minecraft:stone_ore_replaceables")))
.build();

WorldgenProvider provider = WorldgenProvider.builder("examplemod", 1)
Expand All @@ -77,7 +77,7 @@ WorldgenProvider provider = WorldgenProvider.builder("examplemod", 1)
`OilDefinition` and template `.oil(...)` remain deprecated migration adapters
for one legacy oil rule. New integrations should use `FluidDepositDefinition`.

Minecraft 26.1 biomes are dynamic registry entries. Ship them as
Minecraft 26.1.2 biomes are dynamic registry entries. Ship them as
`data/<modid>/worldgen/biome/<name>.json`, or generate that data through a
`RegistrySetBuilder`. `OreSpawnBiomes.copyAndRegister` is an optional bootstrap
helper for cloning a known biome while generating the datapack entry:
Expand All @@ -101,21 +101,21 @@ Then declare placement and materials through the same provider:

```java
WorldgenProvider provider = WorldgenProvider.builder("examplemod", 1)
.biomePalette(new ResourceLocation("examplemod", "overworld"),
new ResourceLocation("minecraft", "overworld"), palette -> palette
.biomePalette(Identifier.parse("examplemod:overworld"),
Identifier.parse("minecraft:overworld"), palette -> palette
.mode(BiomePlacementMode.REPLACE)
.scope(BiomeReplacementScope.MINECRAFT_ONLY)
.regionSize(BiomeRegionSize.LARGE)
.coverage(1.0)
.fallbackWeight(0.0)
.biome(new ResourceLocation("examplemod", "candy_plains"), biome -> biome
.biome(Identifier.parse("examplemod:candy_plains"), biome -> biome
.weight(3.0)
.similarBiome(new ResourceLocation("minecraft", "plains"))))
.dimensionMaterials(new ResourceLocation("examplemod", "overworld_materials"),
new ResourceLocation("minecraft", "overworld"), materials -> materials
.defaultFluid(new ResourceLocation("examplemod", "lemonade"))
.snowBlock(new ResourceLocation("examplemod", "icing"))
.iceBlock(new ResourceLocation("examplemod", "frozen_lemonade")))
.similarBiome(Identifier.parse("minecraft:plains"))))
.dimensionMaterials(Identifier.parse("examplemod:overworld_materials"),
Identifier.parse("minecraft:overworld"), materials -> materials
.defaultFluid(Identifier.parse("examplemod:lemonade"))
.snowBlock(Identifier.parse("examplemod:icing"))
.iceBlock(Identifier.parse("examplemod:frozen_lemonade")))
.build();
```

Expand All @@ -135,9 +135,11 @@ OreSpawnApi.createSampler(server.overworld()).ifPresent(sampler -> {
});
```

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

Custom pattern mods create a Forge `DeferredRegister<OrePatternType>` using
Expand Down
14 changes: 12 additions & 2 deletions docs/BIOMES.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ default states contain real fluids.

## Registering Biomes

Minecraft 26.1 loads biomes from the dynamic datapack registry. A provider mod
Minecraft 26.1.2 loads biomes from the dynamic datapack registry. A provider mod
can ship a biome directly at
`data/<modid>/worldgen/biome/<name>.json`. For generated data,
`OreSpawnBiomes.copyAndRegister` copies a known biome's complete builder before
Expand All @@ -114,7 +114,7 @@ Add the bootstrap to the `RegistrySetBuilder` passed to Forge's
`DatapackBuiltinEntriesProvider`. `blankAndRegister` starts from an empty
builder and is intended for advanced providers that deliberately supply every
required climate, effects, spawn, and generation field. Do not use
`DeferredRegister<Biome>` on 26.1: it runs before the live datapack biome
`DeferredRegister<Biome>` on 26.1.2: it runs before the live datapack biome
registry exists. Both bootstrap helpers only generate content; placement still
belongs in the OreSpawn provider declaration.

Expand All @@ -128,6 +128,16 @@ Biome surfaces support:
- `ceiling_block`: optional underside material;
- `filler_depth`: 0-16 blocks.

Provider surfaces run during `LOCAL_MODIFICATIONS`: after Minecraft has built
base surfaces and lakes, but before structures and vegetation. That ordering
lets OreSpawn replace the actual exposed ground while preserving later trees,
plants, authored structures, and block entities. In ceiling dimensions,
`ceiling_block` applies to the roof underside and does not replace the roof top.

Surface correction is generation-only. Installing or updating OreSpawn does
not rewrite already generated chunks; travel into new terrain to see a changed
provider surface definition.

Dimension materials support the ordinary aquifer fluid, a deep aquifer fluid
and threshold, and replacements for vanilla snow and ice. OreSpawn converts
weather products in loaded chunks and around players; it does not replace every
Expand Down
19 changes: 19 additions & 0 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,21 @@ When a control is `custom`, its value comes from `formations.custom`:
| `edge_octaves` | 1-8 | Number of boundary-detail scales |
| `continuity` | 0-1 | Proportion of formations retaining global identity |

For Stable Layers, the Edge Detail presets use these
`wavelength / amplitude / octaves` values:

| Preset | Edge detail |
|---|---:|
| Tiny | `48 / 4 / 1` |
| Small | `64 / 12 / 2` |
| Average | `96 / 24 / 3` |
| Large | `128 / 48 / 4` |
| Huge | `192 / 96 / 5` |

Average is calibrated to retain visible variation at later layer contacts.
Custom profiles keep their explicit values; these numbers are only used by the
named presets and as defaults for new Custom settings.

Cyano settings use `cyano.geome_size` (4-32767),
`cyano.rock_layer_noise` (1-32767), and `cyano.rock_layer_thickness` (1-255).
They are ignored by Sky.
Expand All @@ -90,6 +105,10 @@ weight by province. A weight of zero prevents selection in that context.
Geomes contain a non-negative `base` weight and non-negative weights for each
rock family. Biome and biome-dictionary maps multiply those geome weights.
Missing optional-mod biome IDs are ignored during baking.
Exact biome-ID maps remain effective when the target uses a dynamic biome
registry. With Stable Layers, a close contest between two geomes transitions
at a deterministic position per layer so the whole underground column does
not change on one sheer plane.

Terrain dimensions require `enabled`, `host_blocks`, and `host_tags`.
`biome_ids` and `biome_namespaces` can narrow a custom dimension. The Overworld
Expand Down
21 changes: 11 additions & 10 deletions docs/DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,11 @@ import zone.moddev.mc.orespawn.api.OreDimensionSelector;
import zone.moddev.mc.orespawn.api.OrePattern;
import zone.moddev.mc.orespawn.api.OreSpawnApi;
import zone.moddev.mc.orespawn.api.WorldgenProvider;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.resources.Identifier;
import net.minecraftforge.fml.event.lifecycle.InterModEnqueueEvent;

private void enqueueWorldgen(InterModEnqueueEvent event) {
ResourceLocation tin = new ResourceLocation("examplemod", "tin_ore");
Identifier tin = Identifier.parse("examplemod:tin_ore");
WorldgenProvider provider = WorldgenProvider.builder("examplemod", 1)
.ore(tin, ore -> ore
.retrogen(false)
Expand All @@ -100,7 +100,7 @@ private void enqueueWorldgen(InterModEnqueueEvent event) {
.quantityRange(4, 11)
.pattern(OrePattern.VEIN)
.heightDistribution(OreHeightDistribution.TRIANGLE)
.hostTag(new ResourceLocation("minecraft", "stone_ore_replaceables"))))
.hostTag(Identifier.parse("minecraft:stone_ore_replaceables"))))
.build();

OreSpawnApi.enqueue(provider);
Expand Down Expand Up @@ -198,10 +198,11 @@ bounded ore or bedrock retrogen is enabled.
6. Confirm the provider appears in `/orespawn status`.
7. Test a new world; profile edits do not rewrite already generated terrain.

OreSpawn's own standard `check` lifecycle includes a consumer-style biome
integration test. It loads a separate test provider and datapack biome, proves
the provider is active, verifies biome selection, climate and configured
surface blocks in non-flat terrain, then reopens and rechecks the same saved
world. Run `gradlew check` (or `gradlew build`, which includes it) before
publishing any change to biome registration, palettes, surfaces or profile
persistence.
OreSpawn's own standard `check` lifecycle includes a consumer-style surface
integration test. A separate test provider creates independently marked
Grass/Dirt, underwater, filler, and roof columns in open and ceiling
normal-noise dimensions. The gate verifies biome and chunk edges, late tree,
vegetation, structure and chest sentinels, the roof underside, and exact save
reload behavior. Run `gradlew check` (or `gradlew build`, which includes it)
before publishing any change to biome registration, palettes, surfaces,
feature ordering, height handling, or profile persistence.
Loading
Loading