Skip to content

Height difference filter ​

Bedrock 1.26.60.22also holds for 1.26.50.24also holds for 1.26.40.26Bench: implemented

minecraft:height_difference_filter_feature looks at the ground around a position and places another feature only where the terrain nearby rises or falls the way you asked. You reach for it when something belongs to a shape of land rather than to a block or a biome: a boulder at the foot of a cliff, a shrine on a ledge, a waterfall head only where the ground drops away. It places nothing itself, which makes it a Proxy feature — it decides, and hands the position to places_feature unchanged.

It is the wrong type when what you care about is depth below the surface rather than variation in it: that is a surface relative threshold feature, which compares one position against the surface directly above it. And it is the wrong type when you want to move to the terrain rather than be judged by it — a snap-to-surface feature walks to the ground; this one never moves anything.

Two things need saying before anything else. On flat ground a filter that asks for a rise places nothing, and that is the feature working, not a bug. And search_radius is the key everything else depends on: it is required, it accepts values it cannot use, and it is where almost every silent failure of this type starts — see search_radius.

Start here: a complete example ​

Two files, both complete. The filter asks for ground at least 3 blocks higher than the origin somewhere within 4 blocks; the block feature it delegates to is the one from the single block page, unchanged.

json
{
  "format_version": "1.21.110",
  "minecraft:height_difference_filter_feature": {
    "description": { "identifier": "wiki:height_diff_gate" },
    "places_feature": "wiki:pumpkin_patch_block",
    "search_radius": 4,
    "min_required_upward_height_diff": 3
  }
}
json
{
  "format_version": "1.21.110",
  "minecraft:single_block_feature": {
    "description": { "identifier": "wiki:pumpkin_patch_block" },
    "enforce_placement_rules": false,
    "enforce_survivability_rules": false,
    "places_block": [
      { "block": "minecraft:pumpkin", "weight": 3 },
      { "block": "minecraft:jack_o_lantern", "weight": 1 }
    ],
    "may_replace": ["minecraft:air"],
    "may_attach_to": { "bottom": "minecraft:grass_block" }
  }
}

What each choice buys you:

  • search_radius: 4 walks four steps north, four east, four south and four west and reads the ground height at each of those sixteen positions. Nothing diagonal, nothing in between, and never the origin's own column — see search_radius.
  • min_required_upward_height_diff: 3 is satisfied by one of those sixteen being at least 3 blocks higher than the origin. It is a "somewhere nearby" test, not an "everywhere" one.
  • No max_allowed_* key is written at all, and that matters: a constraint you leave out is satisfied automatically, so this file asks exactly one question and nothing else.
  • The delegate runs at this feature's own position, untouched — the same (x, y, z) the filter was given. Nothing is offset, and wiki:pumpkin_patch_block still runs its own may_attach_to.bottom check there.
featurelab generate --pack <pack> --feature wiki:height_diff_gate --env plains --seed 1

Run against the plains preset with feature seed 1 and the default origin, this places 0 blocks and reports one warning, placement returned no result and wrote no blocks. The plains surface is flat at this origin: every one of the sixteen sampled columns reads the same height the origin sits at, none of them reaches the +3 the file asked for, and the gate refuses. That is the correct answer for this file on this terrain.

Zero blocks on flat ground is the feature working

This is the one type whose successful behaviour is most often mistaken for a broken pack. If you write a height filter, test it on flat terrain and see nothing placed, nothing is wrong — you asked for a slope and there is no slope. Give it ground that varies, or loosen the constraint. The tip below builds that ground so you can watch the same file's sibling pass.

Seeing the same gate pass: build the slope first

Every terrain preset the bench ships is essentially flat around the origin, so the only way to watch this gate say yes is to raise some ground before it runs. These three files do that — a stone column five blocks east, then the filter, run one after the other from the same position:

json
{
  "format_version": "1.21.110",
  "minecraft:single_block_feature": {
    "description": { "identifier": "wiki:height_diff_ridge_block" },
    "enforce_placement_rules": false,
    "enforce_survivability_rules": false,
    "places_block": "minecraft:stone"
  }
}
json
{
  "format_version": "1.21.110",
  "minecraft:scatter_feature": {
    "description": { "identifier": "wiki:height_diff_ridge" },
    "places_feature": "wiki:height_diff_ridge_block",
    "distribution": {
      "iterations": 3,
      "x": 5,
      "y": { "distribution": "fixed_grid", "extent": [0, 2], "step_size": 1, "grid_offset": 0 },
      "z": 0
    }
  }
}
json
{
  "format_version": "1.21.110",
  "minecraft:aggregate_feature": {
    "description": { "identifier": "wiki:height_diff_demo" },
    "features": ["wiki:height_diff_ridge", "wiki:height_diff_gate_reaching"]
  }
}

