Noise & Terrain

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, Density function, Noise settings, misode's noise, density function and 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.

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

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

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

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 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:

Try a vanilla noise
Hover the map to read a value
128 blocks, base octave
Same algorithm and octave scales as the game, with another seed, so features match in size but not in place. Noise coordinates are block coordinates here (xz_scale 1). Values are clamped to -1..1 for the colors.
val myNoise = dp.noise("my_noise") {
	baseOctave = -7
	amplitudeModifiers(1.0, 0.5, 0.25)
}
Kotlin

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:

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)
}
Kotlin

Each call returns a DensityFunctionArgument usable as an input to another node, as a 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 vals inside it. For a single node, or to return one node out of a group, dp.densityFunctionsBuilder exposes the same builders:

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)
}
Kotlin

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(...).

dp.densityFunctions {
	shift("shift_inline", noiseParameters(-3, 1.0, 1.0))
}
Kotlin
{
	"type": "minecraft:shift",
	"noise": {
		"amplitude_modifiers": [1.0, 1.0],
		"base_octave": -3,
		"octave_count": 2
	}
}
JSON

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:

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

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.

Multi-Parameter Nodes

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

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)
}
Kotlin

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:

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

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:

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

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:

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

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:

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)
}
Kotlin

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.

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)
	}
}
Kotlin

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):

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
}
Kotlin

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:

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

Noise Options

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

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 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:

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

Default Blocks

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

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:

noiseRouter {
	continents(DensityFunctions.Overworld.CONTINENTS)
	finalDensity(myTerrainDensity)
	chunkSurfaceLevel(63.0)
}
Kotlin
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" }:

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)
	}
}
Kotlin

Reference: 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.

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)
}
Kotlin

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:

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)
}
Kotlin
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.

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:

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

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

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

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

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

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

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:

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)
	}
}
Kotlin

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


Putting It Together

The worldgen overview walks through a noise, its density functions, the noise settings wiring them together, and the dimension pointing at the result.

See Also

  • Biomes - the biomes a noise generator distributes over the terrain
  • Carvers - the caves cut out of the terrain afterwards
  • Dimensions - the dimension pointing at noise settings
  • Providers - the vertical anchors used by material rule conditions
  • World Generation - overview of the worldgen system