Structure Configuration

Register world-generated structures — buildings, plants, dungeon pieces — with placement rules and optional branching links.

Structures are pre-built arrangements of blocks placed during world generation — trees, buildings, ruins, dungeons. This is the catalog file that registers structure placement rules; the actual block layout comes from .structure files (exported from the in-game structure editor) under mods/<YourMod>/structures/<structure-name>/.


File Location

Register the file in your mod’s manifest (mods/<YourMod>/<YourMod>.yaml):

structure-config: structure-config.yaml

Then create the file it points to, relative to your mod’s folder:

mods/<YourMod>/structure-config.yaml

The file has two top-level keys — structures: (required, the base placement catalog) and structure-links: (optional, for branching/composite structures):

structures:
  - name: my_structure
    spawn-type: surface

name must match a subfolder under mods/<YourMod>/structures/ containing one or more .structure files exported from the structure editor. There is no automatic mod-name prefixing here — name is used directly as the folder name.


Minimal Example

structures:
  - name: my_bush
    spawn-type: surface
    spacing:
      x: 5
      y: 5
    spawn-chance: 0.3
    biomes:
      - YourMod:my_biome

(A link-free structure like this no longer needs max-size — they default to the structure’s own measured size.)


Fields

FieldDefaultDescription
name(required)Matches a subfolder under mods/<YourMod>/structures/.
spawn-type(required)Where/how this structure is placed — see Spawn Types below.
biomes(none)Which biome names this structure can spawn in. Empty means it never spawns.
spawn-chance1.0Probability (0.01.0) a given eligible cell spawns this structure. Resolved once per cell from the world seed at chunk generation, not re-rolled — see Chance vs. weight. (Trees use the same key name for the same purpose.)
spawn-chance-inversefalseSelects the opposite end of the per-cell random range, so the structure lands on a different set of cells at the same densityspawn-chance: 0.5 still fills 50% of eligible cells either way. Use it to shift a layout without changing how much spawns.
spacing (x, y)1 eachSpacing, in blocks, of the placement grid cell this structure is checked against — larger values space out attempts (and allow bigger structures) at the cost of density. Must be at least 1 (a smaller value is clamped up with a warning).
max-link-depth64Maximum number of link hops followed out from this structure’s anchor. A safety bound against a self-link or link cycle chaining forever (a self-link that always attaches — chance: 1.0 in an any-of:, or a lone candidate in a one-of: — would otherwise never stop). Raise it only if you intentionally build deep link chains; there is also a hard per-anchor cap on total linked structures that no value can exceed.
max-size (width, height)(structure’s own size)Optional. The generator’s scan-reach budget — how far out it looks for this structure’s anchor so placement still works when only a distant part overlaps the chunk being generated. Defaults to (and can never go below) the structure’s own size, measured automatically from its .structure files, so a link-free structure never needs to set it. Raise it above the body size only if the structure has links that place children far from it — the value must cover the whole linked span, or those far children won’t generate.
surface-snapnoneHow the structure is positioned vertically relative to the terrain surface. none keeps its authored Y; on_top snaps it to rest one block above the surface; embedded snaps it so its top row is flush with the surface (sunk one block into the ground).
air-blocksnoneHow the structure’s own AIR cells treat existing terrain. none leaves terrain intact (the air is transparent); all carves the air space out of the ground; except_bottom carves it out but keeps terrain under the structure’s bottom row (useful for a room without a floating floor).
fill-to-surfacefalseAfter placing, extends each solid column straight down to the ground so the structure doesn’t sit on stilts. Independent of surface-snap.
placementnormalnormal uses the dense per-chunk scan (best for small, common structures). landmark uses the sparse region-grid pass for large, rare set-pieces (super-trees, boss arenas) so their size doesn’t slow generation everywhere — see Landmark Structures below.
selectionrareLandmark-only. rare keeps the spawn-chance roll (a region may have none). guaranteed bypasses the roll and places exactly one per spacing cell wherever a valid anchor exists. singular places exactly one in the whole world, in a seeded ring around origin (see distance), with an absolute guarantee — see Landmark Structures. Ignored when placement: normal.
distance (min, max)300 / 2500For selection: singular only. The ring of distances, in blocks from world origin, the one instance is placed within (|x| ∈ [min, max], side chosen from the seed). Ignored for every other selection/placement mode (setting it elsewhere logs a warning). max below min collapses to a zero-width ring at min (warned).
anchor-altitudeaverageLandmark-only, and only with surface-snap: none. How a wide body’s vertical anchor is derived from the terrain sampled across its whole footprint: average (mean surface), min (lowest surface point — the body rises through higher terrain; pair with clear-above), or max (highest point — pair with fill-to-surface).
clear-abovefalseLandmark-only. Clears (sets to air) terrain that rises above the anchor within the body’s footprint columns, up to the body’s top, so a hill doesn’t bury it. Foreground (collision) layer only.
entity-placements(none)Entities to spawn alongside the structure — see Entity & Static Object Placements below.
static-object-placements(none)Static objects (interactables, trees) to spawn alongside the structure — see below.

