Biomes, noise & density

The biome definition and the noise machinery beneath it: noise settings, density functions, surface rules, and custom noise β€” the math that actually shapes terrain.

The biome definition

A biome file (data/<ns>/worldgen/biome/<id>.json) controls climate (for grass/foliage tint and rain/snow), client-side effects (colors, fog, particles, sound/music), mob spawning, cave carvers, and the list of placed features generated in each of the 11 generation steps.

FieldTypeNotes
has_precipitation 1.19.4+boolWhether weather (rain/snow) falls at all. Replaced the old precipitation enum.
temperaturefloatGrass/foliage color influence; decides rain vs. snow (below ~0.15 → snow) and gameplay (e.g. water freezing).
temperature_modifierstring"none" (default) or "frozen" β€” adds noisy warm patches (used by frozen oceans).
downfallfloatHumidity for grass/foliage color; historically affected rain intensity.
effectsobjectClient rendering (see below).
spawnersobjectPer-category spawn lists (required; may be empty).
spawn_costsobjectPer-entity spawn density budget (required; may be empty).
carversarray/tagCave/ravine carvers (#tag or list of carver IDs).
featuresarray of 11 arraysPlaced features per generation step (indices 0–10; see below).
creature_spawn_probabilityfloat (optional)Chunk creature-spawn chance (0–0.9999).
⚠ Version differences
  • precipitation enum → has_precipitation boolean (1.19.4 / pack_format 12). Old: "precipitation": "none" | "rain" | "snow". New: "has_precipitation": true|false, and rain-vs-snow is decided purely by temperature. A 1.21 file must use has_precipitation.
  • dry_foliage_color added in 1.21.5 (leaf litter) β€” omit for ≤1.21.4.
  • Older packs used sky_color/grass_color_modifier identically; the effects object is otherwise stable across 1.19–1.21.

Biome effects

The effects object drives all client-side rendering.

FieldTypeNotes
fog_colorint (decimal RGB)Required.
water_colorintRequired (default 4159204). Water + cauldron color.
water_fog_colorintRequired. Underwater fog.
sky_colorintRequired.
foliage_colorint (optional)Overrides computed leaf/vine tint.
dry_foliage_color 1.21.5+int (optional)Leaf-litter tint.
grass_colorint (optional)Overrides computed grass tint.
grass_color_modifierstring"none" / "dark_forest" / "swamp".
particleobject (optional){ options: {type}, probability } ambient particle.
ambient_soundstring/object (optional)Looping ambience.
mood_soundobject (optional)Cave "mood" sound (sound, tick_delay, block_search_extent, offset).
additions_soundobject (optional)Random additional sounds (sound, tick_chance).
musicobject/list (optional)Background music (sound, min_delay, max_delay, replace_current_music).

Spawners & features

spawners β€” keys are spawn categories: monster, creature, ambient, water_creature, underground_water_creature, water_ambient, misc, axolotls. Each is a list of { "type": <entity>, "weight": int, "minCount": int>0, "maxCount": int≥minCount }.

spawn_costs β€” per entity { "energy_budget": double, "charge": double } (density-based spawn limiting, used by soul-sand-valley-style crowding control).

features β€” an array of 11 sub-arrays, one per generation step (indices 0–10 inclusive), each holding placed-feature IDs (or #tags) run in that step:

#Step#Step
0RAW_GENERATION6UNDERGROUND_ORES
1LAKES7UNDERGROUND_DECORATION
2LOCAL_MODIFICATIONS8FLUID_SPRINGS
3UNDERGROUND_STRUCTURES9VEGETAL_DECORATION
4SURFACE_STRUCTURES10TOP_LAYER_MODIFICATION
5STRONGHOLDS
πŸ’‘ Best practice

Always include spawners, spawn_costs, carvers, and features even when empty β€” they are required keys. Ensure any placed features you list actually exist, or worldgen throws and the chunk fails to load. To reproduce overworld caves, reference the carver tag rather than leaving carvers empty (empty does not mean "vanilla").

Biome example

data/example/worldgen/biome/my_plains.json

{
  "has_precipitation": true,
  "temperature": 0.8,
  "temperature_modifier": "none",
  "downfall": 0.4,
  "effects": {
    "fog_color": 12638463,
    "sky_color": 7907327,
    "water_color": 4159204,
    "water_fog_color": 329011,
    "foliage_color": 9215022,
    "grass_color": 9287662,
    "grass_color_modifier": "none",
    "mood_sound": {
      "sound": "minecraft:ambient.cave",
      "tick_delay": 6000,
      "block_search_extent": 8,
      "offset": 2.0
    }
  },
  "spawners": {
    "creature": [
      { "type": "minecraft:sheep", "weight": 12, "minCount": 4, "maxCount": 4 },
      { "type": "minecraft:pig",   "weight": 10, "minCount": 4, "maxCount": 4 }
    ],
    "monster": [
      { "type": "minecraft:zombie", "weight": 100, "minCount": 4, "maxCount": 4 }
    ]
  },
  "spawn_costs": {},
  "carvers": [],
  "features": [
    [], [], [], [],
    [], [],
    ["minecraft:ore_dirt", "minecraft:ore_gravel"],
    [], [],
    ["minecraft:trees_plains", "minecraft:patch_grass_plain"],
    []
  ]
}
🎩 Trick
  • Custom colored world: author one biome with vivid grass_color/foliage_color/sky_color/fog_color, then use it via a fixed biome source. Set both grass_color and foliage_color explicitly to force a flat tint regardless of temperature/downfall.
  • Silent/empty biome: empty spawners categories + empty features steps = a sterile canvas biome.
  • Perpetual-snow biome: temperature: -0.5, has_precipitation: true.

Noise settings

Noise settings (data/<ns>/worldgen/noise_settings/<id>.json) drive noise-based terrain: the default block/fluid, the world's vertical envelope, aquifers, ore veins, the noise router (which wires density functions into named generation slots), spawn targeting, and the surface rule decision tree. This is the most complex worldgen file; the heavy math lives in density functions.

FieldTypeNotes
sea_levelintFluid level used by generation (independent of the vanilla Y=63 mob rule).
disable_mob_generationboolSuppresses creature spawning during chunk gen.
aquifers_enabledboolLocal water tables in caves; if false, caves below sea_level flood with default_fluid.
ore_veins_enabledboolLarge copper/iron ore veins.
legacy_random_sourceboolUse the pre-1.18 RNG (also constrains custom noise firstOctave ≤ 1).
default_blockblockstate {Name, Properties?}Terrain fill (e.g. minecraft:stone).
default_fluidblockstateSea/lake fill (minecraft:water OW / minecraft:lava Nether).
noiseobjectVertical envelope + cell size (below).
noise_routerobjectDensity-function slots (below).
spawn_targetarrayClimate parameter targets around which player spawn is chosen.
surface_ruleobjectTop-of-terrain block decisions (see below).

The noise object sets the vertical envelope and cell size:

FieldRangeNotes
min_y−2032…2031, ÷16Terrain floor β€” must match dimension_type.min_y.
height0…4064, ÷16Terrain height β€” must match dimension_type.height; min_y + height ≤ 2032.
size_horizontal0–4Horizontal cell size; larger = smoother but coarser (1 = 8-block cells, 3 = 12-block).
size_vertical0–4Vertical cell size.

The noise router

The noise_router maps density functions (inline object, referenced ID, or a constant) into named generation slots. Grouped by purpose:

SlotRole
final_densityThe terrain shape. >0 → place default_block; ≤0 → air / aquifer fluid. Constant 0 = void world; constant 1 = solid-stone world.
initial_density_without_jaggednessPreliminary density used before jaggedness, feeding surface/aquifer estimation.
barrierAquifer separation vs. open caves.
fluid_level_floodednessProbability a cave aquifer holds liquid (clamped −1…1).
fluid_level_spreadVertical spread / surface height of aquifer liquid.
lavaWhether an aquifer is lava instead of water (threshold ~0.3).
vein_toggleOre-vein type/region (copper Y 0–50 positive; iron Y −60…−8 non-positive).
vein_ridgedWhich blocks make up a vein.
vein_gapGaps/placement of ore within veins.
temperatureClimate param → biome placement only (not terrain).
vegetation(a.k.a. humidity) climate → biome placement.
continents(continentalness) climate → biome placement.
erosionClimate → biome placement.
depthClimate → biome placement (surface vs. cave).
ridges(weirdness) climate → biome placement.

Key insight: temperature/vegetation/continents/erosion/depth/ridges do NOT shape terrain β€” they only feed the multi_noise biome source. Terrain height is decided solely by final_density.

data/example/worldgen/noise_settings/my_settings.json β€” a void world (final_density: 0)

{
  "sea_level": 63,
  "disable_mob_generation": false,
  "aquifers_enabled": true,
  "ore_veins_enabled": true,
  "legacy_random_source": false,
  "default_block": { "Name": "minecraft:stone" },
  "default_fluid": { "Name": "minecraft:water", "Properties": { "level": "0" } },
  "noise": {
    "min_y": -64,
    "height": 384,
    "size_horizontal": 1,
    "size_vertical": 2
  },
  "noise_router": {
    "barrier": 0,
    "fluid_level_floodedness": 0,
    "fluid_level_spread": 0,
    "lava": 0,
    "temperature": 0,
    "vegetation": 0,
    "continents": 0,
    "erosion": 0,
    "depth": 0,
    "ridges": 0,
    "initial_density_without_jaggedness": 0,
    "final_density": { "type": "minecraft:constant", "argument": 0 },
    "vein_toggle": 0,
    "vein_ridged": 0,
    "vein_gap": 0
  },
  "spawn_target": [],
  "surface_rule": { "type": "minecraft:block", "result_state": { "Name": "minecraft:stone" } }
}

For real terrain, copy data/minecraft/worldgen/noise_settings/overworld.json from the vanilla data and edit incrementally β€” hand-writing a full router is impractical.

⚠ Version differences
  • initial_density_without_jaggednesspreliminary_surface_level (1.21.9 / 25w31a). Use initial_density_without_jaggedness for 1.21–1.21.8; the newer slot is a 2-D function sampled at Y=0.
  • 26.3-snap1: surface_rule renamed to material_rule. 26.3-snap2: spawn_target accepts any density function. Neither applies to 1.21.
  • The noise-based system itself stabilized in 1.18 (Caves & Cliffs pt. 2).

Density functions

Density functions (data/<ns>/worldgen/density_function/<id>.json) are JSON-encoded math expressions of position (x,y,z) returning a number. They compose into the noise-router slots. A function is either referenced (a string ID, e.g. "minecraft:overworld/sloped_cheese") or inline (a { "type": ... } object). A bare number is shorthand for a constant. Exception: clamp's input accepts only an inline definition, not a string ID.

Unary transforms & constants

Unary transforms take argument = a density function:

TypeEffect
abs|x|
square
cube
half_negativex/2 if x<0 else x
quarter_negativex/4 if x<0 else x
squeezeclamp to [−1,1] then x/2 − x³/24
constant{argument: double} (−1e6…1e6). A raw number is equivalent.

Binary & caching

Binary: add, mul, min, max β€” each {argument1, argument2}.

Caching / performance (take argument): interpolated (interpolate across cell corners), flat_cache (compute once per 4×4 column at Y=0), cache_2d (once per XZ column), cache_once (compute once per position even if referenced twice), cache_all_in_cell (engine-internal β€” avoid in datapacks).

Noise-based

TypeFields
noisenoise (custom-noise ID), xz_scale, y_scale
shifted_noisenoise, xz_scale, y_scale, shift_x, shift_y, shift_z
weird_scaled_samplerinput, noise, rarity_value_mapper (type_1/type_2) β€” samples noise at a rarity that scales with input.
end_islandsnone (End-island noise, range −0.84375…0.5625)
old_blended_noisexz_scale, y_scale, xz_factor, y_factor, smear_scale_multiplier (legacy terrain noise)

Shift helpers (argument = a noise ID, sample downscaled by 4): shift_a (samples at x/4,0,z/4), shift_b (z/4,x/4,0), shift (x/4,y/4,z/4).

Range, blending, splines

TypeFields
range_choiceinput, min_inclusive, max_exclusive, when_in_range, when_out_of_range β€” an if/else on the input's value.
clampinput (inline only), min, max.
blend_alphanone (old-chunk transition blending)
blend_offsetnone
blend_densityargument
beardifiernone β€” engine adapts terrain under structures.
splinespline: { coordinate: <density fn>, points: [ { location, value(number or nested spline), derivative } ] } β€” cubic-spline mapping used for continentalness/erosion/ridges → offset/factor/jaggedness.
y_clamped_gradientfrom_y, to_y (int −4064…4062), from_value, to_value β€” linearly maps clamped Y into a value range.
⚠ Version differences
  • Density functions were introduced 1.18.2-pre1; spline added 1.18.2-pre2.
  • 1.19: removed terrain_shaper_spline and the legacy spline format (22w11a); removed slide (22w12a β€” recreate via add/mul/y_clamped_gradient); old_blended_noise gained its scale/factor params.
  • 1.21.9 (25w31a): added find_top_surface and invert.
  • 26.2-snap5: added interval_select; removed weird_scaled_sampler (still valid in 1.21).

Inline example β€” unary + constant

{ "type": "minecraft:abs", "argument": { "type": "minecraft:constant", "argument": -5.0 } }

A y_clamped_gradient β€” the simplest "flat surface at a Y level" terrain

{
  "type": "minecraft:y_clamped_gradient",
  "from_y": -64,
  "to_y": 320,
  "from_value": 1.0,
  "to_value": -1.0
}

Used as final_density, this alone makes solid ground low and air high.

πŸ’‘ Best practice

Wrap expensive noise in cache_2d/flat_cache when the value is constant per column; use cache_once when the same function feeds two slots. Break complex expressions into named files under worldgen/density_function/ and reference them by ID β€” mirroring how vanilla organizes overworld/*. Note that clamp can't reference by ID β€” inline its input, or wrap a reference in an op that does accept IDs.

Surface rules

Surface rules (the surface_rule field inside noise_settings) decide which block goes at each solid surface position β€” grass over dirt over stone, sand in deserts, deepslate below a Y band, bedrock at the floor. It is a decision tree of three node types evaluated top-down.

TypeField(s)Meaning
minecraft:blockresult_state (blockstate)Place this block; a terminal/leaf.
minecraft:sequencesequence (array of rules)Try each in order; first match wins.
minecraft:conditionif_true (a condition), then_run (a rule)If the condition holds, run the nested rule.

Condition types (if_true):

typeFieldsPurpose
biomebiome_is (array of biome IDs)Match specific biomes.
noise_thresholdnoise (noise ID), min_threshold, max_thresholdSampled noise within a band (e.g. gravel/surface variation).
vertical_gradientrandom_name, true_at_and_below (Y anchor), false_at_and_above (Y anchor)Fuzzy Y transition (deepslate blend, bedrock roof/floor roughness).
y_aboveanchor (Y anchor), surface_depth_multiplier (int), add_stone_depth (bool)True above a height.
wateroffset (int), surface_depth_multiplier (int), add_stone_depth (bool)Relative to the local water level.
temperatureTrue where it's cold enough to snow.
steepOn steep (mountain-face) slopes.
notinvert (a condition)Negation.
holeWhere surface depth = 0 (noise-cave openings).
above_preliminary_surfaceAt/above the preliminary surface (keeps grass out of noise caves).
stone_depthsurface_type (floor/ceiling), offset (int), add_surface_depth (bool), secondary_depth_range (int)Distance into the surface from floor or ceiling β€” the workhorse for layering grass/dirt/stone.

Y anchors (anchor, true_at_and_below, …) are objects like { "absolute": 63 }, { "above_bottom": 0 }, or { "below_top": 0 }.

surface_rule inside noise_settings β€” desert sand, else grass→dirt cap, else stone

{
  "type": "minecraft:sequence",
  "sequence": [
    {
      "type": "minecraft:condition",
      "if_true": { "type": "minecraft:biome", "biome_is": ["minecraft:desert"] },
      "then_run": { "type": "minecraft:block", "result_state": { "Name": "minecraft:sand" } }
    },
    {
      "type": "minecraft:condition",
      "if_true": {
        "type": "minecraft:stone_depth",
        "surface_type": "floor",
        "offset": 0,
        "add_surface_depth": true,
        "secondary_depth_range": 0
      },
      "then_run": { "type": "minecraft:block", "result_state": { "Name": "minecraft:grass_block", "Properties": { "snowy": "false" } } }
    },
    { "type": "minecraft:block", "result_state": { "Name": "minecraft:dirt" } }
  ]
}
🎩 Trick

How badlands gets its terracotta banding. The colored bands are one built-in rule source β€” { "type": "minecraft:bandlands" } β€” gated by a minecraft:biome condition on badlands/eroded_badlands/wooded_badlands. It's hardcoded (SurfaceRules.Bandlands): effectively a ~192-entry, Y-indexed table of stained terracotta (orange base; bands of plain/yellow/brown/red/light-gray terracotta; white_terracotta bands driven by a separate noise), with red_sand capping the top so the bands only show on cut walls. The palette and spacing are baked into Java and can't be reconfigured β€” to make custom bands, stack minecraft:y_above (or noise_threshold) conditions each selecting a different terracotta result_state.

⚠ Version differences
  • 26.2-snap6: noise_gradient surface rule removed; noise_threshold changed. 26.3-snap1: the whole surface_rule field renamed to material_rule. Neither applies to 1.21.
  • The condition/rule system as shown has been stable across 1.18–1.21.
πŸ’‘ Best practice

Put the most specific conditions first (biome-specific), general fill (stone) last as the sequence's final leaf. Reuse stone_depth floor for the grass/dirt cap β€” it automatically respects the biome's surface depth.

🎩 Trick
  • Badlands-style banding: emulate with noise_threshold selecting between terracotta colors.
  • Bedrock floor roughness: vertical_gradient with a random_name from true_at_and_below above_bottom 5 to false_at_and_above above_bottom 0.
  • Single-material world: a lone { "type":"block", "result_state": {...} } (no sequence) paints everything.

Custom noise

A custom noise definition (data/<ns>/worldgen/noise/<id>.json) supplies a Perlin/octave noise referenced by density functions (noise, shifted_noise, shift*). Two fields only.

FieldTypeNotes
firstOctaveintThe lowest (largest-scale) octave. More negative → larger features. If legacy_random_source is true in the consuming noise settings, firstOctave must be ≤ 1; otherwise unbounded.
amplitudesarray of doublesWeight of each successive octave. Octave i has frequency 2^(firstOctave + i); 0.0 entries disable that octave.

Each amplitude's normalized contribution follows roughly 1.04 · value · 2^(n−i−1) / (2^n − 1) where n = amplitudes.length. Lower octaves = broad shape, higher octaves = fine detail.

data/example/worldgen/noise/rolling.json

{
  "firstOctave": -3,
  "amplitudes": [1.0, 1.0, 0.5, 0.25]
}
πŸ’‘ Best practice

Reuse vanilla noises (minecraft:temperature, minecraft:vegetation, minecraft:continentalness, minecraft:erosion, minecraft:ridge, minecraft:offset, minecraft:cave_*) via ID rather than redefining β€” they're tuned to the vanilla router. For bigger biomes/terrain, lower firstOctave (more negative) and/or use fewer high-octave amplitudes; for spikier detail, append small trailing amplitudes (e.g. ..., 0.1, 0.05).

Sources