Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Density-function nodes

Verified against Minecraft 26.3 · Reference · the forty-four node types a worldgen/density_function file may name: what each one takes, what the compiler turns it into, what range it reports, and which ids the shipped data actually writes.

Density functions is the lecture: one graph, rewritten twice and compiled once per dimension, and one kind of cache. This is the catalogue behind it — the four tables you would pause the video to read.

DensityFunctions.bootstrap registers every entry below into BuiltInRegistries.DENSITY_FUNCTION_TYPE, in this order, under the minecraft namespace. That registry is built-in and frozen at start-up, which is why adding a new kind of node takes code while adding a new graph takes a JSON file.

Forty-four node types are registered (DensityFunctions.bootstrap): one by name, then three context values, seven more by name, eleven unary transforms, four roundings, six arithmetic, and twelve last.

The table

children counts the density-function slots; each one accepts an id string, an inline object or a bare number, because every child slot is typed DensityFunction.CODEC.

idclasschildrenother fieldswhat it computes
constantConstantFunction0valuea fixed value
blend_alphaSimpleDensityFunction0—the chunk’s blend alpha from the sampling context, else 1.0
blend_offsetSimpleDensityFunction0—the chunk’s blend offset from the context, else 0.0
beardifierSimpleDensityFunction0—the chunk’s structure-terrain adjustment from the context, else 0.0
noiseNoiseFunction3, optionalnoise, xz_scale, y_scalesamples the noise a NormalNoise defines at position × scale, plus three offsets
end_outer_islandsEndIslandFunction0—the End’s simplex field of outer islands, as a density
distance_to_pointDistanceToPointFunction0point, metricthe distance from a fixed point, by one of four metrics
gradientGradientFunction0axis, tiling, from_coordinate, to_coordinate, from_value, to_valueone coordinate mapped onto a value range, clamped, repeated or mirrored past the ends
shift_aShiftNoiseFunction.ShiftA0noisedomain warp read at x, 0, z
shift_bShiftNoiseFunction.ShiftB0noisedomain warp read at z, x, 0
shiftShiftNoiseFunction.Shift0noisedomain warp read at x, y, z
absUnaryFunction1—absolute value
squareUnaryFunction1—the child squared
cubeUnaryFunction1—the child cubed
sqrtUnaryFunction1—the square root
half_negativeUnaryFunction1—identity above zero, halved below
quarter_negativeUnaryFunction1—identity above zero, quartered below
reciprocalUnaryFunction1—the reciprocal
negateUnaryFunction1—the child negated
squeezeUnaryFunction1—clamp to ±1, then a soft odd cubic
logUnaryFunction1—the natural logarithm
signUnaryFunction1—the child’s sign: −1, 0 or 1
floorRoundFunction2—rounded down to a multiple of multiple, 1 unless given
roundRoundFunction2—rounded to the nearest multiple
ceilRoundFunction2—rounded up to a multiple
truncateRoundFunction2—rounded toward zero to a multiple
addBinaryFunction2—the sum
subBinaryFunction2—the difference
mulBinaryFunction2—the product — with neither child a constant, a single sample short-circuits when the first is exactly zero
divBinaryFunction2—the quotient, with the same short-circuit
minBinaryFunction2—the minimum — with neither child a constant, a single sample skips the second when the first is already at or below the second’s lower bound
maxBinaryFunction2—the maximum, with the symmetric skip
powPowFunction2—base raised to exponent
splineSplineFunctioninside the splinesplinea CubicSpline whose coordinates are themselves density functions
lerpLerpFunction3—from first to second, by alpha
clampClampFunction1min, maxthe child, clamped
range_choiceRangeChoiceFunction3min_inclusive, max_exclusiveone of two branches, by whether the input is in range
interval_selectIntervalSelectFunction1 + a listthresholdsthe branch whose ascending threshold the input first falls below
cacheCacheFunction1—nothing of its own — a request the compiler replaces with a cache of its child; left in place it refuses to compile
blend_densityBlendDensityFunction1—the child, blended toward old-terrain density where the context holds a non-empty Blender
interpolatedInterpolatedFunction1cell_size_xz, cell_size_ythe child read at cell corners and interpolated between them
sliceSliceFunction1axis, coordinatethe child read with one coordinate pinned
find_top_surfaceFindTopSurfaceFunction2lower_bound, cell_heightsteps down in strides until the density goes positive, and returns that Y
old_blended_noiseBlendedNoise0xz_scale, y_scale, xz_factor, y_factor, smear_scale_multiplierthe pre-1.18 terrain noise, decoded unseeded

Where one class serves several ids. The three context values are the constants of one enum, SimpleDensityFunction; the eleven unary transforms are all UnaryFunction, a record of a UnaryFunction.Type and its input; the four roundings share RoundFunction, and the six arithmetic ids share BinaryFunction. In each case an enum constant carries its own codec, and the node’s codec() returns it — which is how a re-serialised graph comes back with the right id. Nothing is folded while parsing: the parse step builds each record as written, a constant operand is specialised only when the graph is compiled (into a sampler such as BinaryFunction.ConstAddSampler), and add in the JSON comes back as add.

One member of DensityFunctions is not in this table because it is not registered: DensityFunctions.HolderHolder, the in-memory stand-in for an id reference, which has no codec at all.

What the caches become