Spawn Types

Which values are valid depends on which world type (default/surface, cave, sky island) the structure’s biome belongs to:

ValueWorldWhere
surfaceDefaultOn the terrain surface.
anywhereDefaultNo placement restriction.
caveDefaultInside a cave’s open space.
cave_floorDefaultOn a cave’s floor.
island_surfaceSky IslandOn top of a sky island.
island_interiorSky IslandInside a sky island’s solid body.
island_undersideSky IslandOn the underside of a sky island.
voidSky IslandIn the open air between and under the islands — the void gulf. See Void-Spanning Structures below.

A spawn-type outside this list (a typo, or a value valid only in a different world type) parses without error but never spawns anything — the engine logs a warning naming the structure and listing the valid values when it loads the config, so check the server log if a structure isn’t appearing.


Landmark Structures

placement: landmark is for large, sparse set-pieces — super-trees, boss arenas, hand-authored monuments that span many chunks — as opposed to the small, common structures the default normal placement handles. It changes three things:

  • Cost isolation. A normal structure’s size widens the neighbourhood scan run for every chunk in the world, so one huge normal structure would slow generation everywhere. A landmark is excluded from that global radius and is instead discovered through its own coarse spacing grid — so its size costs nothing outside the regions it can actually appear in. Keep a landmark’s spacing much larger than its max-size (region-scale — e.g. spacing: 512 for a body a couple hundred blocks wide); a spacing smaller than the body defeats the isolation and logs a warning.
  • How many, and how strong a guarantee. selection: rare keeps the ordinary spawn-chance roll for landmarks that should be occasional finds. selection: guaranteed places exactly one per spacing cell (its location still varies with the seed — the “authored content, procedural placement” model), scanning the cell for the first valid surface anchor so a cave mouth or biome edge at the canonical spot doesn’t lose it; it is best-effort (a cell that genuinely can’t host it yields nothing). selection: singular places exactly one in the entire world — the right mode for a unique set-piece a boss lair, a world portal, anything a second copy of would break. Its one instance sits in a seeded ring distance: { min, max } blocks from origin (default 3002500), and its guarantee is absolute: if the ring target is unsuitable the engine escalates — scan the target cell, then widen outward, then relax the biome filter, and finally manufacture a site (clearing/filling terrain) so placement can never fail. Each escalation past the ordinary cell scan is logged, so a world that had to reach for a fallback is visible in the server log rather than silent.
  • Footprint fit. With surface-snap: none (rigid placement), anchor-altitude derives the anchor from the surface across the whole footprint (not one column), and clear-above carves away any hill that would bury the body. fill-to-surface already extends each column down to its own local surface.
structures:
  - name: greatbough           # a colossal multi-chunk super-tree
    spawn-type: surface
    placement: landmark
    selection: guaranteed       # one per region, always
    spacing: { x: 512, y: 512 } # region-scale — much larger than the body
    max-size: { width: 180, height: 320 }
    anchor-altitude: min        # base at the lowest surface under the footprint...
    clear-above: true           # ...and carve the hill it rises through
    fill-to-surface: true
    biomes:
      - YourMod:smotherwood
structures:
  - name: border_seal_lair      # a unique boss lair — exactly one in the world
    spawn-type: surface
    placement: landmark
    selection: singular
    distance: { min: 300, max: 2500 } # somewhere in this ring around origin (seed picks where)
    spacing: { x: 512, y: 512 }
    max-size: { width: 120, height: 160 }
    fill-to-surface: true       # so a manufactured site (last-resort rung) reads cleanly
    biomes:
      - YourMod:blighted_reach

