Structures & jigsaw
The full custom-structure pipeline: the structure definition, the structure set that places it, the template pool and processor list that assemble its pieces, and the .nbt builds plus the structure-block / jigsaw-block workflow behind them. Primary syntax targets 1.21+ (singular worldgen/structure/ and structure/ folders).
Structure definition
The structure definition JSON at data/<namespace>/worldgen/structure/ is distinct from the .nbt build files (see below). It declares which biomes the structure may generate in, its generation step, mob-spawn overrides, terrain adaptation, and — for jigsaw structures — the starting template pool and expansion limits.
worldgen/structure — jigsaw root schema shape
{
"type": "minecraft:jigsaw",
"biomes": "#minecraft:has_structure/village_plains",
"step": "surface_structures",
"terrain_adaptation": "beard_thin",
"spawn_overrides": {},
"start_pool": "example:village/town_centers",
"size": 6,
"start_height": { "absolute": 0 },
"project_start_to_heightmap": "WORLD_SURFACE_WG",
"max_distance_from_center": 80,
"use_expansion_hack": false
}
Structure types
One data-driven type plus fifteen hardcoded types. Most custom work uses jigsaw.
| Kind | Types |
|---|---|
| Data-driven | jigsaw — villages, outposts, bastions, ancient cities, trail ruins, trial chambers, and all custom structures. |
| Hardcoded (15) | buried_treasure, desert_pyramid, end_city, fortress, igloo, jungle_temple, mineshaft, nether_fossil, ocean_monument, ocean_ruin, ruined_portal, shipwreck, stronghold, swamp_hut, woodland_mansion |
Hardcoded types accept the common fields below plus a few type-specific ones — e.g. mineshaft has mineshaft_type + probability; ocean_ruin has biome_temperature, large_probability, cluster_probability; ruined_portal has a setups[] list; shipwreck has is_beached; nether_fossil has height.
Common fields (all structure types)
| Field | Notes |
|---|---|
type | One of the types above. |
biomes | A biome id, an inline array, or a #tag. Structure biome tags live in tags/worldgen/biome/. |
step | Generation step enum — surface_structures is typical. (Full enum: raw_generation, lakes, local_modifications, underground_structures, surface_structures, strongholds, underground_ores, underground_decoration, fluid_springs, vegetal_decoration, top_layer_modification.) |
spawn_overrides | Per-mob-category spawn rules (optional). See below. |
terrain_adaptation | none, beard_thin, beard_box, bury, or encapsulate — what each one does to the terrain, and which vanilla structures use it, is in the terrain-adaptation section immediately below. |
spawn_overrides is an object keyed by mob category (monster, creature, ambient, water_creature, water_ambient, misc, underground_water_creature, axolotls). Each entry sets a bounding_box (piece or full) and a spawns list:
spawn_overrides — one category
{
"monster": {
"bounding_box": "piece",
"spawns": [ { "type": "minecraft:pillager", "weight": 1, "minCount": 1, "maxCount": 2 } ]
}
}
An empty spawns list suppresses natural spawns inside the box — the Ancient City / Trial Chamber "no mobs wander in" trick.
terrain_adaptation decides how the world is reshaped to receive the structure. It is optional and defaults to none. It is a field of the base structure type, not of jigsaw — nether_fossil and stronghold use it without being jigsaw structures at all.
The four active values are two families of two. The beard family is for structures that sit on the surface and need ground beneath them; the bury family is for structures that sit inside rock and need to stay enclosed. Within each family, the second value is the heavier variant.
| Value | What it does to the terrain | Vanilla structures |
|---|---|---|
none | Nothing. The structure is stamped into whatever terrain is there, including open air. | 23 (by omission) + bastion_remnant, which is the only jigsaw structure to leave it unset |
beard_thin | Generates terrain under the structure to support it, and removes terrain inside it. | 25 — every village and zombie village, pillager outpost, all 18 abandoned camps, and nether_fossil |
beard_box | The heavier alternative to beard_thin. | 1 — ancient_city |
bury | Generates terrain surrounding the structure so that it ends up buried. | 2 — stronghold and trail_ruins |
encapsulate | The heavier alternative to bury. | 1 — trial_chambers |
So of the 52 vanilla structure definitions, 29 declare the field and 23 rely on the none default. The distribution is lopsided on purpose: beard_thin is the ordinary case, and the other three values are each used by exactly the one or two structures whose shape demands them.
- 22w13a (1.19) —
terrain_adaptationreplaced the booleanadapt_noise, which defaulted tofalse. Before that the choice was binary: adapt the noise or do not. - A pre-1.19 pack carrying
adapt_noisehas no equivalent tobeard_box,buryorencapsulate— those distinctions did not exist to express.
Two things follow from the fact that the adaptation reshapes terrain rather than the structure:
- It works on noise terrain. The reshaping happens while the world's terrain is being generated. A dimension whose generator does not produce terrain that way has nothing for the adaptation to bend.
- It is charged against your size budget, which is the practical constraint and is covered immediately below.
The adaptation is charged against the size budget.
Exceed the remaining allowance and the pack does not fail to validate — it fails to
load, with Horizontal structure size including terrain adaptation must not exceed 128,
which is why a structure can pass every static check and still leave the game refusing the whole pack.
The three blending adaptations cost 12, so the usable ceiling under them is 116.
Vanilla sits on that ceiling deliberately rather than near it. Reading
terrain_adaptation beside max_distance_from_center across all 52 vanilla
structure definitions, the only two that reach it are the two largest, and both stop at exactly 116:
| Structure | terrain_adaptation | max_distance_from_center |
|---|---|---|
ancient_city | beard_box | 116 |
trial_chambers | encapsulate | 116 |
| villages, pillager outpost, abandoned camps | beard_thin | 80 |
trail_ruins | bury | 80 |
stronghold | bury | unset |
Whether bury carries the same 12-block cost cannot be settled from the
game's data: no vanilla structure uses bury above 80, so nothing in the
pack exercises the limit. The rule as documented draws no exception — the horizontal cap
is stated as 1–128 when terrain_adaptation is none and
1–116 otherwise, which includes bury. That is a documented rule
rather than a measured one, so the two sources are marked apart here rather than merged:
treat 116 as the ceiling under bury, and know that vanilla
provides no example either way.
encapsulate is the value most often misread here. It reads as
“underground, different rules” while costing exactly what the
obviously-terrain-shaping beard_box costs. A field report bears this
out: a third-party pack was found with two structures sitting on the 116
ceiling rather than one — a surface keep under beard_box and its
basement under encapsulate — because the basement was assumed to
be uncharged. Every value except none is charged.
Jigsaw-specific fields
| Field | Type / values |
|---|---|
start_pool | Template pool id (must contain ≥ 1 element) — the first piece. |
size | Int, max jigsaw recursion depth. Capped at 20; villages use 6 and trial_chambers is the only vanilla structure at the cap. The lower bound is where the sources disagree — see the note below. |
start_height | Height provider (see projection behavior below). |
project_start_to_heightmap | Heightmap enum, or omit to disable surface projection. |
max_distance_from_center | Int; horizontal Chebyshev cap. Hard max 128, and the terrain adaptation is charged against it: none allows 1–128, while beard_thin, beard_box and encapsulate cost 12, leaving 116 (see the note below). |
use_expansion_hack | Bool — legacy vertical-expansion behavior. |
pool_aliases | List — redirect pool names at generation time (see below). |
dimension_padding | { "bottom": n, "top": n } — keep pieces away from world limits. |
liquid_settings | apply_waterlogging (default) or ignore_waterlogging. |
start_height × project_start_to_heightmap interaction:
- Projected (heightmap set):
start_heightis a relative Y from the surface.0= one block into the ground; positive raises, negative buries. Useabsoluteanchors. - Not projected (heightmap omitted):
start_heightis the literal spawn Y regardless of terrain. May useabsolute/above_bottom/below_top(Nether structures usebelow_topto sit under the roof).
pool_aliases entries take one of three shapes:
pool_aliases — direct, random, random_group
{ "type": "minecraft:direct", "alias": "example:target", "target": "example:pool_a" }
{ "type": "minecraft:random", "alias": "example:target",
"targets": [ { "data": "example:pool_a", "weight": 1 }, { "data": "example:pool_b", "weight": 2 } ] }
{ "type": "minecraft:random_group",
"groups": [ { "data": [ /* list of direct/random aliases */ ], "weight": 1 } ] }
data/example/worldgen/structure/tall_tower.json (single-piece surface structure)
{
"type": "minecraft:jigsaw",
"biomes": "#minecraft:has_structure/mineshaft",
"step": "surface_structures",
"terrain_adaptation": "beard_thin",
"start_pool": "example:tall_tower",
"size": 1,
"start_height": { "absolute": 0 },
"project_start_to_heightmap": "WORLD_SURFACE_WG",
"max_distance_from_center": 80,
"use_expansion_hack": false,
"spawn_overrides": {}
}
- Since 1.19 (22w11a): the consolidated
jigsawtype was introduced, merging the oldvillage/pillager_outpost/bastion_remnantconfigured-structure-feature types; the registry moved fromconfigured_structure_featuretostructure. - Since 1.21 (pack_format 48): folder is singular
worldgen/structure/;dimension_padding,liquid_settings,pool_aliases, and split horizontal/vertical distance handling are available. sizemax: was 7 in ≤ 1.20.2, raised to 20 from 1.20.3+.sizeminimum: the field reference gives the range as 0–20; the game's own codec isintRange(1, 20). Only the floor is in dispute and no vanilla structure sits on it — the shipped range is 2–20 — so treat 1 as the minimum.- Structure biome tags live in
tags/worldgen/biome/— nottags/biome/.
- Single-build structure:
size: 1, astart_poolwith onesingle_pool_element, and a start piece containing one downward/heightmap-facing jigsaw — no other pieces needed. - Every jigsaw structure gets a random rotation even with
alignedjoints — design relative-coordinate mechanics (functions, puzzles) to be rotation-agnostic behind an anchor block. - Reduce
max_distance_from_center/sizeif a sprawling structure hurts chunk-load performance.
Structure set
A structure set at data/<namespace>/worldgen/structure_set/ controls where and how often a group of structure definitions is attempted. Biome validity is not here — that lives on each structure. Vanilla groups same-frequency structures (all village biome variants) into one set.
worldgen/structure_set — root schema shape
{
"structures": [ { "structure": "example:tall_tower", "weight": 1 } ],
"placement": { "type": "minecraft:random_spread", "salt": 1234567, "spacing": 32, "separation": 8 }
}
Fields
structures[] — each { "structure": <id>, "weight": <positive int> }. On a successful attempt, one is chosen by weight among those whose biome/conditions pass.
placement common fields:
| Field | Notes |
|---|---|
type | minecraft:random_spread or minecraft:concentric_rings. |
salt | Non-negative int seed offset (unique per set). |
frequency | Float 0–1 (default 1) — attempt keep-chance. |
frequency_reduction_method | default, legacy_type_1, legacy_type_2, legacy_type_3. |
locate_offset | [x,y,z] chunk offset, each −16..16. |
exclusion_zone | { "other_set": <id>, "chunk_count": 1–16 } — fail if too near another set. |
random_spread extras: spacing (0–4096 chunks, average grid cell), separation (0–4096, minimum gap, must be < spacing), spread_type (linear default, or triangular).
concentric_rings extras (used for strongholds): distance (0–1023, ring spacing in 6-chunk units), count (1–4095 total placements), spread (0–1023, placements on the nearest ring), preferred_biomes (biome ids / #tags).
data/example/worldgen/structure_set/towers.json
{
"structures": [
{ "structure": "example:tall_tower", "weight": 3 },
{ "structure": "example:short_tower", "weight": 1 }
],
"placement": {
"type": "minecraft:random_spread",
"salt": 84920155,
"spacing": 34,
"separation": 8,
"spread_type": "linear",
"frequency": 0.6,
"exclusion_zone": { "other_set": "minecraft:villages", "chunk_count": 8 }
}
}
- Structure sets were introduced with the 1.18 worldgen rewrite; the schema is stable through 1.21.8.
- Give every custom set a unique large
salt— reused salts make different structures land on identical chunks. - Lower
spacing/separation= denser; always keepseparation < spacingor generation errors. exclusion_zoneis how vanilla keeps pillager outposts away from villages./locate structure <id>searches by structure definition, independent of the set.
Template pool
A template pool at data/<namespace>/worldgen/template_pool/ is "a loot table for builds." A jigsaw's start_pool and each jigsaw block's Target Pool point at one of these. It weights the candidate pieces and supplies a fallback.
| Field | Notes |
|---|---|
fallback | Another pool id used when elements can't fit or size is exhausted (an end-cap); minecraft:empty if none. |
elements[] | Each { "weight": <int 1–150>, "element": { ... } }. |
name | Optional — ignored on load; the file path is the id. |
element_type (inside each element):
| Type | Fields |
|---|---|
single_pool_element | location (NBT id), processors (list id/inline), projection, optional override_liquid_settings. |
legacy_single_pool_element | As single; legacy structure-block block handling. |
list_pool_element | elements[] (list of pool elements placed together), projection. |
feature_pool_element | feature (placed feature id/inline), projection. |
empty_pool_element | (nothing to place) — terminator. |
projection — the two values, and what terrain_matching actually does
projection is required on every pool element except empty_pool_element, and takes exactly two values.
| Value | Behaviour | Vanilla uses |
|---|---|---|
rigid | The piece is stamped at the Y its jigsaw connection put it at and keeps its exact shape. Houses, towers, town centres — and always the start piece. | 2,073 |
terrain_matching | Each block's Y is adjusted to follow the terrain, so a flat piece drapes over a slope instead of cutting a shelf or bridging a dip. | 183 |
terrain_matching is not a placement mode of its own. It is a shorthand: the game hardcodes a minecraft:gravity processor onto the template when it sees this projection, with heightmap at its default WORLD_SURFACE_WG and offset at -1. Everything the projection does, that processor does. (Despite the name, this processor has nothing to do with falling-block physics; it rewrites Y coordinates as the template is stamped.)
Two consequences follow directly from those two parameter values:
offset: -1means the piece is always sunk one block. That is correct for a road — the path surface finishes flush with ground level rather than sitting one block proud — and wrong for anything you wanted resting on the surface. The projection gives you no way to change it.WORLD_SURFACE_WGis the highest non-air block, which counts water. Aterrain_matchingpiece crossing a pond therefore lands on the water surface, not the bed.
That water case is not hypothetical — it is why village streets carry a processor list at all. minecraft:street_plains exists almost entirely to repair what the projection does at a waterline: a dirt_path block that lands where the world holds water becomes oak_planks, and the road's grass_block and dirt shoulders in the same situation are turned back into water. The plank bridges you see over village ponds are that rule, not a bridge piece. The remaining rule is cosmetic: 10% of path blocks revert to grass for wear.
What vanilla actually uses it for
Measured across every template pool in the game: of the 183 terrain_matching elements, 182 are village streets and street terminators — every biome variant and every zombie variant. The single remaining use is pillager_outpost/feature_plates, the ground plates scattered around an outpost's base. It is, in vanilla, a roads-and-groundplates feature and nothing else.
It deforms anything with vertical geometry. Because every column is dropped independently onto the heightmap, a piece with height does not slide down the slope — it shears. A wall built across a gradient arrives as a staircase of disconnected columns. Reserve it for pieces that are essentially one layer thick and meant to lie on the ground; make everything else rigid, and the start piece rigid without exception.
projection and the structure's terrain_adaptation pull in opposite directions and are easy to conflate. terrain_adaptation (beard_thin, beard_box, bury, encapsulate) reshapes the terrain to accommodate the structure; projection: terrain_matching reshapes the piece to accommodate the terrain. Villages use both at once — village_plains is beard_thin at the structure level while its streets are terrain_matching at the element level. Of the 28 vanilla jigsaw structures, 24 use beard_thin; ancient_city uses beard_box, trail_ruins bury, trial_chambers encapsulate, and bastion_remnant declares none.
Write the gravity processor yourself and you get the parameters the projection hides. Keep the element rigid and put a minecraft:gravity processor in its processor list, and you choose both the heightmap and the offset: offset: 0 to drape a piece on the surface instead of sunk into it, or heightmap: "OCEAN_FLOOR_WG" to follow the seabed under water rather than the water surface — which is exactly the case the vanilla street rules have to patch up after the fact. Vanilla writes zero gravity processors by hand; all 52 processor entries in its processor lists are rule, protected_blocks, block_rot and capped. Every gravity processor in the game is one terrain_matching injected.
data/example/worldgen/processor_list/drape_on_seabed.json
{
"processors": [
{ "processor_type": "minecraft:gravity",
"heightmap": "OCEAN_FLOOR_WG",
"offset": 0 }
]
}
Using beard_thin and terrain_matching together
The natural assumption is that a structure with beard_thin gets flattened ground, and that terrain_matching pieces then settle onto it so walls and floors line up. Neither half of that holds, and vanilla's own data says so.
beard_thin supports; it does not level. It generates terrain under the pieces and clears terrain inside them. The structure itself is anchored at a single point — 25 of 28 vanilla jigsaw structures set project_start_to_heightmap, which drops the start piece to the surface — and every rigid piece after that hangs off that anchor rather than off its own local ground. On a slope, distant pieces end up above the terrain and the beard fills the gap beneath them. The column of dirt and stone under a hillside village house is not a bug; it is beard_thin doing exactly its job.
Vanilla's data states the expectation directly. 24 of the 28 jigsaw structures set use_expansion_hack, documented as raising the structure's vertical limit “to prevent cut-off when terrain height varies” — and they are precisely the 24 that combine beard_thin with a heightmap-projected start. The four that leave it off (ancient_city, bastion_remnant, trial_chambers, trail_ruins) are the fixed-height and buried ones. A bearded surface structure is built on the assumption that the ground varies, not that it has been flattened.
Whether a wall matches its floor depends on where the boundary falls. Within a single piece there is one projection for the whole template: under rigid the wall and floor keep their exact relationship and always agree; under terrain_matching every column is dropped onto the heightmap independently, so the floor drapes while the wall shears into disconnected columns. Between two pieces it is worse, because the two Y values come from different computations entirely — a rigid piece takes its Y from the jigsaw connection, a terrain_matching piece from heightmap plus the injected offset: -1. They agree only by coincidence, and the sink guarantees a systematic one-block step even when the ground is level.
Keep a projection uniform along a whole jigsaw chain, and vanilla does exactly that. Across all 244 vanilla template pools that declare a projection, zero mix the two: 226 are uniformly rigid and 18 uniformly terrain_matching. Every terrain_matching pool falls back either to another terrain_matching pool or to minecraft:empty, so a road chain never degrades into a rigid end-cap. The projection changes only at the road-to-building boundary — which is where the step you see in game actually is.
So: put anything with height on rigid, and put a wall and the floor it meets in the same piece so one projection governs both. Use terrain_matching only for pieces that are essentially one layer thick, in pools that are uniformly terrain_matching with fallbacks to match. If you genuinely need flat ground, no adaptation will give it to you — beard_thin will happily support a building on a pillar rather than level the hill. Flatten it yourself with a generous rigid floor slab, or accept the pillar the way vanilla does.
Heightmaps a gravity processor can target
| Heightmap | Highest block of… | Note |
|---|---|---|
WORLD_SURFACE_WG | any non-air block | The default, and what terrain_matching uses. Worldgen only; falls back to WORLD_SURFACE elsewhere. |
WORLD_SURFACE | any non-air block | Counts water and vegetation. |
OCEAN_FLOOR_WG | blocks whose material blocks motion | Ignores water — the seabed. Worldgen only. |
OCEAN_FLOOR | blocks whose material blocks motion | Server-side. |
MOTION_BLOCKING | motion-blocking blocks, plus fluids and waterlogged blocks | |
MOTION_BLOCKING_NO_LEAVES | as above, excluding leaves | Server-side. |
The _WG suffix marks when the heightmap is used, not what it measures — both variants store the same quantity. What keeps a terrain_matching road off the treetops is generation order rather than the heightmap definition: structures are placed before most vegetation features run, so the surface it samples is still bare terrain.
data/example/worldgen/template_pool/tall_tower.json
{
"name": "example:tall_tower",
"fallback": "minecraft:empty",
"elements": [
{ "weight": 3, "element": {
"element_type": "minecraft:single_pool_element",
"location": "example:tower/body",
"processors": "example:mossify",
"projection": "rigid" } },
{ "weight": 1, "element": {
"element_type": "minecraft:single_pool_element",
"location": "example:tower/body_ruined",
"processors": "minecraft:empty",
"projection": "rigid" } }
]
}
list_pool_element,legacy_single_pool_element, andoverride_liquid_settingsare Java-only; the schema is stable across 1.19 → 1.21.8.
Provide a small fallback pool (a blank wall / cap) so truncated jigsaw chains don't look sheared off. Use terrain_matching only for pieces meant to melt into the ground (roads); everything else rigid, especially the start piece.
Processor list
A processor list at data/<namespace>/worldgen/processor_list/ is an ordered set of block transformations applied as a template piece is stamped into the world (weathering, block swaps, gravity settling, loot injection). Pool elements reference it via processors. The root is a processors array-holder object:
worldgen/processor_list — root schema shape
{ "processors": [ { "processor_type": "minecraft:block_ignore", "blocks": [ { "Name": "minecraft:structure_void" } ] } ] }
Processor types
processor_type | Fields |
|---|---|
block_ignore | blocks[] (block states to skip placing). |
block_rot | integrity (0–1 keep-fraction), optional rottable_blocks (block list). |
gravity | heightmap, offset (int) — settle each column to a heightmap. |
jigsaw_replacement | none — convert leftover jigsaw blocks to air. |
rule | rules[] (see below). |
block_age | mossiness (float) — random weathering / cobweb / cracks. |
blackstone_replace | none — swap cobblestone-family → blackstone-family. |
lava_submerged_block | none — waterlogged / air-in-lava handling. |
protected_blocks | value (block #tag that must never be overwritten). |
capped | delegate (a processor), limit (int provider — max applications). |
nop | none. |
Rules & rule tests
Each entry in a rule processor's rules[]: input_predicate tests the block in the template; location_predicate tests the block already in the world at that spot; if both pass, output_state is written.
rule — one entry
{
"input_predicate": { "predicate_type": "minecraft:always_true" },
"location_predicate": { "predicate_type": "minecraft:block_match", "block": "minecraft:red_sand" },
"position_predicate": { "predicate_type": "minecraft:always_true" },
"output_state": { "Name": "minecraft:red_sandstone" },
"block_entity_modifier": { "type": "minecraft:passthrough" }
}
Rule tests (predicate_type — shared with ore targets):
| Predicate type | Fields |
|---|---|
always_true | — |
block_match | block |
blockstate_match | block_state |
tag_match | tag (no leading #) |
random_block_match | block, probability |
random_blockstate_match | block_state, probability |
Joint type only means something on a vertical joint
Minecraft only ever applies a Y rotation to a template
piece. That single fact decides where joint matters:
- Horizontal front (north/south/east/west) —
exactly one Y rotation makes the child oppose the parent, so the
rotation is already determined.
rollableversusalignedchanges nothing at all. - Vertical front (up/down) — a Y rotation does
not change an up or down face, so all four rotations are candidates and
the joint type is what picks between them.
alignedpins the piece by requiring the parent and child tops to match;rollableleaves it free, and the piece lands facing a random way.
The type is read from the parent connector — the end doing the placing — so that is where the fix goes.
When the tag is absent, vanilla defaults the joint type from the front
axis: horizontal defaults to aligned, vertical
defaults to rollable. But the structure block GUI
writes joint:"rollable" explicitly on everything it
saves. So every vertical connector saved from the GUI is
rollable whether you intended it or not, and pieces that
should line up come out facing different directions — in the
reported case, a gate roof that landed differently on all four gates. Set
joint="aligned" on the parent connector.
Because the type is inert on a horizontal joint, setting
aligned there is harmless and not a defect. A check
that flags it is a false positive — one written in production fired
on 31 groups of pieces that had always worked correctly. The narrow case
worth diagnosing is the opposite: a vertical connector,
rollable or absent, whose pool contains a piece that is not
square in plan. That piece lands turned, and may not fit.
The two joint values and the field being optional are confirmed against
vanilla-mcdoc @ eea955a6afdb. The axis-dependent default and the GUI
behaviour are observed in production by a sibling pack,
not stated in the schema — they are reported here as measured
behaviour rather than specification.
Trial spawners and vaults: the block entities a structure has to configure
Both arrived with the trial chambers and both are inert until
configured, so a structure that places one must also carry its NBT
— either baked into the saved piece, which is how vanilla does it (see trial chambers as a system), or, when a processor rule is what places the block, through append_static.
minecraft:trial_spawner
Two independent behaviour blocks: normal_config and
ominous_config (the latter used when the player carries Bad
Omen). Since 1.21.2 each accepts either
an inline config object or a string id referencing a
data-driven trial_spawner file. That indirection pays off when
the same config is needed in more than one processor list or
structure; it saves nothing when several pool elements already
share a single processor list, because that is one copy either way.
| Field | Meaning |
|---|---|
spawn_range | How far from the spawner a mob may appear (1–128). |
total_mobs / total_mobs_added_per_player | Mobs per activation, and how much each extra player adds. |
simultaneous_mobs / simultaneous_mobs_added_per_player | How many may be alive at once, and the per-player increment. |
ticks_between_spawn | Delay between spawns. |
spawn_potentials | The weighted list of what to spawn — see the casing note below. |
loot_tables_to_eject | Weighted list of reward tables; one is chosen, then used once per nearby player. |
items_to_drop_when_ominous | Table used for the ominous item spawners during the fight. |
minecraft:vault
Three parents, not two, and the split tells you which parts are yours to write:
config— authored. The only block a data pack should hand-write.server_data— runtime bookkeeping. Who has claimed it, what is still to come out.shared_data— synced to nearby clients. What players in range actually see.
| Field | Meaning |
|---|---|
config — authored | |
key_item | The item stack required to open it. |
loot_table | Reward table. Defaults to minecraft:chests/trial_chambers/reward. |
override_loot_table_to_display | Which table generates the preview item. Defaults to whatever loot_table is — see the refresh loop below. |
activation_range / deactivation_range | Player distances at which it wakes and sleeps. |
server_data — runtime | |
state_updating_resumes_at | Tick at which the loot table is rolled again to refresh the display item. |
rewarded_players | UUIDs that have already claimed it — a listed player can no longer open this vault, while others still can. |
items_to_eject | The queue still to be spat out. Each item is removed as it is ejected, and is previewed as the display item first. |
total_ejections_needed | How many the roll started with. Does not change as items leave. |
shared_data — synced to clients in range | |
display_item | The item every nearby player sees floating in the vault. |
connected_players | UUIDs currently in range. |
connected_particles_range | Distance over which the connecting particles are drawn. |
Three fields across all three parents are one mechanism, which is why reading any of them alone is misleading:
config.override_loot_table_to_displaychooses which table generates the preview;shared_data.display_itemholds the item that roll produced, and is what players actually see;server_data.state_updating_resumes_atis the tick that rolls it again.
So override_loot_table_to_display is not a cosmetic override bolted onto the side — it is one leg of a refresh loop that keeps the floating item changing while the vault waits.
SpawnPotentials and
spawn_potentials are different block entities
Same content, two casings, and which one is correct depends on the block entity — not on the version:
minecraft:mob_spawner(the ordinary spawner) uses CamelCase:SpawnPotentials,SpawnData,SpawnCount,MaxNearbyEntities. Still current.minecraft:trial_spawneruses snake_case inside its config:spawn_potentials.
And there is a third layer inside the second. The
entity compound nested in a snake_case spawn entry is
entity NBT, so it is CamelCase again. One valid rule therefore
nests CamelCase inside snake_case inside CamelCase:
"block_entity_modifier": {
"type": "minecraft:append_static",
"data": {
"normal_config": { // snake_case - block entity
"spawn_potentials": [ {
"weight": 1,
"data": {
"entity": { // snake_case key ...
"id": "minecraft:zombie",
"CustomName": { "text": "Guard" } // ... CamelCase value: entity NBT
}
}
} ]
}
}
}
This is the shape most likely to be “fixed” by someone normalising the casing, and normalising it breaks it. Nothing here is inconsistent by accident: each key belongs to whichever system owns that level.
What did go stale is the entry shape, not the
key. Up to 1.18 an entry was { Entity, Weight }; since 1.18 it
is a weighted entry whose payload carries entity,
custom_spawn_rules and — since
1.20.5 — equipment, which rolls
a loot table onto the spawned mob. Anything still documenting
Entity/Weight predates 1.18.
The eleven processor types, and which you can actually write
The worldgen/structure_processor registry holds eleven entries at 26.2. Only some are things you would ever put in a processor list by hand:
| Processor | Takes fields? | In practice |
|---|---|---|
rule | yes | The workhorse. 35 of vanilla’s 52 processor entries. |
block_rot | yes | Randomly removes blocks. 6 entries. |
protected_blocks | yes | Refuses to overwrite a block tag. 7 entries. |
capped | yes | Caps how many times a delegate applies. 4 entries. |
block_age, block_ignore | yes | Weathering and ignore-lists. Unused by vanilla. |
gravity | yes | Injected by terrain_matching. Vanilla writes zero by hand — but writing one by hand works, and is how you get at the heightmap and offset. |
jigsaw_replacement | no | Injected. Substitutes each jigsaw block with its final_state. Registered, so it can be declared — see below. |
blackstone_replace, lava_submerged_block | no | Fixed behaviour, used by bastions and ruined portals. |
nop | no | Does nothing. Useful as a placeholder delegate. |
Counts measured from vanilla’s own processor lists at 26.2; the registry contents from the game’s shipped registry dump.
jigsaw_replacement
A question with no published answer, recorded here as an open question rather than guessed at, because guessing is how the wrong thing gets repeated:
When a pool element’s own processors run, has jigsaw_replacement already substituted the jigsaw blocks with their final_state, or do the processors still see minecraft:jigsaw?
The Minecraft Wiki says only that jigsaw blocks are replaced “additionally” via the minecraft:jigsaw_replacement processor — it never states the order. Its Processor list page does not state ordering for any processor. The mcdoc schema models fields, not application order. So the honest answer today is that it is unknown, not that it is unimportant: it decides whether a rule processor can key off a colour that exists only as a final_state.
minecraft:jigsaw_replacement is a registered processor type, so nothing stops you putting it in your own list at a position you choose — which makes the ordering yours rather than implicit:
"processors": [
{ "processor_type": "minecraft:jigsaw_replacement" },
{ "processor_type": "minecraft:rule", "rules": [ ... ] }
]
The configuration currently under test in a sibling pack
"processors": [
{ "processor_type": "minecraft:jigsaw_replacement" },
{ "processor_type": "minecraft:rule", "rules": [ ... ] }
]
Applied to 1×1×1 elements whose single cell is a jigsaw with
final_state set to a coloured concrete powder, with rules
matching those colours. If the decorations appear, declaring the processor
first works; if bare powder appears, it does not.
That test answers “does declaring
jigsaw_replacement first work?” and nothing
else. It cannot tell you the implicit order, because a
powder result would only mean the lever did not help — not whether
replacement normally runs before or after. Pinning the default still
needs the discriminating test below, run with no
jigsaw_replacement declared. Both are open; neither answer
is assumed from the other.
Untested, and flagged as such. Vanilla declares jigsaw_replacement zero times — exactly the pattern gravity follows, and a hand-written gravity processor does work. That makes this principled rather than established. To settle it, place a 1×1×1 element whose one cell is a jigsaw with final_state: yellow_concrete_powder and a single rule matching minecraft:jigsaw to some obvious output: a diamond block means processors run first and their output survives, the powder means replacement wins, and a leftover jigsaw means the predicate cannot match it at all.
Position predicates (the optional position_predicate): always_true, linear_pos (min_chance, max_chance, min_dist, max_dist), axis_aligned_linear_pos (adds axis).
block_entity_modifier types — there are
exactly four: clear, passthrough (keep existing NBT),
append_static (data compound merged in),
append_loot (loot_table — sets a
LootTable so the container fills on first open).
append_static is the only way
to configure a block entity a processor creates
A rule processor can place a block, but placing a
trial spawner or a vault is only half the job — both
are inert without configuration. append_static is the route:
it merges a data compound into the block entity the rule just
created. Nothing else in the processor system can reach that NBT.
data/example/worldgen/processor_list/mossify.json
{
"processors": [
{ "processor_type": "minecraft:block_age", "mossiness": 0.2 },
{ "processor_type": "minecraft:rule",
"rules": [
{ "input_predicate": { "predicate_type": "minecraft:block_match", "block": "minecraft:sand" },
"location_predicate": { "predicate_type": "minecraft:block_match", "block": "minecraft:red_sand" },
"output_state": { "Name": "minecraft:red_sand" } }
] }
]
}
block_rot,block_age,gravity,blackstone_replace,lava_submerged_block, andnopare Java-only; the set is stable across 1.18 → 1.21.8.capped(delegate +limitint provider) andprotected_blocksare present in 1.21.
append_lootin a rule'sblock_entity_modifieris how structures ship unfilled chests.protected_blockswith a custom tag prevents a processor or terrain from overwriting hand-placed key blocks.- Wrap a heavy processor in
cappedto bound how many blocks it touches per piece.
Structure NBT & jigsaw workflow
Keep the two "structure" things straight:
data/<namespace>/structure/<name>.nbt— the build saved by a Structure Block (blocks + optional entities).data/<namespace>/worldgen/structure/<name>.json— the definition (above) that decides biomes, step, and spawns. A template pool element'slocationpoints at the.nbtbuild.
Structure Block workflow
/give @s structure_block. The placed block defaults to Load mode; click Load twice to reach Save mode.- Set a Structure Name (
namespace:path, lowercase / no spaces), a Relative Position (e.g.1 0 1), and a Structure Size (max 48×48×48; extends in +X/+Y/+Z). - Toggle Include Entities if display entities / mobs are inside (they must sit ≥ 0.01 blocks inside the box or they're dropped). Redstone state is not saved.
- Press SAVE — vanilla writes the file to the world's
generated/<namespace>/structures/(plural). Copy it into the data pack atdata/<namespace>/structure/(singular). The two folders are named differently; see below. - Use Structure Void (
/give @s structure_void) at the build's edges so natural terrain / foliage shows through instead of air. (Build with sponge as a placeholder, swap to voids last — voids have a tiny hitbox.)
The 1.21 rename applied to data pack folders only. The release note for pack_format 48 puts it exactly that way: it renamed “legacy folders like loot_tables and tags/items to loot_table and tags/item and functions to function”. Nothing else was named. Note what that does and does not tell you: the release note is silent on the world-save folder, which is an absence of a rename rather than a statement that it stayed plural. Resource packs still use models/, atlases/, blockstates/ and items/ (measured from the shipped assets). Assuming the singular rule applies everywhere sends you to a folder that does not exist, and nothing reports it: you simply find no files. Applying the singular rule everywhere sends you looking in a folder that does not exist, and nothing reports it: you simply find no files.
The plural here is documentation-only, and unverified. No shipped game artefact in this reference records it: generated/ is written at runtime by a structure block, not distributed with the game, so it is outside what can be audited here. Two Minecraft Wiki articles state the plural and the 1.21 release note does not contradict them, but silence is not confirmation and this page has not verified it against a vanilla-created save. Treat it accordingly.
A live server may not match either. generated/ is a world-save folder that anyone can reorganise by hand, and on a long-lived server they often have. A tree carrying near-duplicate namespaces, or .nbt files sitting at the namespace root with no subfolder at all, has been curated rather than written by the game, and tells you nothing about vanilla behaviour. The plural above is what vanilla writes; on a real server, list the directory rather than assume it.
Jigsaw blocks
/give @s jigsaw. Directional; the sideways interface shows:
| Field | Meaning |
|---|---|
| Target Pool | Template pool this jigsaw may spawn from (minecraft:empty if it's only a connector). |
| Name | This jigsaw's own name (what a parent's Target Name matches). |
| Target Name | The Name of the jigsaw in the child piece it should line up with. |
Turns Into (final_state) | Block that replaces the jigsaw after generation (usually minecraft:air; accepts blockstates). |
| Joint Type (up/down only) | rollable (random rotation allowed) or aligned (fixed to the indicator line). Only meaningful on a vertical front — see below. |
| Selection / Placement Priority 1.20.4+ | Higher = generated first, so key children claim space before others. |
Convention: pair a parent and child by swapping Name ↔ Target Name and namespacing both.
Single-piece structure recipe: a .nbt build (with one downward or heightmap-facing jigsaw, Target Pool minecraft:empty) → a one-element template_pool (single_pool_element, location = the .nbt, projection: rigid) → a structure definition with size: 1 pointing start_pool at that pool → a structure_set with a random_spread placement. Done.
- Structure Block max is 48³; larger builds must be split across jigsaw-connected pieces.
- A jigsaw child's bounding box cannot intersect any previously placed piece's box — put jigsaws at the very edge facing out, or keep an internally-spawned child small enough to fit.
- Max horizontal reach from the start center is 128 blocks (also bounded by
max_distance_from_centerand terrain adaptation). - Max jigsaw steps = 20 (7 in ≤ 1.20.2); villages use ~6. Deep branching hurts chunk-load performance. Everything inherits the start piece's random rotation.
- Since 1.21: the build folder is singular
structure/. Structure Blocks in ≤ 1.20 saved tostructures/, so rename after exporting a pre-1.21 build. - Since 1.20.4: jigsaw blocks expose Selection / Placement Priority.
- Since 26.3 Snapshot 7: the block-state rename reaches saved templates too, not just JSON. Every block state in a
.nbtpalette now usesid/properties. Measured across all 1,511 vanilla 26.3 templates: 24,242idkeys and 17,770properties, with zeroNameorPropertiesremaining, and all 4,272 jigsaw blocks carryingorientationunderproperties. A tool that reads only the old keys sees every 26.3 jigsaw as unrotated rather than failing loudly. Note this is the block state key, unrelated to a jigsaw’s own Name field above. Existing templates keep working: a pack whose 320 templates all carry the pre-rename keys was observed generating normally in a 26.3 world, so unlike the JSON rename — which stops a file loading outright — this one breaks tools that read templates, not the packs themselves. - Multi-palette templates: a template may carry
palettes— a list of alternative palettes — instead of a singlepalette. Twenty vanilla 26.3 templates do, all of them shipwrecks. A reader that looks only atpalettefinds no blocks at all in those, and reports an empty structure rather than an error.
Building and testing a jigsaw structure
The sections above describe each field. This one is the order of work, and the handful of rules that decide whether a jigsaw structure comes out the same every time or wanders somewhere new on every seed. The rules below come from tracing the jigsaw blocks of shipping packs rather than from reading the schema, and the numbers are measured results.
The rule that reframes everything: jigsaw cannot close a loop
A new piece only ever attaches to a free connector. Two pieces that are already placed never join each other. So the generator can never “close” a ring — a curtain wall, a cloister, a moat. What you do instead is make the geometry close and let the last connector go unfilled: walk the chain all the way round, and the final placement lands on the footprint of the start piece, is rejected as overlapping, and the connector is simply never used. The wall is continuous anyway, because the piece it wanted to place is already standing there. The ring closes by arithmetic, not by joining.
Vanilla itself ships connectors that can never fire
A jigsaw names a target, and the piece drawn from its pool must contain a
jigsaw whose Name matches it. When nothing in that pool answers, the
connector simply never fires: no error, no log line, nothing in game. This is worth knowing
because vanilla does it too. Measured across all 1,511 vanilla
26.3 templates and 245 template pools, resolving each pool through
its elements and its fallback chain:
| Jigsaw connectors in vanilla 26.3 | Count |
|---|---|
| Total jigsaw blocks | 4,272 |
Pool is minecraft:empty — a deliberate terminator | 825 |
| Target answered by the pool or its fallback | 3,184 |
| Pointing at a feature pool — inert by construction | 234 |
| Pointing at an alias placeholder — no file by design | 4 |
| Target answered by nothing at all | 25 |
The middle two rows are the ones that trip up a validator, and neither is a fault.
A feature_pool_element places a configured feature — a tree, usually
— and a feature contains no jigsaw blocks, so its target can never be
answered by anything and is simply inert; 24 of the 245 vanilla pools are feature-only.
The four placeholders are the trial-chamber spawner pools
(contents/melee, ranged, slow_ranged,
small_melee), which have no files because
pool_aliases supply them at generation
time. Whether a checker trips over the 234 depends on how it walks a pool: one that follows
only elements carrying a location skips feature elements naturally, while one that
resolves every referenced pool id has to exclude them deliberately — the first sweep
behind this table did not, and reported 259.
The remaining 25 are genuinely unanswered: 17 in
trail_ruins, 7 in bastion_remnant, 1 in
trial_chambers. Fifteen of them target hall from
trail_ruins/buildings/grouped, a pool whose twenty pieces between them offer
only room and empty.
A misspelt target produces exactly the same silence as
vanilla’s own dead connectors — the wing just is not there, and nothing tells
you why. When a structure generates short, check whether the target name is answered by
any piece in the pool it points at, rather than re-reading the piece that failed to appear.
And if you write a validator, do not fail a build on a dead connector alone: vanilla would
fail it 25 times. The signal worth acting on is a target that nearly matches a name
in its pool, which is a typo; a target that matches nothing anywhere is usually deliberate.
That rule was tested against this data: none of vanilla’s 25 dead targets
is within one or two edits of a name its pool offers, so a near-miss check catches a
misspelling while leaving vanilla clean.
Four rules that make a layout deterministic
- One pool per step, one element per pool. A wall built from a
single
wall_segmentspool drawn from repeatedly is not a perimeter; it is a random walk of unpredictable length, and it wanders off one side. Number the chain instead —segment_1,segment_2, … — each its own pool with exactly one element. - One connector name per join, used once on each side. A parent’s
target names the child’s entry connector, and that connector carries
exactly that name. Mirrored pairs such as
a2bandb2anever connect. - Cap every face you do not want to grow. Give it a jigsaw targeting
minecraft:emptyin poolminecraft:empty. Any face left with a live connector is a face that will sprout. - Corners are in the chain, and they turn. The corner piece has its entry connector on one face and its exit on the perpendicular one. It ends one arm and starts the next; it is not decoration placed afterwards.
Applied to a castle curtain wall that had been sprawling: reducing eight connectors per wall segment to one per face took the piece count from 856 to 195; breaking a gate–spacer recursion took it to 79; numbering the chain and capping every face settled it at 151 pieces with the minimum, mean and maximum identical across eight seeds. Identical across seeds is the number to aim for — it means the layout is no longer a random walk.
Symmetry is arithmetic too. With a 9-wide gate and 6-wide corner turrets, the
arm length works out to (31 + 2L) / 2 for a standoff L
— an odd numerator, so the two arms can never be equal at any standoff and
opposing arms miss the corner by one block. A 10-wide gate makes it exact.
The order of work
- Draw the chain before building pieces. Every join as parent connector → child pool → child entry connector. If you cannot write it as a list, the generator cannot follow it either.
- Build and save the pieces with structure blocks, placing jigsaw
blocks on the faces from the chain — see
Structure NBT & jigsaw workflow. Check the joint type
on every vertical connector: the structure block GUI saves them as
rollablewhether you meant it or not (joint type). - Write one pool per step, then the structure and its structure set. Choose projection per element, not per pool.
- Simulate before you generate. Drop the pack into MCJIG and look at the boxes. A pool that sprawls, recurses or never places the piece you care about is obvious in seconds, and it costs no world.
- Place it with
/place structurein a flat test world, several times at different coordinates. A deterministic layout looks the same every time; if it does not, go back to rule 1. - Read the log after every
/reload. A pool or processor list that fails to parse is never registered, and the structure silently places nothing — see silent failures.
Trial chambers as a system
Trial chambers are the most complete worked example in vanilla of a structure that has to ship configured block entities, randomised encounters and a reward loop together. Everything below is measured from the game's own data at 26.3.
The parts
| Part | Count | Where |
|---|---|---|
| Structure | 1 | worldgen/structure/trial_chambers — jigsaw, start pool trial_chambers/chamber/end, size 20, starting between Y −40 and −20 |
| Template pools | 47 | worldgen/template_pool/trial_chambers/… |
| Spawner configs | 28 | trial_spawner/trial_chamber/… — 14 spawner types, each with a normal and an ominous file |
| Loot tables | 19 | chest, spawner-eject and equipment tables |
| Processor lists | 1 | trial_chambers_copper_bulb_degradation — and it configures nothing |
Configuration lives in the pieces, not in processors
The only trial-chamber processor list ages copper bulbs. Spawners and vaults arrive configured because their block-entity data is saved into the structure pieces themselves — and the two block entities do it differently:
A spawner piece — references, not a copy
{
"id": "minecraft:trial_spawner",
"normal_config": "minecraft:trial_chamber/melee/zombie/normal",
"ominous_config": "minecraft:trial_chamber/melee/zombie/ominous"
}
A vault piece — its config written inline
{
"id": "minecraft:vault",
"config": {
"key_item": { "id": "minecraft:trial_key", "count": 1 },
"loot_table": "minecraft:chests/trial_chambers/reward"
}
}
The spawner style suits behaviour that is reused: each piece names a config file
instead of carrying a copy, so changing how a spawner behaves is one file rather than
every piece that places it. The vault needs only two fields, so inline costs nothing. The
ominous vault is the same piece shape with minecraft:ominous_trial_key and
chests/trial_chambers/reward_ominous. For every field these blocks accept,
see trial spawners and vaults.
How a chamber decides its mobs
Spawner pieces do not pick a mob. They pull from placeholder pools such as
trial_chambers/spawner/contents/melee, and the structure's
pool_aliases resolve those placeholders once per generated
structure:
| Placeholder | Alias type | Resolves to one of |
|---|---|---|
contents/ranged and contents/slow_ranged | random_group | skeleton, stray or poison skeleton — the same one for both |
contents/melee | random | zombie, husk or spider |
contents/small_melee | random | slime, cave spider, silverfish or baby zombie |
That is why a chamber feels themed rather than shuffled: every ranged and slow-ranged
spawner in one structure agrees on the skeleton variant, because a
random_group picks a whole group of aliases together, while melee and
small melee each roll on their own. Breeze spawners have no alias and are always
breezes. The three alias shapes are described under
jigsaw-specific fields; this is the clearest reason to use
the group form — whenever two placeholders must stay consistent with each other.
That timing is measured in a generated world, not inferred from the
files. Across 23 chambers generated in a 26.3 world, all 76 alias
resolutions were internally consistent: not one chamber mixed two mobs for the same
placeholder, and every chamber resolved its random_group exactly once, so
ranged and slow_ranged always agreed with each other.
melee and small_melee varied between chambers but never within
one. Two honest limits on that: it is 23 chambers from a single world, and it observes the
outcome rather than the engine’s code path.
What “ominous” changes
Measured across all 14 spawner types, the ominous config differs from the normal one in these fields:
| Field | Differs in | What changes |
|---|---|---|
loot_tables_to_eject | 14 of 14 | No normal config names its eject tables, so it takes the default. Every ominous config names them explicitly — for the zombie spawner, an ominous key at weight 3 and consumables at weight 7. |
spawn_potentials | 9 of 14 | The ominous mob also rolls
equipment — for the melee zombie, from
equipment/trial_chamber_melee with a drop chance of 0, so it is armed
but drops nothing. |
total_mobs, simultaneous_mobs | 5 of 14 | Breeze, spider, cave spider, silverfish and slime change how many mobs spawn instead. |
The last two rows do not overlap: each ominous spawner either arms its mobs or changes their numbers, never both.
The reward loop
- A cleared spawner ejects loot. Ominous configs name their tables, an ominous key
among them. Normal configs leave
loot_tables_to_ejectto its default, which these files do not state; the vault expects atrial_key, andspawners/trial_chamber/keyis the table that produces one. - The key opens a vault whose
key_itemmatches it. - The vault rolls
chests/trial_chambers/reward, which is itself a table of tables: one roll ofreward_rare(weight 8) orreward_common(weight 2); one to three rolls ofreward_common; and a 25% chance ofreward_unique.
For your own dungeon: put spawner behaviour in named
trial_spawner files and reference them from the saved pieces; keep a
vault's two fields inline; resolve encounter variety with
pool_aliases rather than by duplicating rooms; and use a
random_group for anything that has to match across the structure. Match
each vault's key_item to the key a spawner actually ejects — a
mismatched key is a vault nobody can open, and nothing will tell you.
The /place command
Used for testing structure generation without waiting for natural spawns.
/place — the four subcommands
/place feature <configured_feature_id> [<pos>]
/place structure <structure_id> [<pos>]
/place jigsaw <start_pool> <target_jigsaw_name> <max_depth> [<pos>]
/place template <template_id> [<pos>] [<rotation>] [<mirror>] [<integrity>] [<seed>] [strict]
place featuretakes a configured feature id (placement modifiers are ignored).place structureruns the full structure definition (spawn overrides, terrain adaptation).place jigsawexpands a pool tomax_depthsteps from a named jigsaw.place templatestamps a raw.nbtbuild:rotation∈none/clockwise_90/counterclockwise_90/180,mirror∈none/front_back/left_right,integrity0–1.
What each argument resolves against
The four subcommands look alike and point at three different places. This is the distinction that most often produces a confident, wrong "does not exist" — the word structure names two unrelated things that legitimately coexist in one pack.
| Subcommand | Argument parser | Resolves against |
|---|---|---|
place feature | minecraft:feature | data/<ns>/worldgen/configured_feature/ |
place jigsaw | minecraft:resource_key, registry minecraft:worldgen/template_pool | data/<ns>/worldgen/template_pool/*.json |
place structure | minecraft:resource_key, registry minecraft:worldgen/structure | data/<ns>/worldgen/structure/*.json |
place template | minecraft:resource_location — no registry | data/<ns>/structure/*.nbt |
place structure and place template do not
share a namespace. The first wants a registry entry — the JSON
structure definition under worldgen/structure/. The second wants a
file path — the raw .nbt build under
structure/. A pack routinely contains both under the same id, and
checking one against the other reports a structure that plainly works as missing.
The parser is the discriminator:
resource_key carries a registry property naming exactly
which registry, while resource_location carries none and means a path.
Prototype spawning with /place structure and /locate structure before relying on natural generation. Keep a tiny fallback / end-cap piece in every pool so truncated chains look intentional, and store rotation-sensitive logic behind an anchor block/entity so mechanics survive the random rotation.
Sources
- Jigsaw structure — Minecraft Wiki
- Structure set — Minecraft Wiki
- Template pool — Minecraft Wiki
- Processor list — Minecraft Wiki
- Tutorial: Custom structures — Minecraft Wiki
- /place command — Minecraft Wiki
- Data pack version — Minecraft Wiki
- PMC "Custom Structure Generation Documentation" (kanokarob / Datapack Hub)