DensityFunctionCompiler.getSampler is the compile step: each dimension’s RandomState compiles a graph the first time it is asked for, and no chunk rewrites anything. A cache node is a request. Before compiling, the compiler’s rewrite inlines every DensityFunctions.HolderHolder and installs a DensityFunctionCompiler.PreparedCache in place of each cache node — one per distinct input, so two cache nodes around equal graphs share one cache. A second rewrite wraps each subgraph that ignores an axis its parent reads, constants and gradients aside, in a slice pinned at zero on that axis. A prepared cache refuses to encode, so only the parsed graph, which the rewrites copy rather than change, re-serialises.

nodecompiled tokeyed on
cachea CachingDensitySampler carrying the prepared cache’s number; its storage is a cell of the SamplerContext it runs inthe volume for a volume read — one buffer, refilled when a different DensityVolume asks — and the position for a single sample: the last block sampled, or its entry in the cached volume
interpolateda sampler that reads the child at the cell corners, a volume stepped by cell_size_xz and cell_size_y, and interpolates every block betweennothing — no value outlives the read
blend_densitya sampler that asks the context for this chunk’s Blender and blends only when there is a non-empty onenot cached

A context has cache cells only when it was built with SamplerContext.Builder.enableCaches: NoiseChunk builds one for each chunk and for each height query, and the biome step, the structure starts and checks, a caching biome resolver, the spawn search and the F3 readout build their own, and SamplerContext.EMPTY_UNCACHED passes every cached read straight through.

Three of the registered nodes above are the constants of one enum, SimpleDensityFunction, and the compiler replaces none of them: each compiles to a ContextBoundSampler that looks its key up in the sampling context on every read, which is how one compiled graph serves every chunk.

singletonreadswith nothing in the context
SimpleDensityFunction.BLEND_ALPHABlender.ALPHA_KEY, a sampler over the alphas NoiseChunk’s constructor computes at quart resolution when the blender is not emptythe constant 1.0
SimpleDensityFunction.BLEND_OFFSETBlender.OFFSET_KEY, likewisethe constant 0.0
SimpleDensityFunction.BEARDIFIERBeardifier.CONTEXT_KEY, this chunk’s Beardifierthe constant 0.0

Bounds

Every node answers DensityFunction.range, an Interval, without a position. Most take theirs from a child — cache, interpolated, slice and blend_density all do — and no registered node stores one: BinaryFunction, UnaryFunction and ClampFunction combine their children’s ranges through Interval on every call, and BlendedNoise derives its own from its parameters. The bounds of a parsed graph are the ones the compiler reads: a noise’s range comes from its definition, not its seed, and no rewrite changes a bound. What a bound is worth is the lecture’s argument; this table is which node departs from its child, and how.

idits range
add, sub, mul, div, min, maxinterval arithmetic on the two children’s rangesmul takes the least and greatest of the four products of the operands’ ends, a zero end giving zero even against infinity; div multiplies by the reciprocal’s range; min and max take the element-wise minimum and maximum. A min or max whose two ranges cannot overlap logs a warning when it is compiled, and only the side that wins whenever the inputs stay inside their declared ranges is sampled
abs, squarethe child’s ends mapped, with zero as the minimum when the child’s range contains zeroInterval.abs and Interval.square map both ends
reciprocal±infinity whenever the child’s range straddles zerothe reciprocal has no finite bound across zero
clampthe child’s range, clamped into min and maxa child already inside them keeps its own, narrower range
find_top_surfaceits lower_bound and its upper bound’s maximumY coordinates — this node’s range is on a different scale from every other row
noiseits NormalNoise’s, fixed when the noise definition is decodedthe three shifts are ignored; the sixty-four shipped noise definitions come out between ±0.87 and ±5.73

DensityFunctions.HolderHolder, off the table, asks its target, and throws while its holder is unbound; forward references parse anyway, because nothing asks a node for its bound until the graph is compiled.

What vanilla actually uses

Fifty-five JSON files ship under worldgen/density_function — four at the top level, the rest in a directory each for the End, the Nether, the overworld and the overworld’s two preset variants — and between them they use thirty-three of the forty-four ids. The seven Registries.NOISE_SETTINGS files, the recipes terrain reads — two of them, caves and floating_islands, named by no shipped world preset — write no id the density-function files lack. All seven final densities end by adding beardifier, three of them in a file and four inline.

That leaves eleven ids vanilla data never writes. constant is never written as a typed object, because a bare number is one. Nine are maths on a value: sqrt, reciprocal, log, sign, pow and the four roundings. And shift — the three-dimensional domain warp — is used by nothing: ShiftNoiseFunction.ShiftA and ShiftNoiseFunction.ShiftB cover the two two-dimensional warps vanilla wants.

For a 1.21-era reader. NoiseChunk.wrapNew is gone: DensityFunctionCompiler compiles each graph once per dimension, and each chunk supplies a SamplerContext. flat_cache, cache_2d, cache_once and cache_all_in_cell are gone, and cache does their work. shifted_noise is noise with shift fields, y_clamped_gradient is gradient on the Y axis, end_islands is end_outer_islands with the central island left to distance_to_point, and minValue and maxValue are DensityFunction.range.


Rules: names, never code · how the system works, not how the code reads · newest version only · every backticked name passes tools/verify_names.py.