guaranteed is best-effort per cell; singular is absolute. A guaranteed cell whose terrain genuinely can’t host the structure (no valid surface, wrong biome throughout) yields nothing there, logged as a warning. singular cannot fail: it escalates (widen the ring → relax the biome → manufacture a site) until it places, logging each step it had to take. singular works for every spawn typesurface, cave/cave_floor, and island_surface/island_underside/island_interior/void. A singular structure that falls to the manufacture-a-site rung brings its own footing: a cave lair carves its pocket via air-blocks; an island set-piece supplies its own platform in its authored geometry (set fill-to-surface/clear-above on a surface lair so a fabricated ground reads cleanly). Note guaranteed (as opposed to singular) on cave/island still only bypasses the roll without a cell scan.

A singular landmark reserves its footprint and wins every conflict. Any recurring landmark (selection: rare or guaranteed) whose body would overlap a singular structure’s footprint is dropped there — the singular boss lair never has a flavour ruin punched through it. The reservation is a safe over-estimate based on max-size, so leave a singular landmark’s max-size accurate. This only governs landmark-vs-landmark: two recurring landmarks can still overlap each other (give overlapping-prone recurring landmarks distinct biomes), and small placement: normal structures are not pushed out of a lair’s footprint.

Landmarks are for monolithic set-pieces, not link chains. A landmark should be a single authored body (a super-tree, an arena). If a structure uses links that place children far from the anchor, do not make it a landmark: landmarks are excluded from the global scan radius (that is what keeps them cheap), so raising their max-size no longer widens the range that guarantees far-placed link children generate — the far end of a long chain can then fail to appear depending on which direction chunks stream in from. Keep linked, spread-out structures on placement: normal and set their max-size to cover the whole linked span, as usual.


Void-Spanning Structures

spawn-type: void places an authored structure into the open air between and under sky islands — the void gulf — rather than on or inside island matter. It is the vehicle for large set-pieces strung across the emptiness of a sky-island world: a boss arena hung in the deeps, or web/bridge geometry spanning the gaps between isles.

Unlike the island_* types, which scan for solid island matter to attach to, a void anchor requires an open-air cell within the feature’s altitude band — so the structure lives in the archipelago’s vertical space, not in infinite empty sky. The vertical home of the structure is the sky-island feature’s own min-altitude/max-altitude band, which is what controls where the void set-piece lands:

  • Register the structure on a low “gulf” feature (a feature whose altitude band sits below the walkable isles) and it hangs in the deep gulf under the archipelago.
  • Register it on the surface archipelago feature and it fills the open gaps between the isles.

The span itself comes from the structure’s own authored .structure geometry — the engine drops the set-piece into a valid void cell; the bridges, platforms, and web that reach from isle to isle are painted into the structure. Pair void with placement: landmark so a large arena is placed sparsely and cost-isolated (see Landmark Structures above), exactly as a surface super-tree is.

structures:
  - name: web_gulf_arena        # a multi-chunk arena strung across the void
    spawn-type: void
    placement: landmark
    selection: guaranteed       # one per region
    spacing: { x: 512, y: 512 } # region-scale — much larger than the body
    max-size: { width: 300, height: 400 }
    biomes:
      - YourMod:web_gulf

A void set-piece composes with a world’s void-floor-y death plane: keep the arena’s floor above that Y (via the hosting feature’s altitude band) so the lethal void sits below the playable geometry, not through it.

Void anchoring is single-cell, like the island_* types: eligibility is tested at the anchor cell only, so a very wide body could overlap island matter at its edges even when the anchor is clear. Give a void arena its own feature/biome band that no isles occupy, as you would keep overlapping landmarks in distinct biomes. Footprint-wide clearance verification is a possible future refinement.


Entity & Static Object Placements

Both are a map keyed by marker id, and each id’s value is a one-of: list of candidates:

static-object-placements:
  chest_nook:                              # a marker id you painted in the editor
    one-of:
      - static-object-type: interactable
        name: chest
        loot-table: test
  loot_corner:                             # a second marker id
    one-of:
      - static-object-type: interactable
        name: barrel
        weight: 4                          # ...one of these two is picked, 80% barrel
      - static-object-type: interactable
        name: crate
        weight: 1                          # 20% crate

Assigning ids in the editor: place a spawn/extender marker block, hover it, and press F3 to type its id (Enter confirms, Esc cancels, an empty id clears it). The hovered marker’s current id shows by the cursor. Ids are saved into the .structure file on export.

