Feature rules
A minecraft:feature_rules file is how a feature reaches a world at all. Every feature type places blocks when it is called; a rule is what calls them. One JSON file per rule, under a behaviour pack's feature_rules/ folder, names a feature, names the biomes it applies to, and says where in each of those biomes' chunks to try it. Without a rule pointing at it, a perfectly correct feature file is inert.
You need one for every top-level feature in your pack — the root of each delegation chain. You do not need one for the features inside a chain: a scatter's places_feature, an aggregate's list, a sequence's steps are all reached through the feature that names them.
A rule is also the one worldgen object that is not a feature, which is the source of most of the confusion around it. It cannot be delegated to, it cannot be named by any places_feature anywhere, and it has no placement of its own that another file could reach. It exists only at the top: the engine attaches it to chunks, and nothing below it can see it.
Start here: a complete example
One file. It attaches the scatter feature page's pumpkin patch to every overworld biome's surface pass, four times per chunk, anywhere in the chunk:
feature_rules/pumpkin_patch.fr.json{
"format_version": "1.21.110",
"minecraft:feature_rules": {
"description": {
"identifier": "wiki:pumpkin_patch.fr",
"places_feature": "wiki:pumpkin_patch"
},
"conditions": {
"placement_pass": "surface_pass",
"minecraft:biome_filter": { "test": "has_biome_tag", "value": "overworld" }
},
"distribution": {
"iterations": 4,
"x": { "distribution": "uniform", "extent": [0, 15] },
"y": 0,
"z": { "distribution": "uniform", "extent": [0, 15] }
}
}
}What each choice buys you:
placement_pass: "surface_pass"runs the rule after the terrain is shaped and the underground passes are finished, so the pumpkin patch has ground to attach to. It is the pass most new rules belong in — see the placement passes.has_biome_tag: "overworld"attaches the rule to every biome carrying that tag.{}would attach it everywhere, including the Nether and the End.extent: [0, 15]onxandzcovers exactly the chunk being decorated and nothing else, because the origin is the chunk's corner — and becauseuniformnever produces its high end,[0, 15]is offsets 0 to 14; the sixteenth column is reached with[0, 16].iterations: 4is four attempts per chunk. How many chunks is not a number in this file — it is however many chunks of matching biomes the world has.
Fields
A rule has three members — description, conditions, distribution — and nothing else in the file is read. Those six keys and description itself are the whole accepted surface: anything else written at any of those levels is an unrecognised member, which the engine names and drops unread, so a misspelled key is silently inert rather than an error.
The file
| Key | Required | Value | What it does |
|---|---|---|---|
description.identifier | yes | this rule's own name | Not a feature reference — nothing can name a rule. Renaming it moves everything the rule places: see what moves a rule's output. |
description.places_feature | yes | a feature identifier | The feature this rule places. Note it sits under description, unlike a scatter's, which sits on the feature body. Unresolved, the rule places nothing at any seed; featurelab check reports it against this file. |
conditions | yes | object | The wrapper is required even when it holds only the pass. |
conditions.placement_pass | yes | one of the passes below | Which pass over the chunk this rule's entry attaches to. Missing: the file does not load. Wrong: the file loads and the rule never runs — a different failure, and the quieter one. |
conditions.minecraft:biome_filter | no — default: every biome | one of the filter shapes below | Which biomes the rule is attached to. Absent or {} means all of them. |
distribution | no — default: a distribution whose iterations is 0 | identical in shape to a scatter's distribution | Where in the chunk to try, and how often. Optional, and leaving it out is worse than an error — see the warning. |
distribution is optional, and leaving it out is worse than an error
The file loads. The rule is inserted, attached to its pass and to every biome its filter matches — and then places nothing, in every chunk, forever, because a rule with no distribution gets a default one whose iterations is 0. Nothing is logged at generation time. If a rule appears to do nothing at all, checking that it has a distribution is the first thing to do.
The four keys marked required are required in the stronger sense: a file missing any of them fails to load and the rule is never inserted at all.
Inside distribution
Key for key, a scatter's: iterations, x, y, z, scatter_chance and coordinate_eval_order, with the same axis object — distribution, extent, step_size, grid_offset — and the same six distribution kinds. Read the scatter page's distribution tables and the six kinds as if they were here; the figure there shows each kind over exactly the [0, 15] extent a rule uses. One thing changes meaning: the origin is the chunk's minimum corner, so x and z are offsets into the chunk, and [0, 15] on both is "inside this chunk and nowhere else" — though it reaches only fifteen of the chunk's sixteen columns, because uniform never produces its high end. See coordinates are chunk-relative.
A rule body carries no project_input_to_floor. That key lives on a scatter's body, outside its distribution, and a rule has no feature body to put it on — a rule is the distribution. When placements need to find ground, the thing to reach for is a snap-to-surface feature inside the chain the rule places, not a key on the rule.
The biome filter
The filter decides which biomes the rule is attached to. Five shapes:
| Shape | Matches |
|---|---|
absent, or {} | Always — every biome. |
{"test": "has_biome_tag", "value": "<tag>"} | Biomes carrying that tag. |
{"any_of": [ … ]} | At least one child matches. |
{"all_of": [ … ]} | Every child matches. |
{"none_of": [ … ]} | No child matches. |
The combinators nest: a child of any_of is itself any of these shapes, including another combinator. has_biome_tag is the test this documentation covers; other test names are not covered here, and a rule that depends on one is outside what this page can vouch for.
A filter that matches nothing is the commonest reason a pack appears to do nothing at all
It is silent in the game — a rule that was never attached to a biome has nothing to report, because from the chunk's point of view it does not exist. Before debugging the feature, check the tag: a mistyped value, or a tag the target biome does not actually carry, produces exactly the same empty world as a broken feature does.
The placement passes
Chunks are not decorated in one sweep. The engine registers eleven passes, in a fixed order, and placement_pass chooses which one a rule's entry is attached to:
| # | Pass | Reach for it when |
|---|---|---|
| 1 | first_pass | Something must exist before anything else runs. |
| 2 | before_underground_pass | |
| 3 | underground_pass | Ores, caves' contents, anything below the surface. |
| 4 | after_underground_pass | |
| 5 | before_surface_pass | Reshaping the ground — flattening, filling, a platform — that surface decorations should land on. |
| 6 | surface_pass | Most new rules: trees, plants, patches, anything that attaches to the ground. |
| 7 | after_surface_pass | Reacting to what the surface decorations placed. |
| 8 | before_sky_pass | |
| 9 | sky_pass | Things above the terrain. |
| 10 | after_sky_pass | |
| 11 | final_pass | Cleanup that must see everything else. |
That is the complete ordered list. The three-way before_/plain/after_ grouping around underground, surface and sky is the useful part of its shape: two hook points around each of the three regions, so an author never has to guess an ordinal to get in front of or behind something. What each pass is for, one at a time, is in the field reference below.
What the ordering buys you is a guarantee about the world your rule sees. A rule in an earlier pass runs against a world that every later pass has not touched yet. That is what makes the terraform-then-decorate shape work: a rule that reshapes ground placed in before_surface_pass has finished before anything in surface_pass starts looking for a surface to attach to, so the decorations land on what the first rule built rather than on what was there before it. Run the two in the other order and the decoration attaches to the old terrain and then gets buried by the new. The converse is the cleanup case: a rule in after_surface_pass or final_pass sees everything the surface decorations placed, and can react to it.
`pregeneration_pass` is a twelfth accepted value
It is kept in its own separate list rather than in the ordered eleven, and only cave carvers may run in it. Any other feature type paired with it is refused with "cave_carver_feature" is the only valid feature in "pregeneration_pass" placement pass. and places nothing, however well the rest of the rule is written. What that pass is for beyond carving, and where it sits relative to the eleven, is not known with certainty.
placement_pass is required, and a wrong value fails differently from a missing one
- Missing: the file does not load. Nothing is inserted, and the whole rule is gone.
- Unrecognised: the engine logs
Feature rule identifier '<id>' specifies unknown pass '<pass>'.— and then keeps the value as written. It does not fall back to a default. The rule is inserted and attached to its biomes, chunk decoration only ever visits the passes it knows, and so the rule is never reached. Nothing is logged again, in any chunk, ever.
The second is the one that costs an afternoon: everything about the file looks right, the rule is "loaded", and it simply never runs. Check the spelling against the table above.
Two rules may share an identifier as long as their passes differ — the engine keys its store by pass first and then by identifier, so both are kept and both run. Two rules with the same identifier in the same pass are not: the first one loaded wins, and the second is dropped silently.
Common mistakes: why nothing was placed
Seven distinct failures produce the same empty chunk. An author can tell them apart — but not all of them from the game alone.
| Cause | How you tell | Fix |
|---|---|---|
| The pass name is not one the engine knows. | The rule loaded, one line was logged at load, and it has never run since. | Correct the spelling against the pass table. |
No distribution. | Loads, attaches, places nothing, in every chunk, forever. Silent after load. | Give it one. A rule without a distribution has iterations 0. |
pregeneration_pass with a non-carver. | Nothing places, and the engine says so once. | Move the rule to one of the eleven decoration passes. |
| The biome filter did not match. | The rule never ran at all, in any chunk. Silent in the game. | Correct the tag, or use {} while testing. |
iterations evaluated to zero. | The rule ran and did nothing, at every seed. | Fix the expression — this is not luck. |
scatter_chance did not roll. | Another seed places. | Nothing, if the odds were intended. A bare number here is a percent: 1.5 means 1.5%, not 150%. |
places_feature is unresolved. | Constant across every seed and every biome. | Fix the identifier — and check its case only if you are comparing against a tool: the game lower-cases both sides of this lookup, so a case mismatch resolves in game. |
The first three are the ones a rule file can get wrong while looking entirely correct: in each case the file loads, the rule exists, and nothing ever runs it.
Of the remaining four, the biome filter and an unresolved reference will never place anything at any seed in any world; the middle two look identical to each other in-game and are separated only by retrying, since a chance rejection eventually places and a zero iterations never does. Test at several seeds before believing a placement is broken is the habit this table exists to make concrete.
One failure is not quiet: a rule whose delegation is refused by the internal-feature check content-logs Feature rule <name> can't place internal feature. That check is a fixed permission about the feature types the game reserves for itself, not a loop check, and a feature loaded from a pack always passes it — internal types are refused when the pack's file loads — so in practice a pack does not see this line, and it is not a sign of a reference loop. See delegation loops.
Everything else stays silent by design; a chunk runs hundreds of refusals per pass and a log line each would be unusable. Failure is normal, and mostly quiet covers why, and how to work down a chain instead of guessing.
How a rule runs
A rule is a {distribution, places_feature} pair added to a chunk's decoration list — the same list that holds the features a biome names directly. When the chunk is decorated, that entry runs the exact same scatter machinery minecraft:scatter_feature uses: the same distribution kinds, the same scatter_chance gate, the same variable.originx/worldx Molang writes, the same rule that every iteration runs whether or not the previous one placed. There is no separate "rule placement algorithm" to learn, and everything the scatter feature page establishes about a distribution applies here verbatim.
Two things differ from a scatter, and both matter:
- The origin is the chunk's minimum corner, not an arbitrary position handed down by a caller. Every decoration entry in a chunk — a rule's included — scatters from that same base point, so a rule's
distribution.x/zare effectively chunk-relative offsets. - It runs once per chunk, of every biome whose filter matches. A rule is not invoked by anything; it is attached, and then it fires for each chunk it applies to. "How many times does my rule run?" is answered by "how many chunks", not by any count in the file.
That is what "once per chunk" looks like from above:

featurelab generate --pack docs/wiki/tools/fixtures --rule wiki:rng_rule_a.fr --env void --seed 42 --size 32x16x32 --min-y 0A 32×16×32 volume starting at -16, 0, -16 spans exactly four chunks, and the run reports four rule invocations — at origins (-16, 0, -16), (0, 0, -16), (-16, 0, 0) and (0, 0, 0). Those are the four chunks' minimum corners. The rule is eight iterations of a uniform [0, 15] x/z distribution placing a one-block marker; the run makes 32 attempts (8 × 4 chunks), eight inside each chunk rather than concentrated in one, and all 32 land — none of them wandering into a neighbour's 16×16. A feature scattering over the same footprint would have come from one call with a wider distribution; this is four calls that never see each other. The void preset is used so nothing but the rule's own output is visible.
Coordinates are chunk-relative, so x/z usually want [0, 16]
Because the origin is the chunk's minimum corner, an extent of [0, 15] on x and z stays inside that chunk and touches no other — but it does not cover the whole of it: uniform never produces its high end, so those are offsets 0 to 14, fifteen of the chunk's sixteen columns and 225 of its 256 cells. [0, 16] is the extent that reaches all sixteen, and it is still confined to the one chunk. Ranges that reach outside it are not illegal — they place into neighbouring chunks — but they make the output depend on which chunk is being decorated as well as where, and two adjacent chunks then spill into each other, which is harder to reason about than it looks.
The Molang side follows from the same fact: variable.originx/originy/originz hold the chunk corner, written once before any axis is evaluated, and variable.worldx/worldy/worldz hold each axis's absolute coordinate the moment that axis is computed. Both behave exactly as the variables a scatter writes describes, including the rule that iterations and scatter_chance are evaluated before any axis and so cannot read this call's world*.
What moves a rule's output
A rule does not carry a seed and cannot be given one. Three facts decide where its placements land, and each one is a change you can or cannot make safely:
- Every chunk decorates on its own. A rule produces the same layout in chunk (10, 4) whether or not its neighbours ever existed, and in whatever order the game happens to load them. So testing at several world seeds tests genuinely different chunks, not the same layout shifted.
- Every rule has its own sequence of random values, derived from the chunk and from the rule's own
description.identifier. Adding a rule to a pack does not move the rules already in it. Renaming a rule moves everything it places — the name is an input — while renaming the feature it points at (and updating the reference) changes nothing about where that rule puts things. If a rename must not move existing content, it is not a rename you can make. - Changing the feature a rule places never moves the positions the rule picked for it. Give the delegate a
randomize_rotation, or swap a one-block delegate for a whole tree: the rule's positions stay exactly where they were. (This is a property of a rule, not of every scatter — a scatter inside a feature does move when its delegate changes; see the RNG page.)
Where the derivations come from is on where a feature's seed comes from; the rename effect is reproduced from two committed fixtures in the advanced section.
Field reference
Generated from the editor's own catalogue — the same text the Feature Lab node editor shows for a rule's form, including one description per placement pass. The tables above are the summary. A rule's schema has no format_version bands: one schema, no per-key version gates, no legacy spellings.
description.identifier
required · string
This rule's own name, and an input to its seed.
Not a feature reference: nothing can name a rule. The name is hashed into the seed the rule's distribution draws from, so renaming a rule moves everything it places, while renaming the feature it points at moves nothing. Two rules may share a name if their passes differ; two in the same pass keep only the first loaded.
description.places_feature
required · feature identifier · drawn as the connection from the rule card
The feature this rule attaches to every matching chunk.
Sits under description, unlike a scatter's, which sits on the feature body. Unresolved, the rule places nothing at any seed in any biome; featurelab check reports it as an error against the rule file, naming $.minecraft:feature_rules.description.places_feature.
conditions
required · group
When in a chunk's generation the rule runs, and in which biomes.
Required: a rule without it fails a required field and the game refuses the whole file, so the rule is never inserted and nothing it would place appears. The two members are a stage of chunk generation and, optionally, a biome test.
Required. A rule with no conditions fails a required field and the game refuses the whole file, so the rule is never inserted and places nothing at all.
conditions.placement_pass
required · enum
Which stage of chunk generation places this rule's feature.
The passes run in the order they are listed, so this is also "how much of the world is already there when my feature arrives" -- a decoration that needs ground under it cannot run before the ground exists. Required, and there is no default to fall back on.
Required, and a CLOSED list. A pass outside it still loads -- the engine logs the unknown name and keeps it verbatim, with no substitution -- and chunk decoration only visits the passes it knows, so such a rule attaches to its biomes and is then never reached: it places nothing, in every chunk, forever.
Values, in the order the engine lists them:
first_pass— The earliest decoration pass, before anything else has been added to the chunk. Nothing another rule places is there yet, so this is where something has to run if everything else is meant to sit on top of it -- and the wrong choice for a decoration that needs a finished surface, which does not exist this early.before_underground_pass— Runs immediately before the underground pass, for anything that must precede it. The three underground passes are a group: this one exists so a rule can prepare the ground that the underground pass proper then works on.underground_pass— The main pass for everything below the surface -- ores, veins and cave dressing. This is where the great majority of subsurface rules belong. The carving of caves themselves happens earlier still, in pregeneration_pass.after_underground_pass— Runs immediately after the underground pass, for anything that builds on it. Everything the underground pass placed is in the chunk by now, so a rule that has to react to an ore or a cave decoration goes here rather than beside it.before_surface_pass— Runs immediately before the surface pass, while the surface is still bare. Useful for anything that has to claim its space before the surface fills up -- a large structure that would otherwise be competing with grass and trees for the same columns.surface_pass— The main pass for everything that sits on the ground: plants, trees and scatter. The terrain is shaped and the underground passes are finished, so a feature placed here can rely on there being ground to stand on. Most new rules belong here.after_surface_pass— Runs immediately after the surface pass, once the ground cover is in place. The right home for anything that decorates what the surface pass placed -- vines on the trees it grew, rather than the trees themselves.before_sky_pass— Runs immediately before the sky pass, after everything on the ground is done. The ground is finished by this point, so it and the two after it are about the space above the ground rather than about the terrain.sky_pass— The main pass for anything placed in open air rather than on the ground. Floating and hanging features belong here. A feature placed this late still has to find its own position: a pass decides when a rule runs, never where.after_sky_pass— Runs immediately after the sky pass, for anything that decorates it. The last pass before final_pass, and the place for something that has to react to what the sky pass produced.final_pass— The last decoration pass: everything every other rule places is already there. Use it for anything that has to see the finished chunk -- a cleanup or an overlay. It is also the only pass where a rule can be sure nothing will be placed over its work.pregeneration_pass— Runs before the decoration passes, and accepts ONE kind of feature. Only a cave carver may run here. Every other type is refused at placement time with a log line and nothing placed, so a rule that names it over anything else loads, attaches to its biomes, and then quietly never produces anything.
conditions.minecraft:biome_filter
optional · json · absent: no filter -- the rule applies in every biome that the pass reaches
Which biomes the rule applies in.
Left out, the rule applies in every biome the pass reaches. A rejection is not silent: the rule is still attached and still consulted, and the filter is what decides each time.
Free-text field
The shape IS known -- {} matches everything, {"test": "has_biome_tag", "value": "..."} is the form almost every real rule uses, and {"all_of": [...]}, {"any_of": [...]} and {"none_of": [...]} combine child filters -- but it is RECURSIVE, and a field in this catalogue is a fixed set of sub-fields with no way to nest into itself. A raw-JSON box that accepts all four shapes is the honest control; a fixed one would refuse the nested spelling that real packs write.
distribution
optional · group · absent: the default-constructed parameters: iterations 0 and scatter_chance 100 -- the rule LOADS, attaches to its pass and its biomes, and then places nothing every chunk
How many times per chunk the rule places its feature, and where in the chunk.
The engine treats this whole object as optional, and a rule without one is the quietest failure this file format has: it loads, attaches to its pass and its biomes, and then places nothing in every chunk forever, because the parameters it falls back on have an iteration count of zero. Its three axes are read the same way as each other: a bare number or Molang string pins that axis to a fixed offset with no draw at all, and the object form {distribution, extent, ...} is what spreads placements across the chunk.
Optional to the schema and load-bearing in practice: leaving it out is the one way to get a rule that is live, correct and completely inert. The same parameter object a scatter uses, with one addition -- iterations is a plain field here, because a rule has no feature-placing edge to carry it.
distribution.iterations
optional · molangOrNumber · absent: 0 -- which means the rule places nothing
How many placements the rule attempts in each chunk it applies to.
Every other setting on the rule is moot until this is above zero, which is what makes it the first thing to check on a rule that appears to do nothing. An attempt is not a guarantee: the feature it places can still refuse the position it is handed.
How many times the rule places its feature per chunk. A number or a Molang string. This is the key that decides whether a rule does anything at all: absent, or 0, and every other setting on the rule is moot. On a scatter the same parameter is drawn on the connection instead, because only there can it also carry the setup script real packs write into it.
distribution.x
optional · coordinate · absent: a zero-width axis at the origin
How far east/west of the chunk position each iteration lands.
A number, a Molang string, or a {distribution, extent} object.
distribution.x.distribution
required · enum
How this axis turns the iteration into a coordinate.
Six kinds, and they differ in how many random draws they spend as well as in where they put things: uniform takes one, gaussian / inverse_gaussian / triangle take two (plus one more for inverse_gaussian on an exact tie), fixed_grid takes none, and jittered_grid takes one only when step_size is 2 or more. Each kind is described below.
Values, in the order the engine lists them:
uniform— One draw, flat across the extent -- and the TOP END IS EXCLUDED. The result is the low end plus a draw over (high - low), so an extent of [0, 4] produces 0, 1, 2 or 3 and never 4. An extent whose high end is not above its low end returns the low end and costs no draw at all.gaussian— Two draws that cancel out, so positions bunch towards the MIDDLE of the extent. The engine takes half the extent's width, draws twice under that half, and adds the difference to the middle. Neither end is ever reached exactly, and an extent narrower than two collapses to the low end with no draws at all. Use it for a soft cluster around a point.inverse_gaussian— The same two draws, arranged so positions bunch at BOTH ENDS and thin out in the middle. The difference between the two draws is measured inward from one end or the other, so small differences land near an end and large ones near the centre -- the exact opposite of gaussian. When the two draws tie, one further draw picks the low end or the high end outright, which is why the extreme ends are the single most likely results.fixed_grid— Even, completely unrandom spacing: cell = grid_offset + low end + iteration index x step_size, wrapped. No draw is ever made on this axis. The wrap is by the extent's width plus one (high - low + 1), so a step_size that does not divide that width walks a rotating set of cells instead of repeating the same few. What is handed to the next axis evaluated is that same sum divided by the wrap width, which is what makes two or three grid axes cover a 2-D or 3-D lattice between them rather than all moving together. Use it for rows, columns and regular fills; note that step_size's default is not established, so write it out.jittered_grid— fixed_grid plus one random offset within each cell -- but ONLY when step_size is 2 or more. The jitter is a single draw under step_size, added before the wrap, so a placement never leaves its own step. Below a step_size of 2 there is no room to jitter into: no draw is made and the axis behaves exactly like fixed_grid. Use it for a scattered look that still covers the extent evenly.triangle— Two draws ADDED, so positions bunch in the middle while both ends stay reachable. The extent is split in two and one draw is taken over each half, both ends INCLUDED, and the two are added to the low end. That is the difference from gaussian worth knowing: gaussian never quite reaches either end, triangle does.
distribution.x.extent
required · groupList · exactly 2 elements
The low and high ends this axis works between, as an offset from the scatter origin.
Both ends are Molang-or-number and both are rounded to whole blocks before the distribution sees them. The two ends are NOT treated alike by every kind -- uniform excludes the top end, triangle includes it, and the grid kinds use max - min + 1 as a wrap width -- so hover the distribution value before assuming an extent of [0, 4] means five positions.
A 2-element [min, max] array. Both ends are Molang-or-number.
distribution.x.step_size
optional · number · absent: 1 -- NOT established; 1 is the only value that makes a bare fixed_grid/jittered_grid axis non-degenerate
The gap between consecutive cells on a grid axis.
Read only by fixed_grid and jittered_grid; the other four kinds ignore it. On jittered_grid it is also the jitter bound, and a value below 2 turns the jitter off entirely, which makes that axis behave exactly like fixed_grid. Note the caveat on the default: it is NOT established, so write the value you mean rather than relying on it.
distribution.x.grid_offset
optional · number · absent: 0
Shifts a grid axis along by a fixed amount before it wraps.
Added to the cell position, and also carried into the index handed to the next axis, so it shifts the whole lattice rather than just the first placement. Read only by fixed_grid and jittered_grid. A negative value wraps around rather than producing a negative cell.
distribution.y
optional · coordinate · absent: a zero-width axis at the origin
The height each iteration is placed at.
Usually the axis that matters most on a rule: it is what puts ore underground and a tree on the surface.
A number, a Molang string, or a {distribution, extent} object.
Sub-keys: the same as distribution.x — distribution, extent, step_size, grid_offset.
distribution.z
optional · coordinate · absent: a zero-width axis at the origin
How far north/south of the chunk position each iteration lands.
A number, a Molang string, or a {distribution, extent} object.
Sub-keys: the same as distribution.x — distribution, extent, step_size, grid_offset.
distribution.scatter_chance
optional · chance · absent: 100 (always scatters)
A gate on the whole rule, rolled before any iteration runs.
It decides whether this chunk gets the feature at all -- it is not a per-iteration roll. Use it for "roughly one chunk in twenty"; use iterations for how many appear once a chunk has been chosen.
Either a percent -- a number or a Molang string -- or a {numerator, denominator} fraction. A CONSTANT percent outside (0, 100] is rewritten to 100 by the engine, so scatter_chance: 0 always scatters; use iterations 0 for "never". numerator and denominator are 32-bit integers, so {1.5, 4} runs as 2/4.
distribution.coordinate_eval_order
optional · enum · absent: xzy
The order the three axes are worked out in, and so what each one can already see.
Each axis draws when its turn comes, so the order decides which draw feeds which axis and placements MOVE when you change it -- this is not a cosmetic setting. variable.worldx, variable.worldy and variable.worldz are each written as soon as that axis is known, so a later axis can read an earlier one while the first can read neither of the others. For grid distributions the cell index is handed on in this same order too, which is what makes two or three grid axes walk a lattice together rather than repeat the same row. The six values below differ only in which axis goes where.
Values, in the order the engine lists them:
xyz— Evaluates x, then y, then z. y and z can read variable.worldx; x's own expression can read neither of theirs.xzy— Evaluates x, then z, then y. z and y can read variable.worldx; x's own expression can read neither of theirs.yxz— Evaluates y, then x, then z. x and z can read variable.worldy; y's own expression can read neither of theirs.yzx— Evaluates y, then z, then x. z and x can read variable.worldy; y's own expression can read neither of theirs.zxy— Evaluates z, then x, then y. x and y can read variable.worldz; z's own expression can read neither of theirs.zyx— Evaluates z, then y, then x. y and x can read variable.worldz; z's own expression can read neither of theirs.
What the bench does differently
featurelab names all seven causes above in its diagnostics — including the biome-filter rejection the game has no reason to mention — because an unfiltered "nothing happened" is indistinguishable from a bug. That is bench tooling, not engine behaviour: the game does not print these lines. Two more things a preview of a rule does that the game does not:
- A rule preview runs the rule over the chunks the bench covers, once per chunk from that chunk's corner, exactly as the game applies it — but in a world built from a preset, with no other rule running beside it. Anything the game decides by consulting other generated content cannot happen there.
featurelab checkreports an unresolvedplaces_featureon a rule as an error against the rule file, naming$.minecraft:feature_rules.description.places_feature, with a near-match suggestion. A rule's delegation is checked exactly like a feature's — and this is the one row of the table above that is found between two files, which no amount of reading either file finds.
See how claims on these pages are backed for the distance between the bench and the game in general.
Advanced: the seeding, reproduced
You do not need this section to write a rule. It is the evidence behind what moves a rule's output, for anyone reading a preview against the game or reproducing the engine.
From the per-entry seed the engine builds two generators: one the distribution draws positions from, one every delegated feature draws from. That split is why a change inside the placed feature never moves the positions the rule picked for it — see that seed is used twice, for two independent streams.
The fixture pack behind this documentation carries two rules that are identical except for their identifiers, rng_rule_a.fr.json and rng_rule_b.fr.json, both placing the same one-block feature through eight iterations of a uniform [0, 15] x/z distribution with y: 0:
feature_rules/rng_rule_a.fr.json{
"format_version": "1.21.110",
"minecraft:feature_rules": {
"description": {
"identifier": "wiki:rng_rule_a.fr",
"places_feature": "wiki:rng_marker"
},
"conditions": {
"placement_pass": "surface_pass",
"minecraft:biome_filter": {}
},
"distribution": {
"iterations": 8,
"x": { "distribution": "uniform", "extent": [0, 15] },
"y": 0,
"z": { "distribution": "uniform", "extent": [0, 15] }
}
}
}featurelab generate --pack docs/wiki/tools/fixtures --rule wiki:rng_rule_a.fr --env void --seed 42 --size 32x16x32 --min-y 0
featurelab generate --pack docs/wiki/tools/fixtures --rule wiki:rng_rule_b.fr --env void --seed 42 --size 32x16x32 --min-y 0Both runs report four invocations at the same four chunk corners and make 32 attempts (8 iterations × 4 chunks), eight in each chunk — that even spread is the per-chunk half of the seeding. Their coordinates share nothing:
| Rule | first four cells (x, z) | placed |
|---|---|---|
wiki:rng_rule_a.fr | (-16, 5), (-16, 10), (-15, -6), (-14, -7) | 32 |
wiki:rng_rule_b.fr | (-15, -10), (-14, -16), (-14, -13), (-14, -11) | 31 |
Only the rule identifier differs between the two files, so its hash is the whole of the difference. Rule B places 31 rather than 32 because two of its 32 attempts landed on the same cell — a coincidence of its own stream, not a rule about anything. The image in how a rule runs is rule A's own output from the first of these two runs.
Note
The per-cell arrays in that JSON are run-length encoded as {"rle": [value, run, value, run, …]} — expand them before indexing if you decode the block grid yourself. The counts and coordinates above come from exactly these two runs.
See also
- Scatter feature — the distribution block a rule uses, kind for kind. Read that page's field tables and its six-kinds figure as if they were this one's.
- RNG and determinism — where a rule's seed comes from, why renaming one moves it, and the two independent streams every decoration entry gets.
- Feature delegation and composite features — what happens below a rule: the shared stream and Molang scope, delegation loops, and why most refusals are quiet.
- Single block feature — the far end of almost every rule's chain.
How this page was checked
Everything above is a statement about Bedrock 1.26.60.22, and holds for 1.26.50.24 too. The per-chunk seeding, the two-stream split and the rename effect are reproduced by the two committed rule fixtures and the commands above, whose counts and coordinates were read back out of featurelab generate's result; the image was rendered from rule A's result by the documentation's own image pipeline. The pass list, the accepted key set and the load-time behaviours (an unknown pass kept verbatim, a missing distribution defaulting to zero iterations) are stated as facts about the game; the pregeneration_pass note says what is not known. The "reach for it when" column of the pass table is advice about what the ordering guarantees, not a statement about what vanilla puts in each pass.