wiki:height_diff_gate_reaching is the example file above with search_radius raised to 5, because the column is five blocks away and a radius of 4 never reaches it. Run the aggregate on plains at seed 1 and 4 cells change: the three stone blocks at (5, 63, 0), (5, 64, 0) and (5, 65, 0), and then the delegate's block at the origin, (0, 63, 0) — the column's top is now at height 66, which is 63 + 3, exactly the rise the filter asked for. Swap wiki:height_diff_gate back in and only the three stone blocks change: the gate is 4 and the slope is 5 away.

Fields ​

Six keys, all on the feature body, and four of them are optional constraints that behave in two different ways. Every constraint means what its name says. "Default" is what the game uses when the key is absent. The long-form account of every key, in the editor's own words, is the field reference further down; this table is the short version.

KeyRequiredValueDefaultWhat it does
places_featureyesfeature identifier—The feature placed when the gate passes, at this feature's own position, unchanged. A name nothing defines is reported and nothing is placed.
search_radiusyeswhole number—How many steps out to sample, along four directions only. A fraction is cut down, not rounded; a value below 1 samples nothing. See search_radius.
min_required_upward_height_diffnowhole numbernot askedRequires at least one sampled column to stand this far above the origin. See the four constraints.
min_required_downward_height_diffnowhole numbernot askedRequires at least one sampled column to sit this far below the origin. See the four constraints.
max_allowed_upward_height_diffnowhole numbernot askedA ceiling: refuses the position as soon as any sampled column stands more than this far above the origin.
max_allowed_downward_height_diffnowhole numbernot askedA floor: refuses the position as soon as any sampled column sits more than this far below the origin.

The four constraints, and which kind each one is ​

The two min_required_* keys are requirements — one satisfying column anywhere in the scan is enough, and the scan keeps going either way. The two max_allowed_* keys are limits — the first column that breaks one ends the whole scan and refuses the position. Every constraint you leave out is satisfied; a file with none of the four passes anywhere the radius is at least 1.

KeyKindPasses whenReach for it when
min_required_upward_height_diffrequirementsome sampled column is at least this far above the originSomething belongs at the foot of a rise: a boulder under a cliff, debris at the base of a slope.
min_required_downward_height_diffrequirementsome sampled column is at least this far below the originSomething belongs at the top of a drop: a waterfall head, a ledge marker, a lookout.
max_allowed_upward_height_difflimitevery sampled column is at most this far above the originYou want open ground, not a hollow: nothing hemmed in by walls on any side.
max_allowed_downward_height_difflimitevery sampled column is at most this far below the originYou want solid footing: nothing perched on the lip of a chasm.

A requirement and a limit on the same side combine into a band. min_required_downward_height_diff: 2 with max_allowed_downward_height_diff: 4 asks for a drop of 2 to 4 blocks somewhere within the radius, and refuses any position that sees a drop deeper than 4 anywhere in it — so a cliff too tall for the band refuses its whole edge, not only the columns next to the deep part.

Common mistakes ​

You wroteWhat happensDo instead
A filter asking for a rise, tested on flat ground0 blocks, one warning about no result. The gate is working; the terrain has no rise in it.Test it on varied ground, or build the slope first.
"search_radius": 4.9, expecting 5It is cut down to 4, with no complaint of any kind. 4.9 and 4.0 are the same file.Write the whole number you mean.
"search_radius": -5 or 0Accepted in silence. Nothing is sampled: the gate then passes everywhere unless you asked for a rise or a fall, in which case it passes nowhere.Write at least 1.
search_radius left outThe file is refused with search_radius is required — it is the one optional-looking key that is not.Write it.
"max_allowed_upward_height_diff": 2 expecting it to keep the position near rising groundIt is a cap, not a request: flat ground passes it, and any column more than 2 blocks higher anywhere on the arms refuses the position."min_required_upward_height_diff" asks for a rise.
A constraint that must hold all the way round, written as min_required_upward_height_diffOne satisfying column passes it. Fifteen flat columns and one raised one is a pass.Use a limit (max_allowed_upward_height_diff / max_allowed_downward_height_diff) for "everywhere" tests.
A max_allowed_downward_height_diff smaller than the drop your min_required_downward_height_diff is meant to findEvery position that sees the drop also breaks the limit: the pair can never pass there.Keep the limit at least as large as the requirement, and larger than the deepest drop you want to accept.
Expecting the origin's own column to be sampledIt never is. The scan starts one step out in each direction. A pillar standing on the origin is invisible to this gate.Compare against neighbours only, or pick a different position.
Expecting diagonal or filled-area coverageFour straight arms, nothing else. A rise sitting diagonally two blocks away is never seen, whatever the radius.Raise the radius until an arm crosses it, or accept the cross shape.
"search_radius": 1000 to "be safe"Four thousand height reads for every single placement attempt, on every attempt the rule makes.Keep it to the distance the terrain feature you are looking for actually spans.
Expecting a diagnostic when the gate refusesThere is none. A refused position is silent; the only warning you see is the generic "nothing was placed" one.Test with the constraints removed one at a time to find which is refusing.

