# Noise & Terrain

> Shape Minecraft terrain from Kotlin - noise definitions, density functions, noise routers and material rules, fully type-safe with Kore.

Updated 2026-10-23. Canonical: https://kore.ayfri.com/docs/data-driven/worldgen/noise

Noise, density functions and noise settings are the three layers that shape Minecraft terrain. They build on each other in that order:

1. **Noise definitions** (`worldgen/noise`) - raw Perlin noise, described by octaves and amplitudes.
2. **Density functions** (`worldgen/density_function`) - composable math nodes that sample noises and combine them into a density value for
   any 3D position.
3. **Noise settings** (`worldgen/noise_settings`) - the terrain configuration a dimension points at: world bounds, default blocks, the noise
   router wiring density functions to generation roles, and the material rules painting the top layers.

Minecraft decides whether a position is solid from the density value the router's `finalDensity` produces: positive density is a block,
negative density is air (or fluid, below sea level).

References: [Noise](https://minecraft.wiki/w/Noise), [Density function](https://minecraft.wiki/w/Density_function),
[Noise settings](https://minecraft.wiki/w/Noise_settings), misode's [noise](https://misode.github.io/worldgen/noise/),
[density function](https://misode.github.io/worldgen/density-function/) and [noise settings](https://misode.github.io/worldgen/noise-settings/)
generators

---

## Noise Definitions

A noise definition configures one Perlin noise instance. **Octaves** layer noise samples at different scales - low octaves make large
features (continents), high octaves add fine detail (small bumps).

`baseOctave = -7` starts at the 2⁷ = 128 block scale. Each following octave doubles in frequency, and `amplitudeModifiers` weights each one,
usually decreasing so detail never overwhelms the base shape. `octaveCount` is the number of octaves, which the game requires to match the
number of `amplitudeModifiers`: left unset, it's written as that number.

```kotlin
val hills = dp.noise("hills") {
	baseOctave = -5
	amplitudeModifiers = listOf(1.0, 0.5, 0.25)
}
```

`amplitudeModifiers(...)` is the vararg form of the same list:

```kotlin
val hills = dp.noise("hills") {
	baseOctave = -5
	amplitudeModifiers(1.0, 0.5, 0.25)
}
```

Only `baseOctave` is always written. Leaving `amplitudeModifiers` out gives every octave a modifier of `1.0`, so
`noise("flat") { baseOctave = -3; octaveCount = 2 }` writes `{"base_octave": -3, "octave_count": 2}`.

A file name holding slashes lands in subfolders, so `noise("cave/entrance")` writes
`data/<namespace>/worldgen/noise/cave/entrance.json`.

| Field                | Type            | Description                                                                                                                   | Vanilla `temperature`                      |
|----------------------|-----------------|-------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|
| `amplitudeModifiers` | `List<Double>?` | Amplitude multiplier per octave, from `0.0` to `1000000.0`, `0.0` skipping one, at most 32. Absent means `1.0` for every octave. | `listOf(1.5, 0.0, 1.0, 0.0, 0.0, 0.0)`     |
| `baseAmplitude`      | `Double?`       | Amplitude every octave is scaled by, from `0.0` to `1000000.0`. Defaults to `1.0`.                                            | `1.2453007926713473`                       |
| `baseOctave`         | `Int`           | Starting octave from `-32` to `32`, negative values are larger scale. Lower means larger features.                            | `-10`                                      |
| `normalize`          | `Boolean?`      | Whether the sampled value is normalized. Defaults to `true`. The deprecated `"legacy"` normalization isn't expressible.      | not written                                |
| `octaveCount`        | `Int?`          | Number of octaves, from `1` to `32`. Defaults to the number of `amplitudeModifiers`, or `1`.                                  | `6`                                        |

The block state providers of [features](https://kore.ayfri.com/docs/data-driven/worldgen/features.md#noise-based) take the same fields inline through
`NoiseParameters`, built with `noiseParameters(baseOctave, vararg amplitudeModifiers)`.

Every call returns a `NoiseArgument`. Vanilla noises are available as `Noises.AQUIFER_BARRIER`, `Noises.CONTINENTALNESS` and so on, and can be
passed anywhere a `NoiseArgument` is expected.

### Octaves and Scale

Octave `n` has a wavelength of 2⁻ⁿ blocks, so `-7` is 128 blocks and `-3` is 8 blocks. Octave `i` of the list sits at `baseOctave + i`, double the
frequency of the previous one. Before its modifier is applied, each octave weighs half the previous one: three octaves weigh 4/7, 2/7 and 1/7.
The game adds two Perlin noises together, the second sampled at the coordinates multiplied by `1.0181268882175227`.

| baseOctave | Wavelength   | Vanilla noise                | Read in vanilla at                                        |
|------------|--------------|------------------------------|-----------------------------------------------------------|
| `-16`      | 65536 blocks | `jagged`                     | `xz_scale` 1500                                           |
| `-10`      | 1024 blocks  | `temperature`                | `xz_scale` 0.25                                           |
| `-9`       | 512 blocks   | `continentalness`, `erosion` | `xz_scale` 0.25                                           |
| `-8`       | 256 blocks   | `vegetation`                 | `xz_scale` 0.25                                           |
| `-7`       | 128 blocks   | `ridge`, `cave_entrance`     | `xz_scale` 0.25 for `ridge`, 0.75 for `cave_entrance`     |
| `-6`       | 64 blocks    | `surface`                    | none, `noise_threshold` has no scale                      |
| `-3`       | 8 blocks     | `offset`                     | coordinates divided by 4, through `shift_a` and `shift_b` |

Density functions scale the coordinates they sample with (`xzScale`), so the in-game size is the wavelength divided by that scale: `temperature`
at `xz_scale` 0.25 repeats every 4096 blocks.

The explorer below samples a noise with the game's algorithm, from your octaves or a vanilla noise, as a map, a terrain slice or one curve per
octave, with the matching Kotlin and JSON:

---

## Density Functions

Density functions are math nodes evaluated per position. Each one lives in its own file and holds exactly one node type, so composing them
means referencing other density function files. The game evaluates density functions and noises in single precision, so setups
that depend on exact rounding can give slightly different terrain than the same values in double precision.

Declare them inside a `densityFunctions { ... }` block, one builder call per file:

```kotlin
dp.densityFunctions {
	val base = constant("base", 1.0)
	val hills = noise("hills", Noises.CONTINENTALNESS, xzScale = 0.25, yScale = 0.0)

	abs("abs_hills", hills)
	add("terrain", base, hills)
}
```

Each call returns a `DensityFunctionArgument` usable as an input to another node, as a [noise router](#noise-router) field, or anywhere else
a density function is expected. Vanilla nodes come from the generated `DensityFunctions` object, e.g.
`DensityFunctions.Overworld.BASE_3D_NOISE`.

The block itself returns the builder scope, not the last node, so arguments you need later are captured in `val`s inside it. For a single
node, or to return one node out of a group, `dp.densityFunctionsBuilder` exposes the same builders:

```kotlin
dp.densityFunctionsBuilder.abs("abs_base_3d_noise", DensityFunctions.Overworld.BASE_3D_NOISE)

val terrain = with(dp.densityFunctionsBuilder) {
	val gradient = gradient("gradient") { axis = Axis.Y; fromCoordinate = 0; toCoordinate = 128; fromValue = 1.0; toValue = -1.0 }
	add("terrain", DensityFunctions.Overworld.BASE_3D_NOISE, gradient)
}
```

Most builders accept either a `Double` or a `DensityFunctionArgument` for their inputs, in every combination. `constant` takes a `Double`
only, and `noise`, `shift`, `shiftA` and `shiftB` take either a `NoiseArgument` or inline noise parameters built with
`noiseParameters(...)`, since they sample a noise directly. Their `noise` property is a `NoiseOrParameters`, set with `noiseOrParameters(...)`.

```kotlin
dp.densityFunctions {
	shift("shift_inline", noiseParameters(-3, 1.0, 1.0))
}
```

```json
{
	"type": "minecraft:shift",
	"noise": {
		"amplitude_modifiers": [1.0, 1.0],
		"base_octave": -3,
		"octave_count": 2
	}
}
```

The JSON fields follow the game: single-input nodes (`abs`, `cube`, `negate`, `reciprocal`...) write `input`, two-input nodes (`add`, `div`,
`max`, `min`, `mul`, `sub`) write `left` and `right`, `constant` writes `value` and the shift nodes write `noise`. The same names are the properties of the
node classes.

### Density Function Types

| Type                   | Builder                   | Description                                                                   |
|------------------------|---------------------------|-------------------------------------------------------------------------------|
| `abs`                  | `abs(...)`                | Absolute value of the input.                                                  |
| `add`                  | `add(...)`                | Sums two inputs.                                                              |
| `beardifier`           | `beardifier(...)`         | Blends nearby terrain into structures. No parameters.                         |
| `blend_alpha`          | `blendAlpha(...)`         | Smooths transitions between chunk generation versions. No parameters.         |
| `blend_density`        | `blendDensity(...)`       | Blends the input across chunk generation version transitions.                 |
| `blend_offset`         | `blendOffset(...)`        | Supports legacy chunk compatibility blending. No parameters.                  |
| `cache`                | `cache(...)`              | Caches the input once per block position, even if referenced multiple times.  |
| `ceil`                 | `ceil(...)`               | Rounds the input up to a multiple of `multiple` (1 by default).               |
| `clamp`                | `clamp(...)`              | Restricts the input between `min` and `max`.                                  |
| `constant`             | `constant(...)`           | A fixed value, ignoring the input position.                                   |
| `cube`                 | `cube(...)`               | Raises the input to the power of 3 (x³).                                      |
| `distance_to_point`    | `distanceToPoint(...)`    | Distance from the sampled position to a block position, in one of 4 metrics.  |
| `div`                  | `div(...)`                | Divides the first input by the second.                                        |
| `end_outer_islands`    | `endOuterIslands(...)`    | Samples the End's outer islands, without the central island. No parameters.   |
| `find_top_surface`     | `findTopSurface(...)`     | Scans a column for the topmost position where a density is above zero.        |
| `floor`                | `floor(...)`              | Rounds the input down to a multiple of `multiple` (1 by default).             |
| `gradient`             | `gradient(...)`           | Linear gradient between two values along any axis, with a tiling mode.        |
| `half_negative`        | `halfNegative(...)`       | Halves the input when negative, otherwise leaves it unchanged.                |
| `interpolated`         | `interpolated(...)`       | Interpolates the input across grid cells of `cellSizeXz` x `cellSizeY` blocks. |
| `interval_select`      | `intervalSelect(...)`     | Picks one of several functions from where the input falls between thresholds. |
| `lerp`                 | `lerp(...)`               | Interpolates from `first` to `second` by `alpha`.                             |
| `log`                  | `log(...)`                | Takes the logarithm of the input.                                             |
| `max`                  | `max(...)`                | Larger of two inputs.                                                         |
| `min`                  | `min(...)`                | Smaller of two inputs.                                                        |
| `mul`                  | `mul(...)`                | Multiplies two inputs.                                                        |
| `negate`               | `negate(...)`             | Flips the sign of the input.                                                  |
| `noise`                | `noise(...)`              | Samples a noise ID or inline parameters, scaled and optionally shifted.       |
| `old_blended_noise`    | `oldBlendedNoise(...)`    | Legacy blended noise used before the 1.18 terrain rewrite.                    |
| `pow`                  | `pow(...)`                | Raises `base` to the power of `exponent`.                                     |
| `quarter_negative`     | `quarterNegative(...)`    | Quarters the input when negative, otherwise leaves it unchanged.              |
| `range_choice`         | `rangeChoice(...)`        | Picks between two inputs based on whether a value falls within a range.       |
| `reciprocal`           | `reciprocal(...)`         | Reciprocal (1 / x) of the input.                                              |
| `round`                | `round(...)`              | Rounds the input to the nearest multiple of `multiple` (1 by default).        |
| `shift`                | `shift(...)`              | Samples a noise ID or inline parameters at `(x/4, y/4, z/4)`, scaled up by 4. |
| `shift_a`              | `shiftA(...)`             | Samples a noise ID or inline parameters at `(x/4, 0, z/4)`, scaled up by 4.   |
| `shift_b`              | `shiftB(...)`             | Samples a noise ID or inline parameters at `(z/4, x/4, 0)`, scaled up by 4.   |
| `sign`                 | `sign(...)`               | Outputs the sign of the input.                                                |
| `slice`                | `slice(...)`              | Evaluates the input with one axis coordinate fixed, flattening 3D to 2D.      |
| `spline`               | `spline(...)`             | Cubic spline interpolating control points over a coordinate.                  |
| `sqrt`                 | `sqrt(...)`               | Takes the square root of the input.                                           |
| `square`               | `square(...)`             | Raises the input to the power of 2 (x²).                                      |
| `squeeze`              | `squeeze(...)`            | Clamps the input to [-1, 1], then applies `x/2 - x³/24`.                      |
| `sub`                  | `sub(...)`                | Subtracts the second input from the first.                                    |
| `truncate`             | `truncate(...)`           | Rounds the input towards zero to a multiple of `multiple` (1 by default).     |

All builders live in `io.github.ayfri.kore.features.worldgen.densityfunction.types` and take the file's name as their first argument.

`lerp` takes the `alpha` density function, then `first` and `second` as a `Double` or a `DensityFunctionArgument`. `floor`, `round`, `ceil`
and `truncate` snap `input / multiple` to an integer and multiply back, so `multiple` is the step. It is optional, defaults to `1` and isn't
written when left out. `round` rounds halves up, and `truncate` rounds towards zero where `floor` rounds down:

```kotlin
dp.densityFunctions {
	val snapped = floor("snapped_depth", DensityFunctions.Overworld.DEPTH, 0.25)
	lerp("blended_depth", DensityFunctions.Overworld.CONTINENTS, snapped, 1.0)
}
```

`blend_alpha`, `blend_offset` and `blend_density` are internal to vanilla generation and are not meant to
be referenced from a datapack, even though the builders exist. `beardifier` is added to the `finalDensity` by hand, see the
[noise router](#noise-router).

### Multi-Parameter Nodes

Nodes with more than a couple of inputs take a builder block instead of a long positional list:

```kotlin
dp.densityFunctions {
	rangeChoice("caves") {
		input(DensityFunctions.Overworld.BASE_3D_NOISE)
		minInclusive = -0.3
		maxExclusive = 0.3
		whenInRange(-1.0)
		whenOutOfRange(1.0)
	}

	gradient("depth_bias") {
		axis = Axis.Y
		fromCoordinate = -64
		toCoordinate = 320
		fromValue = 1.0
		toValue = -1.0
	}

	noise("shifted_hills", Noises.CONTINENTALNESS, xzScale = 0.25, yScale = 0.0) {
		shiftX(DensityFunctions.SHIFT_X)
		shiftZ(DensityFunctions.SHIFT_Z)
	}

	intervalSelect("cave_shape") {
		input(DensityFunctions.Overworld.BASE_3D_NOISE)
		thresholds(-0.3f, 0.3f)
		function(DensityFunctions.Nether.BASE_3D_NOISE)
		function(0.0)
		function(DensityFunctions.End.BASE_3D_NOISE)
	}

	findTopSurface("top_surface", DensityFunctions.Overworld.BASE_3D_NOISE, DensityFunctions.Y, lowerBound = -64, cellHeight = 8)
}
```

`noise` offsets the sampled coordinates with `shiftX`, `shiftY` and `shiftZ`, each a `Double` or a density function. A shift left out isn't
written and counts as a constant `0`. Vanilla passes the `shift_x` and `shift_z` density functions to bend its climate noises (`overworld/temperature`, `overworld/vegetation`, `overworld/continents`...).

`interpolated` has no default cell sizes: `cellSizeXz` and `cellSizeY` are required, at least `1`. Vanilla's Overworld uses `4` and `8`
and the End `8` and `4`:

```kotlin
dp.densityFunctions {
	interpolated("smooth_terrain", DensityFunctions.Overworld.BASE_3D_NOISE, cellSizeXz = 4, cellSizeY = 8)
}
```

`intervalSelect` holds one more function than it has thresholds: the first function applies below the first threshold, each following one
applies between two consecutive thresholds, and the last one applies at or above the last threshold. Thresholds must be sorted ascending.

`gradient` goes linearly from `fromValue` to `toValue` as the `axis` coordinate goes from `fromCoordinate` to `toCoordinate`, which have to
differ. `tiling` sets the value past that range: `CLAMP_TO_EDGE` (the default, left out of the file) holds the edge value, `REPEAT` restarts
the gradient and `MIRRORED_REPEAT` goes back and forth:

```kotlin
dp.densityFunctions {
	gradient("x_stripes") {
		axis = Axis.X
		fromCoordinate = 0
		toCoordinate = 16
		fromValue = -1.0
		toValue = 1.0
		tiling = TilingMode.MIRRORED_REPEAT
	}
}
```

`distanceToPoint` measures the distance to a block position with a `DistanceMetric` (`EUCLIDEAN` by default, `EUCLIDEAN_SQUARED`,
`MANHATTAN` or `CHEBYSHEV`), and `slice` evaluates a function with one axis fixed. Chained, they give a distance field that is the same at
every Y, the base of a disc like the End's main island:

```kotlin
dp.densityFunctions {
	val distance = distanceToPoint("end/distance", 0, 0, 0)
	slice("end/disc", Axis.Y, 0, distance)
}
```

### Splines

A `spline` interpolates control points over a coordinate density function. Points hold either a constant value or a nested spline, which is
how vanilla layers continentalness, erosion and ridges into a single terrain offset:

```kotlin
dp.densityFunctions {
	spline("offset", DensityFunctions.Overworld.CONTINENTS) {
		point(-1.1f, 0.044f)
		point(-0.51f, DensityFunctions.Overworld.EROSION, derivative = 0.5f) {
			point(-0.6f, 1.0f)
			point(0.5f, -1.0f)
		}
	}

	spline("flat", 0.5f)
}
```

Points are ordered by increasing `location`, and `derivative` sets the slope of the curve at that point.

Fields typed `DensityFunctionOrDouble` are set through their matching setter function (`input(...)`, `whenInRange(...)`, `shiftX(...)`),
overloaded for both a `Double` and a `DensityFunctionArgument`. Assigning the field directly needs an explicit
`densityFunctionOrDouble(...)` wrapper.

---

## Noise Settings

Noise settings are the complete terrain configuration for a dimension. A dimension references one through its noise generator.

```kotlin
val terrain = dp.noiseSettings("custom_terrain") {
	seaLevel = 63

	noiseOptions(minY = -64, height = 384)

	defaultBlock(Blocks.STONE)
	defaultFluid(Blocks.WATER) { this["level"] = "0" }

	aquifers {
		barrier(DensityFunctions.Overworld.BASE_3D_NOISE)
		surfaceLevel(DensityFunctions.Overworld.PRELIMINARY_SURFACE_LEVEL)
	}

	noiseRouter {
		finalDensity(DensityFunctions.Overworld.SLOPED_CHEESE)
	}

	materialRules {
		oreVein(Blocks.COPPER_ORE, Blocks.RAW_COPPER_BLOCK, Blocks.GRANITE, DensityFunctions.Overworld.OreVein.COPPER_DENSITY) {
			fillerGap(DensityFunctions.Overworld.OreVein.GAP)
			richness(DensityFunctions.Overworld.OreVein.RICHNESS)
		}
		block(Blocks.STONE)
	}
}
```

### Properties

Defaults below are Kore's, which are not always vanilla's - the Overworld for instance ships with `aquifers`.

| Property               | Type                                                     | Kore default       | Description                                             |
|------------------------|----------------------------------------------------------|--------------------|---------------------------------------------------------|
| `aquifers`             | `Aquifers?`                                              | `null`             | Local water/lava pockets instead of a flat sea          |
| `debugFunctions`       | `List<DebugFunctionEntry>?`                              | `null`             | Density functions shown in the generation debug entry   |
| `defaultBlock`         | `BlockState`                                             | `stone`            | Block placed where density is positive                  |
| `defaultFluid`         | `BlockState`                                             | `water[level=0]`   | Fluid placed below `seaLevel` where density is negative |
| `disableMobGeneration` | `Boolean`                                                | `false`            | Skips mob spawning during chunk generation              |
| `legacyRandomSource`   | `Boolean`                                                | `false`            | Uses the pre-1.18 random source                         |
| `materialRule`         | `MaterialRule`                                           | `bandlands`        | Rule picking the block of each position, inline or ID   |
| `noise`                | `NoiseOptions`                                           | `(-64, 384)`       | Vertical range of the terrain                           |
| `noiseRouter`          | `NoiseRouter`                                            | all zeroes         | Density functions wired to generation roles             |
| `seaLevel`             | `Int`                                                    | `63`               | Y level the default fluid fills up to                   |
| `spawnTarget`          | `List<Map<DensityFunctionArgument, DoubleOrDoublePair>>` | empty              | Climate points the world spawn prefers                  |

### Spawn Target

`spawnTargetPoint` adds one point of climate space: the density functions it lists, each set to a value or a `min..max` range. The world
spawn prefers where all of them match, but it is a preference, not a filter: every candidate is scored by how far its climate is outside
the closest point (lower is better), plus its distance from `0, 0`, so the search still ends somewhere when nothing matches. Several points
describe several preferred areas. Narrow a range to steer the spawn, widen it to `-1.0..1.0` to ignore that function. Vanilla's overworld
prefers land (`continents` from `-0.11`) away from the `ridges` values near `0`, so it has one point per side (only the first is shown):

```kotlin
spawnTargetPoint {
	this[DensityFunctions.Overworld.CONTINENTS] = -0.11..1.0
	this[DensityFunctions.Overworld.EROSION] = -1.0..1.0
	this[DensityFunctions.Overworld.RIDGES] = -1.0..-0.16
	this[DensityFunctions.Overworld.TEMPERATURE] = -1.0..1.0
	this[DensityFunctions.Overworld.VEGETATION] = -1.0..1.0
}
```

Each point is a JSON object keyed by density function ID, and a lone number, such as `this[DensityFunctions.Overworld.DEPTH] = 0.5`,
replaces a range:

```json
"spawn_target": [
	{
		"minecraft:overworld/continents": [-0.11, 1.0],
		"minecraft:overworld/ridges": [-1.0, -0.16]
	}
]
```

### Noise Options

```kotlin
noiseOptions(
	minY = -64,   // Lowest generated Y level, -2032 to 2031, multiple of 16
	height = 384  // Total height, 0 to 4064, multiple of 16
)
```

`minY + height` must stay within the dimension type's own vertical range. The sampling resolution isn't part of the noise options: each
[`interpolated`](#density-function-types) density function sets its own `cellSizeXz` and `cellSizeY`.

### Debug Functions

`debugFunction(function, label)` adds a density function the game samples at the player's position and lists under `label` in the
`chunk_generation_stats` debug entry. `function` is a `DensityFunctionArgument` or a `Double`, and without any `debugFunction` call the
field isn't written. Vanilla's overworld lists nine, labeled with one letter for final density (`N`), temperature (`T`), vegetation (`V`),
continents (`C`), erosion (`E`), depth (`D`) and ridges (`W`), plus `PV` for an inline peaks and valleys expression and `PS` for
`overworld/preliminary_surface_level`:

```kotlin
debugFunction(DensityFunctions.Overworld.FINAL_DENSITY, "N")
debugFunction(DensityFunctions.Overworld.CONTINENTS, "C")
```

### Default Blocks

```kotlin
defaultBlock(Blocks.DEEPSLATE)
defaultFluid(Blocks.LAVA) { this["level"] = "0" }
```

Both take an optional block-state property block, and both accept a plain `Map<String, String>` instead.

### Noise Router

The noise router maps density functions to generation roles. Every field is a `DensityFunctionOrDouble`, so set it with the matching
function, which is overloaded for both a `Double` and a `DensityFunctionArgument`:

```kotlin
noiseRouter {
	continents(DensityFunctions.Overworld.CONTINENTS)
	finalDensity(myTerrainDensity)
	chunkSurfaceLevel(63.0)
}
```

| Field                | Role                                                       |
|----------------------|------------------------------------------------------------|
| `chunkSurfaceLevel`  | Estimated surface height of the chunk, used by generation  |
| `continents`         | Continentalness climate parameter, ocean vs inland         |
| `depth`              | Depth climate parameter, distance below the surface        |
| `erosion`            | Erosion climate parameter, flat vs mountainous             |
| `finalDensity`       | Final solid/air decision for every position                |
| `ridges`             | Weirdness climate parameter, driving ridged terrain        |
| `temperature`        | Temperature climate parameter for biome placement          |
| `vegetation`         | Humidity climate parameter for biome placement             |

Unset fields serialize as `0.0`, which means flat, featureless terrain - so a hand-written router usually starts from the vanilla density
functions in the generated `DensityFunctions` object.

`finalDensity` no longer adds the `minecraft:beardifier` on its own, so terrain doesn't blend into structures unless the density function
does it. A pack reusing vanilla terrain adds it, as the vanilla overworld does with an `add` whose `right` is `{ "type": "minecraft:beardifier" }`:

```kotlin
val terrain = with(dp.densityFunctionsBuilder) {
	val beardifier = beardifier("beardifier")
	add("terrain_with_structures", DensityFunctions.Overworld.SLOPED_CHEESE, beardifier)
}

dp.noiseSettings("my_terrain") {
	noiseRouter {
		finalDensity(terrain)
	}
}
```

Reference: [Noise router](https://minecraft.wiki/w/Noise_settings#Noise_router)

### Aquifers

`aquifers { }` carves the local water and lava pockets. Without it, the terrain is only flooded up to `seaLevel`, like the Nether and the End
in vanilla. Every field is a `DensityFunctionOrDouble`, set with the matching function, overloaded for a `Double` and a
`DensityFunctionArgument`, and unset fields serialize as `0.0`.

| Field                   | Role                                                       |
|-------------------------|------------------------------------------------------------|
| `barrier`               | Noise separating the fluid pockets from the stone          |
| `exclusion`             | Density below which no aquifer generates                   |
| `fluidLevelFloodedness` | How often the aquifers are filled                          |
| `fluidLevelSpread`      | How much the fluid level of the aquifers varies            |
| `lava`                  | Whether an aquifer holds lava instead of water             |
| `surfaceLevel`          | Surface height the aquifers are placed relative to         |

### Material Rules

Material rules decide which blocks replace the top layers of terrain. They are evaluated in order, and the first rule producing a block wins.

```kotlin
materialRules {
	condition(biomes(Biomes.DESERT)) {
		block(Blocks.SAND)
	}

	condition(stoneDepth(Surface.FLOOR, addSurfaceDepth = true)) {
		condition(water(offset = -1, surfaceDepthMultiplier = 0)) {
			block(Blocks.GRASS_BLOCK)
		}
		block(Blocks.DIRT)
	}

	block(Blocks.STONE)
}
```

Every builder below lives on the `materialRules` scope, so nothing leaks into the global namespace and the IDE completes the whole rule set
from inside the block.

| Rule        | Builder                    | Description                                  |
|-------------|----------------------------|----------------------------------------------|
| `bandlands` | `bandlands()`              | Vanilla badlands terracotta banding.         |
| `block`     | `block(block) { }`         | Places a block state.                        |
| `condition` | `condition(condition) { }` | Runs nested rules when the condition passes. |
| `ore_vein`  | `oreVein(...) { }`         | Places a large vein of ore and raw ore.      |
| `sequence`  | `sequence { }`             | Groups rules, first match wins.              |

A `condition` block holding a single rule serializes as that rule, several rules are wrapped in a `sequence`.

#### Ore Veins

`oreVein(oreBlock, rawOreBlock, fillerBlock, density)` places one large vein where the rule is evaluated, repeatable. `density` is required,
a density function or a constant: a `0.0` density replaces nothing. `rawOreChance` is the chance, from `0.0` to `1.0`, of a raw ore block
instead of the ore (default `0.02`, like vanilla). The block takes the other density functions of the vein, unset ones serializing as `0.0`:

```kotlin
materialRules {
	oreVein(Blocks.DEEPSLATE_IRON_ORE, Blocks.RAW_IRON_BLOCK, Blocks.TUFF, DensityFunctions.Overworld.OreVein.IRON_DENSITY, rawOreChance = 0.02f) {
		fillerGap(DensityFunctions.Overworld.OreVein.GAP)
		richness(DensityFunctions.Overworld.OreVein.RICHNESS)
	}
	block(Blocks.STONE)
}
```

| Field       | Role                                                                                    |
|-------------|-----------------------------------------------------------------------------------------|
| `density`   | Chance, from `0.0` to `1.0`, that the vein replaces a block, `0.0` or lower replaces nothing |
| `fillerGap` | Overrides `richness` when positive: the filler block is always placed                   |
| `richness`  | Chance of an ore block instead of a filler block in the vein                            |

Vanilla keeps its veins in the `overworld/copper_ore_vein` and `overworld/iron_ore_vein` material rule files, referenced from the
`overworld` and `overworld_caves` rules. A pack declares its own the same way, in a [`materialRule` file](#material-rule-and-condition-files).

#### Conditions

Condition builders live on the `materialRules` scope too, so they resolve without extra imports inside the block.

| Condition                   | Builder                                                      | Description                                         |
|-----------------------------|--------------------------------------------------------------|-----------------------------------------------------|
| `above_preliminary_surface` | `AbovePreliminarySurface`                                    | Position is above the router's preliminary surface. |
| `biome`                     | `biomes(...)`                                                | Position is in one of the listed biomes.            |
| `hole`                      | `Hole`                                                       | Column has a surface depth of 0.                    |
| `noise_threshold`           | `noiseThreshold(noise, minThreshold, maxThreshold) { is3d }` | Noise value falls within a range, in 2D or 3D.      |
| `not`                       | `not(condition)`                                             | Inverts another condition.                          |
| `steep`                     | `Steep`                                                      | Position is on a steep north or east facing slope.  |
| `stone_depth`               | `stoneDepth(surfaceType, offset, ...)`                       | Depth below the floor or ceiling of the terrain.    |
| `temperature`               | `Temperature`                                                | Biome is cold enough for snowfall.                  |
| `vertical_gradient`         | `verticalGradient(name, trueAtAndBelow, falseAtAndAbove)`    | Random blend between two Y anchors.                 |
| `water`                     | `water(offset, surfaceDepthMultiplier, addStoneDepth)`       | Position is above the local water level.            |
| `y_above`                   | `yAbove(anchor, surfaceDepthMultiplier, addStoneDepth)`      | Position is above a Y anchor, exclusive.            |

`noiseThreshold` evaluates its noise in 2D (X/Z) by default. Set `is3d = true` to evaluate it in 3D instead:

```kotlin
condition(noiseThreshold(hills) {
	minThreshold = -0.5
	maxThreshold = 0.5
	is3d = true
}) {
	block(Blocks.GRANITE)
}
```

`AbovePreliminarySurface`, `Hole`, `Steep` and `Temperature` take no arguments, so they are passed directly as objects:

```kotlin
condition(AbovePreliminarySurface) {
	block(Blocks.GRASS_BLOCK)
}

condition(not(Steep)) {
	block(Blocks.GRAVEL)
}
```

The vertical anchors used by `yAbove` and `verticalGradient` are built with `absolute(y)`, `aboveBottom(offset)` or
`belowTop(offset)`, all scoped to the `materialRules { }` block:

```kotlin
condition(verticalGradient("bedrock_floor", aboveBottom(0), aboveBottom(5))) {
	block(Blocks.BEDROCK)
}
```

#### Material Rule and Condition Files

Rules and conditions can live in their own files, `worldgen/material_rule/<name>.json` and `worldgen/material_condition/<name>.json`, and
be used by ID wherever an inline one fits. `materialRule` holds the rules of its block, a single one as-is and several in a `sequence`,
`materialCondition` the condition its block returns:

```kotlin
val highlands = materialCondition("highlands") { yAbove(absolute(120)) }

val peaks = materialRule("peaks") {
	condition(reference(highlands)) { block(Blocks.SNOW_BLOCK) }
}

noiseSettings("my_settings") {
	materialRules {
		rule(peaks)
		rule(MaterialRules.Overworld.SURFACE)
	}
}
```

`rule(id)` appends a rule file, `reference(id)` turns a condition file into a condition, and `materialRule(id)` sets the noise settings'
rule to a file, like vanilla's `materialRule(MaterialRules.Overworld)`. The vanilla files are typed in the generated `MaterialRules` and
`MaterialConditions`.

Reference: [Material rule](https://minecraft.wiki/w/Surface_rule)

---

## Putting It Together

The [worldgen overview](https://kore.ayfri.com/docs/data-driven/worldgen.md#a-complete-custom-dimension) walks through a noise, its density functions, the noise
settings wiring them together, and the dimension pointing at the result.

## See Also

- [Biomes](https://kore.ayfri.com/docs/data-driven/worldgen/biomes.md) - the biomes a noise generator distributes over the terrain
- [Carvers](https://kore.ayfri.com/docs/data-driven/worldgen/carvers.md) - the caves cut out of the terrain afterwards
- [Dimensions](https://kore.ayfri.com/docs/data-driven/worldgen/dimensions.md) - the dimension pointing at noise settings
- [Providers](https://kore.ayfri.com/docs/data-driven/worldgen/providers.md) - the vertical anchors used by material rule conditions
- [World Generation](https://kore.ayfri.com/docs/data-driven/worldgen.md) - overview of the worldgen system