The key is the marker’s id, painted per marker in the structure editor and stored in the .structure file — not a probability table, and no longer positional:

  • Each spawn-marker block carries an id you assign in the editor. A placement entry binds to the marker with the matching id, so moving or reordering markers in the editor never reshuffles bindings (the old behaviour, when groups were a positional list bound to the Nth marker in scan order). A marker whose id no entry references — or that was left unnamed — spawns nothing and logs a warning; an entry whose id matches no marker simply never fires.
  • Every marker always spawns exactly one entry — which is what one-of: names. If an id lists more than one candidate, one is chosen at random (a deterministic per-location roll), in proportion to each candidate’s weight — letting a single marker spawn a weighted pick from a set (e.g. the 80/20 barrel-or-crate above).
  • Each candidate takes an optional weight (default 1.0): its share of the marker’s single draw, relative to the other candidates. It is not a probability — 4 vs 1 and 0.8 vs 0.2 are the same 80/20 split. Omitting weight on every candidate makes the pick uniform (the previous behaviour). weight: 0 opts a candidate out; if every candidate is 0 the marker spawns nothing. A placement entry takes no chance — that is an any-of: concept, and writing it (or any other field the entry’s type doesn’t read, e.g. default-state on a tree) is a load-time error naming the key.

one-of: here vs. in a structure link. Placements only ever use one-of: (a weighted single pick — the same weighting a link’s one-of: uses); there is no any-of: for placements, since a marker is a single cell that spawns exactly one thing. A link group, by contrast, chooses between one-of: (exactly one, by weight) and any-of: (each rolls its own chance) — see any-of: vs. one-of:.

Each entry needs an entity-type (entity-placements) or static-object-type (static-object-placements) discriminator plus that type’s own reference fields, and optionally a loot-table (see Loot Table Configuration) to fill that instance’s inventory on spawn. A loot-table naming a table no loaded mod defines (e.g. a typo) is warned about at startup and leaves that instance’s inventory empty.

The reference field is not spelled the same on both sides. A static-object placement names its object with name; an entity placement of entity-type: mob names its mob with entity-name:

The accepted keys depend on the discriminator, and the list below is exhaustive — anything else fails the entry at load:

PlacementDiscriminatorNames the content withAlso accepts
static-object-placementsstatic-object-type: treenameloot-table, weight
static-object-placementsstatic-object-type: interactablenameloot-table, weight, collidable, default-state, interact-cooldown-ms
entity-placementsentity-type: mobentity-nameloot-table, weight, spawn-group, display-name, health, oxygen
entity-placementsentity-type: player / item_drop(nothing — the type is the whole reference)loot-table, weight

weight (default 1.0) is the candidate’s share of its marker’s single one-of: draw — see the bullet list above.

The extra keys on an interactable placement, and health / oxygen on a mob, override that object’s catalog values for this placed instance only, leaving interactable-config.yaml / mob-config.yaml untouched. Note that collidable, default-state and interact-cooldown-ms are legal only on an interactable — writing one on a tree is an error, not a no-op.

A mob placement’s optional spawn-group overrides which natural-spawn pool this spawned instance counts against, defaulting to Neutral rather than to whatever the mob’s own catalog entry declares — set it explicitly if the structure’s mobs should count against the same pool as naturally-spawned ones. See spawn-group.


structure-links lets one placed structure spawn additional connected structures at fixed offsets — useful for branching layouts (a cactus with random limb variations, a village with a random number of houses) that a single fixed .structure file can’t express:

structure-links:
  - name: cactus_base
    base-path: "/CACTUS/"
    links:
      arms:                                # the extender marker's id
        any-of:
          - link: cactus_left_1
            path: "/CACTUS_LIMBS/CACTUS_LEFT_1.structure"
            chance: 0.10
          - link: cactus_right_1
            path: "/CACTUS_LIMBS/CACTUS_RIGHT_1.structure"
            chance: 1.0

name matches a structure-spawn point from one of the base structures: entries (or another link’s own link name, allowing chains). base-path is a folder prefix prepended to every path below it. links is a map keyed by the extender-marker id each group attaches at — paint an id on each structure-extender marker in the editor, then key its group here. (This replaces the old positional list, where the Nth group bound to the Nth extender marker in scan order and silently reshuffled if you moved a marker.) A marker whose id no group references grows nothing; a group whose id matches no extender marker never attaches.

Offsets are derived from the marker by default. A child attaches with its own spawn point on the parent’s extender marker, so you usually omit x-offset/y-offset entirely — set them only to nudge the child off that connection point.

any-of: vs. one-of:

Each group picks one of two rules, and the wrapper key you write is the choice. They are not interchangeable, and each accepts a different per-entry field:

GroupAttachesPer-entry fieldUse for
any-of:zero, one, or several — each candidate rolls alonechance (default 1.0)candidates that don’t exclude each other
one-of:always exactly one, drawn in proportion to weightweight (default 1.0)mutually exclusive candidates