How it runs ​

  1. Resolve places_feature. A name no loaded file defines reports `height_difference_filter_feature` could not find feature `places_feature`. and nothing is placed — this happens before the ground is ever looked at.
  2. Check the radius. Below 1, nothing is sampled at all, and the answer is decided on the spot: it is yes, unless min_required_upward_height_diff or min_required_downward_height_diff was written, in which case it is no. The two limits are not consulted on this path.
  3. Walk the four arms — north, then east, then south, then west — one step at a time out to the radius, reading the ground height at each step.
  4. Apply the limits as you go. The first sampled column that stands higher than max_allowed_upward_height_diff allows, or lower than max_allowed_downward_height_diff allows, ends the scan there and refuses the position.
  5. Collect the requirements. Each of the two requirement keys is remembered as satisfied the moment one column satisfies it; the scan carries on regardless, because the other one may still need a column.
  6. Decide. Both requirements satisfied, and no limit broken, and the gate passes.
  7. Place places_feature at the unchanged position. The delegate runs its own checks there and may still refuse. If the gate did not pass, nothing at all happens and nothing is reported.

search_radius: the key that decides everything else ​

It is required — a file without it does not load — and it is measured in steps along each arm, not in any kind of area. A radius of r reads exactly 4 × r columns: r to the north, r to the east, r to the south, r to the west, starting one block out. The origin's own column is never among them, and neither is anything off the four axes.

Three things about the value itself, all of them silent:

  • A fraction is cut towards zero, never rounded. 4.9 is 4; -0.5 is 0. Nothing is reported, so a file that looks like it reaches five blocks reaches four.
  • Zero and negative values are accepted. They are not clamped and they do not warn. What search_radius < 1 means is "sample nothing", and that has two very different outcomes depending on what else the file says.
  • With nothing sampled, the answer is decided by which keys exist, not by the terrain: the gate passes when neither min_required_upward_height_diff nor min_required_downward_height_diff is written, and can never pass when either of them is. The two limit keys are ignored entirely — a radius of 0 with only max_allowed_downward_height_diff written passes everywhere, however the ground actually falls away.

So a mistyped radius fails in whichever direction is hardest to notice. A filter that was meant to be selective and has a radius of 0 places its delegate at every position it is offered; a filter that asks for a rise and has a radius of 0 places nothing, anywhere, for ever, and says nothing about why.

The cost is linear in the radius and paid on every placement attempt, not once: four arms times the radius, for every position a rule offers. A radius in the hundreds is the difference between a pack that generates and one that stalls.

What "the ground height" means here ​

Each sample is the height of that column — the level the terrain surface has reached there — and it is read live, at the moment the gate runs. That has one consequence worth planning around: anything already placed in that column counts. A feature earlier in the same pass that raised or lowered the ground changes what this gate sees, which is exactly what makes the slope-building recipe in the example above work, and is also why the same filter can pass or refuse depending on the order features run in.

The columns read are the four arms only. This gate has no idea what the ground does diagonally, and no idea what it does at the position it is standing on.

Field reference ​

The long-form account of every key, generated from the editor's own catalogue — the same text the Feature Lab node editor shows in its ? pane and on hover, with the schema facts (required, kind, absent-key default) the editor's forms are built from. The table above is the summary.

places_feature ​

required · feature identifier · edited on the connection, not in the form

The feature placed when the terrain around the position has the shape you asked for.

A namespace:id that some file in the pack defines, placed at this feature's own position, unchanged. It is resolved BEFORE the ground is looked at, so a name nothing defines is reported at every attempt rather than only at the ones the gate would have passed. A gate that refuses, by contrast, reports nothing at all.

search_radius ​

required · integer

How far out to sample the terrain height, in blocks.

The four horizontal compass directions are walked one step at a time out to this distance, and the height at every step is tested. Below 1 nothing is sampled: the gate then passes unless one of the two min_required keys was configured, in which case it can never pass.

min_required_upward_height_diff ​

optional · integer

Requires the ground to rise at least this far somewhere within the radius.

Satisfied by any ONE sampled position, not by all of them -- it is a "somewhere nearby" test. Leaving it out satisfies it automatically.

min_required_downward_height_diff ​

optional · integer

Requires the ground to drop at least this far somewhere within the radius.

Like its min_required_upward counterpart it only has to hold at one sampled position, and leaving it out satisfies it automatically.

max_allowed_upward_height_diff ​

optional · integer

Refuses the position if the ground anywhere within the radius rises further than this.