Writing weight inside an any-of:, or chance inside a one-of:, is a load-time error naming the key — the field would otherwise look like it was doing something it isn’t.

If this feels familiar, it’s the same split a drop-table makes between its drops: (independent chance per entry) and its groups: (weighted pick-one), down to each key being rejected on the wrong side.

any-of: — each link’s chance is simply its own spawn probability, so chance: 1.0 always attaches and 0.0 never does. The cactus above uses this: a left limb 10% of the time, a right limb always, independently.

one-of: — exactly one candidate always attaches, so there is no “nothing happened” outcome. weight is relative within the group, not a probability: 0.9 against 0.1 means 90%/10%, and so does 9 against 1. A single-candidate one-of: therefore always attaches that candidate. weight: 0 opts a candidate out; if every candidate is 0 the junction grows nothing.

This is what lets a chain terminate deliberately. A road segment that either continues or is capped by a house is mutually exclusive, so it needs one-of: — with any-of: the two rolls are independent, and the common outcome is that neither fires and the chain just stops with no house at all:

  - name: village
    base-path: "/VILLAGE/"
    links:
      road_ahead:                     # extender marker where the road continues
        one-of:                       # continue the road, or cap it — never both, never neither
          - link: left_house
            path: "/HOUSES/LEFT_HOUSE.structure"
            weight: 0.1               # 10% -> the chain ends here, in a house
          - link: village
            path: "VILLAGE.structure"
            weight: 0.9               # 90% -> another road segment; mean run ~10
            surface-snap: embedded
      road_end:                       # a second extender marker
        one-of:
          - link: right_house         # sole candidate -> the far end is always a house
            path: "/HOUSES/RIGHT_HOUSE.structure"

Both rules are deterministic per world seed and position, so a given seed always reproduces the same layout.

A link may point back at its own parent (a self-link) to build repeating structures like the village road above — but a self-link that always attaches would chain endlessly. Give it a real chance of stopping: in an any-of: group that means a chance below 1.0, and in a one-of: group it means at least one sibling with a non-zero weight (a lone self-link in a one-of: never terminates, since exactly one candidate always attaches). Generation is bounded by the parent structure’s max-link-depth (default 64 hops) plus a hard total cap, so a runaway chain is stopped rather than hanging the world; raise max-link-depth only for intentionally deep chains.

FieldDefaultDescription
link(required)A name for this specific link instance.
path(required)The .structure file to place, relative to base-path.
chance1.0any-of: groups only. Probability (0.01.0) this specific link spawns, rolled independently of its siblings. An error inside a one-of: group.
weight1.0one-of: groups only. This candidate’s share of its group’s single draw, relative to its siblings (not a probability — 0.9 vs 0.1 and 9 vs 1 are the same split). 0 opts it out. An error inside an any-of: group.
x-offset / y-offset0 eachOptional nudge from the connection point. By default the child attaches with its own spawn point on the parent’s extender marker (offset derived from the marker), so you only set these to shift it off that point.
surface-snapnoneSame meaning as the base structure field above (none / on_top / embedded).
air-blocksnoneSame meaning as the base structure field above (none / all / except_bottom).
fill-to-surfacefalseSame meaning as the base structure field above.

Complete Example

Based on the real shipped structures:

structures:
  - name: cactus
    spawn-type: surface
    spacing:
      x: 5
      y: 5
    spawn-chance: 0.7
    max-size:
      width: 5
      height: 10
    biomes:
      - desert

  - name: base
    spawn-type: cave_floor
    spacing:
      x: 40
      y: 40
    spawn-chance: 0.5
    air-blocks: except_bottom
    max-size:
      width: 20
      height: 20
    static-object-placements:
      vault:                             # marker id painted on the chest's spawn marker
        one-of:
          - static-object-type: interactable
            name: chest
            loot-table: test

  - name: village
    spawn-type: surface
    biomes:
      - plains
    spacing:
      x: 50
      y: 50
    spawn-chance: 0.6
    max-size:
      width: 30
      height: 30
    surface-snap: embedded

structure-links:
  - name: village
    base-path: "/VILLAGE/"
    links:
      road_ahead:
        one-of:
          - link: left_house
            path: "/HOUSES/LEFT_HOUSE.structure"
            weight: 0.1
          - link: village
            path: "VILLAGE.structure"
            weight: 0.9
            surface-snap: embedded
      road_end:
        one-of:
          - link: right_house
            path: "/HOUSES/RIGHT_HOUSE.structure"

Last updated