A hard fail: the first sample that breaks it ends the scan, and the position is refused. Leaving it out disables it.

max_allowed_downward_height_diff ​

optional · integer

Refuses the position if the ground anywhere within the radius drops further than this.

Another hard fail: the first sample that breaks it ends the scan. Leaving it out disables it.

What the bench does differently ​

This page documents the game. Three things belong to the featurelab bench used to illustrate it:

  • The ground height is the bench's own terrain reading, taken from the finished volume rather than from the game's terrain-generation estimate. On the flat presets the bench ships the two agree; on real, varied terrain the value a live game would compare against is the same quantity but not necessarily the same number. The surface relative threshold page documents the same substitution in more detail, and it applies here identically.
  • Every terrain preset is effectively flat where features are placed, so a filter that asks for any variation at all refuses everywhere on the bench. That is a property of the bench's scenery, not of the type: the recipe in the example above exists because there is no hilly preset to point at.
  • Delegations nested more than 4096 deep stop the placement here, and nowhere in the game. The bench ends such a run with an error diagnostic beginning delegation depth limit hit and marks the result partial. This applies to every delegating type, not to this one in particular; apart from it, neither the bench nor the game refuses a delegation loop, so a loop through a height filter keeps recursing in both for as long as every link keeps placing. The limit is a count, so a run that hits it hits it at the same point every time; it exists so that a loop in which nothing ever declines cannot crash the tool.

Advanced: what this type costs the random stream ​

You do not need this section to use a height difference filter. It is for reading a preview value for value against the game, or for reproducing the engine exactly. RNG and determinism is the model these numbers fit into; this is this type's row in it.

This type never touches the random stream. Not on the passing path, not on the refusing path, not on the unresolved-reference path. It is a pure decision followed by a delegation, so a position it refuses and a position it accepts leave the stream in exactly the same place — everything spent inside it belongs to places_feature.

The decision, written out, with y the origin's own height and h a sampled column's:

KeyTest at each sampleEffect
max_allowed_upward_height_diff = Uy + U < hrefuse immediately
max_allowed_downward_height_diff = Dy − D > hrefuse immediately
min_required_upward_height_diff = uy + u <= hmark satisfied
min_required_downward_height_diff = dy − d >= hmark satisfied

The two limits are tested before the two requirements at every step, and the arms are walked north, east, south, west, each from step 1 to the radius inclusive. Both requirement flags start satisfied when their key is absent; both limits are skipped when theirs is. The final answer is the two requirement flags together, and a scan that refuses on a limit never reaches it.

Because a refusal aborts mid-scan and an acceptance does not, the number of height reads differs between the two outcomes — but height reads are not random values, so nothing downstream can tell.

See also ​

  • Surface relative threshold feature — the other pure gate: depth below the surface rather than variation across it, and the page that documents the surface reading both types depend on.
  • Scan surface feature — the Proxy type that offers a delegate one position per column of a chunk; pair it with this one to decorate only the columns whose neighbourhood has the shape you want.
  • Single block feature — the delegate this page's example reuses verbatim.
  • Feature delegation — what a wrapper does and does not change about the position, the Molang scope and the random stream.
  • Feature rules — how any of this reaches a world: a feature file is inert until a rule attaches it to the chunks of the biomes it belongs in.
  • RNG and determinism — the model the "costs nothing" claim above fits into.

How this page was checked ​

Everything above is a statement about Bedrock 1.26.60.22, and it holds for 1.26.50.24 and 1.26.40.26 too: this type was introduced at 1.26.40 and neither its keys, their required and optional split, nor its decision changed at 1.26.50 or at 1.26.60.

The four constraint tests, the four-arm scan and its order, the "one column satisfies a requirement, one column breaks a limit" split, the below-1 radius branch and the fact that nothing is reported when the gate refuses are stated as facts about the game.

The worked example was run end to end from the committed fixture height_difference_filter_demo.json and its zero-block result and single warning read back out of featurelab generate. The slope recipe was run the same way and its four changed cells — (5, 63, 0), (5, 64, 0), (5, 65, 0) and (0, 63, 0) — read from the result before the paragraph describing them was written; its files are printed in full on this page rather than committed to the fixture pack.

search_radius's two silent behaviours were measured rather than reasoned about, because the page previously claimed a bound the game does not enforce. A file with "search_radius": -5 loads with no diagnostic at all and its gate passes; the same file with a rise required places nothing, also silently. A file with "search_radius": 4.9 behaves in every respect like 4: run against a slope exactly five blocks away it refuses, where 5 accepts. Both were checked against the same scene, one number apart.

What is uncertain is collected in what the bench does differently: the height value itself and the flatness of every bench preset. The bench's delegation depth limit listed there is a tooling limit, not an uncertainty about the game.

Every page is a statement about Minecraft Bedrock 1.26.60.22 unless its badge says otherwise.