# 17 - Trees, Fungi, Coral, Crystals, Formations, Ruins Procedural objects are structures Iris builds from JSON parameters instead of loading from `.iob` files. Each entry bakes a small pool of deterministic variant objects at engine start, then scatters them exactly like an object placement. Six families exist — trees, fungi, coral, crystals, formations, ruins — and they all live under `proceduralObjects` on a biome or a region. Related: `12 - Regions.md`, `13 - Biomes.md`, `15 - Caves & Carving.md`, `16 - Surfaces, Decorators & Deposits.md`, `18 - Structures Overview.md`, `19 - Objects.md`, `20 - Object Placement.md`. ## The mental model Two things happen at completely different times. **Bake.** The first time an entry is touched — normally during the engine's cache warm-up — Iris runs the family's generator `variants` times and produces that many in-memory `IrisObject` instances. The bake is a pure function of the entry's fields and its `seed`, so the same JSON always produces byte-identical variants on every platform and every restart. An entry that bakes nothing (impossible dimensions, an empty palette) is silently skipped at placement time, so a family that never appears is worth checking in the logs first. **Place.** During mantle object generation, per chunk, for each entry in scope: 1. Roll `chance` once, with a small ±0.005 jitter, for the whole entry. 2. If it passes, make `density` attempts. 3. Each attempt picks a random baked variant, picks a random X/Z inside the chunk (or searches for a cave anchor, see below), and hands the variant to the ordinary object placer with the entry's `mode`, `rotation`, `clamp`, `translate`, `underwater`, and stilt or vacuum settings. So `chance` is per chunk and `density` is per chunk-that-passed. `chance: 0.5, density: 4` gives you four objects in half the chunks and none in the rest — clumpier than `chance: 1, density: 2`, which gives two everywhere. Placement runs in the same mantle stage as `.iob` object placement, before the terrain blocks exist, which is why procedural objects can be anchored to carved cave space and why they respect the same surface-support rules as regular objects (`20 - Object Placement.md`). **Scope.** Three lists are read per chunk, all resolved at the chunk centre: the surface biome's `proceduralObjects`, the region's, and the cave biome's (only when it differs from the surface biome). Everything in all three is evaluated; they add rather than override. **Cost.** The mantle object component's radius grows to cover the largest baked variant across the whole pack. One 60-block formation therefore widens the generation footprint for every chunk in the world, not just the biome that uses it. Keep large shapes rare and large *entries* rarer. ## Walkthrough: procedural trees in a biome Use a validating `OVERWORLD` pack with `useMantle` and `decorate` on. Save this as `biomes/tutorial/tree-test.json`, list `tutorial/tree-test` as a region land biome, and temporarily set the dimension `focus` to the same key. ```json { "name": "Procedural Tree Test", "derivative": "minecraft:plains", "vanillaDerivative": "minecraft:plains", "layers": [ { "palette": [{ "block": "minecraft:grass_block" }] } ], "generators": [ { "generator": "flat", "min": 16, "max": 16 } ], "proceduralObjects": { "trees": [ { "name": "tutorial-oak", "chance": 1, "density": 2, "variants": 4, "seed": 9001, "trunk": "minecraft:oak_log", "leaves": "minecraft:oak_leaves", "profile": "OAK", "heightMin": 7, "heightMax": 11, "plausible": true } ] } } ``` 1. Reuse `generators/flat.json` from `26 - Example - Minimal Dimension.md`, validate the pack, and open Studio on seed `1337`. 2. Fly out and generate fresh chunks. Success is two oaks per chunk, drawn from four distinct silhouettes, rooted on the grass, with leaves carrying normal decay distances. 3. If nothing appears: confirm the biome is focused and the chunks are new; confirm `useMantle` is true; check the console for bake failures. Keep `chance: 1` until you have seen a tree. 4. Break a leaf block and watch the canopy. With `plausible: true`, leaves more than six blocks from wood are permanently persistent, and everything closer decays normally when you cut the trunk. 5. Tune height, profile, trunk shape, and canopy before touching `chance` and `density`. Reopen Studio and confirm the same seed reproduces the same four shapes, then drop `chance` to something forest-like and remove `focus`. Variant heights are not random per variant. Iris spreads them evenly across `heightMin..heightMax` and adds up to ±30% of one step of jitter, so four variants over `7..11` give roughly 7, 8, 10, 11 rather than four coin flips. Raising `variants` therefore fills in the height range rather than just adding randomness. ## Container (`IrisProceduralObjects`) Snippet key: `procedural-objects`. Valid on biomes and regions. | Field | Type | Family | |-------|------|--------| | `trees` | `IrisProceduralTree[]` | Trunk plus canopy, optionally branches, roots and forks | | `fungi` | `IrisFungus[]` | Stem plus cap, or sideways shelf brackets | | `coral` | `IrisCoral[]` | Waterlogged reef structures in five forms | | `crystals` | `IrisCrystal[]` | Budding base with tapered shards; cave-first | | `formations` | `IrisFormation[]` | Natural rock landmarks with strata and erosion | | `ruins` | `IrisRuin[]` | Man-made shapes with weathering, erosion and burial | ```json { "proceduralObjects": { "trees": [ { "...": "..." } ], "crystals": [ { "...": "..." } ] } } ``` ## Shared placement fields Every family carries this same block of fields and converts them into an `IrisObjectPlacement` at placement time. Defaults differ per family, so the values below are noted where they diverge. | Field | Default | What it does | |-------|---------|--------------| | `name` | family name | Used in logs and as the variant load key. Must be unique within a pack if you want to identify variants in debug output | | `chance` | 0.4 (trees, fungi, coral), 0.2 (crystals), 0.05 (ruins), 0.02 (formations) | Probability the entry attempts anything at all in a given chunk | | `density` | `1` | Attempts once the chance roll passes. Raising this clusters objects; raising `chance` spreads them | | `variants` | 8 (trees), 6 (all others) | How many distinct shapes to bake, 1 to 64. Below about 4 the repetition is visible; above about 16 you are paying memory for variation nobody sees | | `seed` | `1337` | Bake seed. Change it to get an entirely different set of shapes from identical settings | | `mode` | `CENTER_HEIGHT`, except ruins `MIN_HEIGHT` | Terrain anchor mode. `MIN_HEIGHT` plants the lowest footprint corner, good for slabs and rubble on slopes; `CENTER_HEIGHT` averages, good for tall pillars | | `rotation` | identity | Rotates placements so variants do not all face the same direction | | `clamp` | unlimited | Min and max terrain height at which the entry may place | | `carvingSupport` | `SURFACE_ONLY`, except crystals `CARVING_ONLY` | `SURFACE_ONLY` places on terrain, `CARVING_ONLY` searches carved cave space, `ANYWHERE` uses the surface path without the surface-only rejection | | `underwater` | `false`, except coral `true` | Anchors on terrain height ignoring the water surface, so the object grows from the seafloor instead of the waterline | | `translate` | zero | XYZ offset. A negative Y sinks the object into the ground | | `stiltSettings` | unset | Configuration for `STILT`, `MIN_STILT`, `FAST_STILT`, `FAST_MIN_STILT`, `CENTER_STILT`, `ERODE_STILT`, `ORGANIC_STILT` | | `vacuumSettings` | unset | Configuration for `VACUUM`, `VACUUM_HIGH`, `VACUUM_FAST`, `VACUUM_ORGANIC`, `VACUUM_WAVY` | | `surfaceSupportBuffer` | `3` (formations only) | Solid ground required around the footprint. Iris uses the larger of this and the dimension `objectSurfaceSupportBuffer` | Variant load keys are `procedural/tree/#` for trees and `procedural/#` for every other family. Only `CARVING_ONLY` entries take the cave path. Those search the chunk for an anchor using the active cave profile's `defaultObjectAnchor`, `anchorScanStep`, `anchorSearchAttempts`, and `objectMinDepthBelowSurface`; an entry that finds no anchor is skipped for that attempt. The profile's `defaultObjectPlaceMode` overrides the entry's `mode`, but only when the entry left `mode` at the default `CENTER_HEIGHT`. See `15 - Caves & Carving.md`. `plausible` is a **tree-only** field. Every other family reports `false`, which means their objects are placed with decay prevention active. This matters only for blocks that have leaf-style decay properties. ## Procedural trees (`IrisProceduralTree`) Snippet key: `procedural-tree`. Built by the trunk builder (which returns one or more limbs), then a canopy per limb, then decorators, then roots, then leaf plausibility. ### Materials and size | Field | Default | What it does | |-------|---------|--------------| | `trunk` | `minecraft:oak_log` | The log block. Ignored entirely when `trunkPalette` is set | | `trunkPalette` | unset | Noise-driven palette for the trunk, for streaked or mixed-species wood | | `leaves` | `minecraft:oak_leaves` | The leaf block. Ignored when `leavesPalette` is set | | `leavesPalette` | unset | Noise-driven leaf palette. Only entries that are genuinely leaves receive decay distances | | `plausible` | `true` | See below | | `heightMin` / `heightMax` | `8` / `12` | Trunk height range spread across the variant pool. Heights below 2 are clamped up | | `trunkWidth` | `1` | Base thickness. 1 is a single column, 2 is a 2x2, 3 a 3x3 | | `profile` | `OAK` | Named silhouette driving default crown radii and layer placement | `IrisTreeProfile`: `OAK`, `BIRCH`, `SPRUCE`, `JUNGLE`, `ACACIA`, `DARK_OAK`, `DARK_OAK_FLAT`, `DARK_OAK_FLAT_WIDE`, `CHERRY`, `PALM`, `WILLOW`, `COLUMNAR`, `BUSH`, `MEGA_SPRUCE`. **What `plausible` actually does.** With `true`, Iris runs a breadth-first search from the trunk through the leaf volume. Leaves within 6 steps of wood get `persistent=false` and their real `distance`, so vanilla decay works exactly as it would on a grown tree. Leaves 7 or more steps away, or unreachable entirely, get `persistent=true` and `distance=7` so they never vanish. It also runs a support pass that prunes orphaned leaf clumps. With `false`, every leaf is forced `persistent=true, distance=1` — a raw dump that never decays and reads as built rather than grown. ### Trunk shaping | Field | Default | What it does | |-------|---------|--------------| | `trunkShape` | `CONSTANT` | Function mapping normalized height to a trunk width multiplier. `TAPER`-like effects come from `LINEAR` with `shapeEnd` below 1 | | `shapeStart` / `shapeEnd` | `1` / `1` | Width multipliers at base and top for `LINEAR` | | `shapeSteepness` | `5` | Transition sharpness for `SIGMOID` | | `shapeBase` | `2.718281828` | Logarithm base for `LOG` | | `shapePeriod` / `shapeAmplitude` | `1` / `0.2` | Wobble frequency and depth for `SINE`, for lumpy or knotted trunks | | `shapePeakOffset` / `shapeFloor` | `0.5` / `0.5` | For `PARABOLIC`: where the waist sits (0 base, 1 top) and how thin it gets | | `leanAngle` | `0` | Degrees off vertical. Non-zero produces a leaning trunk; combine with rotation for wind-swept stands | | `leanAzimuth` | `0` | Compass direction of the lean | | `trunkCurve` | `LINEAR` | How lean accumulates over height. `SIGMOID` bends mostly in the middle; `CONSTANT` shears uniformly | | `curveSteepness` | `8` | Sharpness of that bend for `SIGMOID` | | `leanAzimuthMode` | `CONSTANT` | Lets the lean *direction* change with height, which is what turns a lean into a spiral or a wander | | `azimuthStart` / `azimuthEnd` | `0` / `0` | Endpoints for `LINEAR` azimuth | | `azimuthTurns` | `1` | Full rotations over the trunk for `SPIRAL` | | `azimuthAmplitude` / `azimuthPeriod` / `azimuthOffset` | `90` / `1` / `0` | Sine azimuth wobble controls, for S-curved trunks | | `azimuthScale` | `1` | Noise scale for `NOISE` azimuth | | `azimuthWhorlCount` | `5` | Positions per ring for `WHORL` azimuth | | `trunkForks` | `1` (1–6) | Splits the trunk into this many limbs above `forkHeight`. Each limb gets its own canopy | | `forkHeight` | `0.5` | Normalized height of the split | | `forkAngle` | `25` | Degrees each fork leans outward | | `secondaryTrunk` | unset | Optional second wood block for a banded trunk. Ignored when `secondaryTrunkPalette` is set | | `secondaryTrunkPalette` | unset | Noise palette for that band | | `secondaryTrunkStart` / `secondaryTrunkEnd` | `0.5` / `1` | Normalized band bounds | | `roots` | `true` | Builds a root system so the tree meets uneven ground instead of hovering | | `rootStyle` | `BUTTRESS` | `TAPROOT` drives one thick root down, `BUTTRESS` flares several out at the base, `STILT` lifts the trunk on legs | | `rootDepth` | `0` | Explicit reach in blocks; 0 scales automatically with tree height | | `rootFlare` | `0` | Explicit flare radius; 0 scales automatically | `IrisTreeFunction`: `CONSTANT`, `LINEAR`, `SIGMOID`, `LOG`, `SINE`, `PARABOLIC`, `EXPONENTIAL`, `SQRT`, `STEP`, `BELL`, `EASE_IN_OUT`. `IrisTreeAzimuthMode`: `CONSTANT`, `LINEAR`, `SPIRAL`, `SINE`, `NOISE`, `RANDOM`, `GOLDEN_ANGLE`, `ALTERNATING`, `WHORL`, `ZIGZAG`. ### Canopy (`IrisTreeCanopy`) Snippet key: `tree-canopy`. Stacked discs, sized by the profile unless you override them. | Field | Default | What it does | |-------|---------|--------------| | `startAngle` | `90` | Elevation of each disc in degrees. Exactly 90 is a flat disc, below 90 domes downward toward a sphere, above 90 flares out into an umbrella | | `squish` | `1` | Vertical scale of the crown volume. Below 1 flattens it | | `mode` | `TRIMMED` | How each disc fills with leaves | | `leafDensity` | `0.85` | Fill probability for the `DENSITY` and `NOISE` modes | | `crownStretchX` / `crownStretchZ` | `1` / `1` | Elliptical crowns for wind-shaped or asymmetric trees | | `layers` | `[]` | Explicit discs. Any entry here replaces the profile-driven layers entirely | | `branches` | unset | When set, branches build most of the canopy and only the topmost profile disc is still placed | `IrisTreeLayer` (snippet `tree-layer`) is `yOffset` (blocks above the trunk base, default `0`) and `radius` (default `2`). `IrisTreeLeafMode`: `TRIMMED`, `FILLED`, `DENSITY`, `NOISE`, `HOLLOW`, `GRADIENT`, `CLUMPED`, `TATTERED`, `SPARSE`. ### Branches (`IrisTreeBranches`) Snippet key: `tree-branches`. Adding a `branches` object switches the tree from a stack of leaf discs to real limbs with leaf balls at their tips — the difference between a vanilla oak and an old-growth silhouette. | Field | Default | What it does | |-------|---------|--------------| | `probabilityFunction` | `TOP_HEAVY` | Where branches spawn along the trunk. `TOP_HEAVY` for a crown, `PERIODIC` for conifer whorls, `BAND` for a single tier | | `probabilityConstant` | `0.5` | Chance for `CONSTANT` | | `probabilityBase` / `probabilityCrown` | `0` / `1` | Endpoints for `LINEAR` | | `probabilitySteepness` / `probabilityMidpoint` | `10` / `0.7` | `SIGMOID` sharpness and where the crown starts | | `probabilityExponent` | `2` | `TOP_HEAVY` bias; higher pushes branches further up | | `probabilityMean` / `probabilityStd` | `0.7` / `0.15` | `GAUSSIAN` centre and spread, for a single dense tier | | `probabilityScale` | `1` | Noise scale for `NOISE` | | `probabilityPeriods` | `5` | Number of whorl rings for `PERIODIC` | | `lengthFunction` | `LINEAR` | How branch length varies with height. `LINEAR` with a large `lengthCrown` gives the classic wide top | | `lengthBase` / `lengthCrown` | `1` / `4` | Endpoints for `LINEAR` | | `lengthConstant` / `lengthMax` | `3` / `4` | Value for `CONSTANT`, and the ceiling for `SIGMOID`, `LOG` and `PARABOLIC` | | `lengthSteepness` | `5` | `SIGMOID` length sharpness | | `azimuthMode` | `RANDOM` | Compass distribution of branches. `GOLDEN_ANGLE` gives even spiral phyllotaxis, `WHORL` gives rings | | `azimuth` | `0` | Fixed direction when `azimuthMode` is `CONSTANT` | | `elevation` | `0` | Starting angle from horizontal. Positive points up, negative droops | | `sag` | `0` | Catenary droop along the branch. Small values read as weight; large values give willow arcs | | `branchDepth` | `1` (0–6) | Recursion levels. 2 and above produce fractal branching and a much larger block count | | `leafStartUp` | `false` | Clamps primary branches so they never droop below horizontal | | `clusterRadius` | `2` | Leaf ball radius at each branch tip | | `clusterMode` | `TRIMMED` | Fill mode for that ball | | `clusterDensity` | `0.85` | Fill probability when `clusterMode` is density- or noise-based | | `subBranches` | unset | One extra level of sub-branches from each tip | `IrisTreeBranchProbability`: `CONSTANT`, `LINEAR`, `SIGMOID`, `TOP_HEAVY`, `GAUSSIAN`, `NOISE`, `BOTTOM_HEAVY`, `PERIODIC`, `BAND`, `INVERSE_GAUSSIAN`, `EXPONENTIAL_DECAY`. `IrisTreeSubBranches` (snippet `tree-sub-branches`): `count` (`1`), `pitchDelta` (`0`, positive bends up), `yawDelta` (`45`, horizontal fan spread), `lengthScale` (`0.5` of the parent), `sag` (`0`), `clusterRadius` (`1`), `clusterMode` (`TRIMMED`), `clusterDensity` (`0.85`). ### Accents | Field | Default | What it does | |-------|---------|--------------| | `secondaryLeaves` | unset | A single accent block scattered through the canopy — blossoms, shroomlight, berries | | `weightedSecondaryLeaves` | `[]` | Weighted list of accent blocks (`block` plus `weight`), overriding the single block | | `secondaryLeavesPalette` | unset | Noise palette, overriding both of the above | | `secondaryLeafFraction` | `0.35` | Share of leaves replaced by the accent. Values near 1 recolour the whole crown | | `decorators` | `[]` | `IrisTreeDecorator` entries applied after the tree is built | ### Tree decorator (`IrisTreeDecorator`) Snippet key: `tree-decorator`. | Field | Default | What it does | |-------|---------|--------------| | `target` | `BRANCH_TIP` | Which set of positions is eligible | | `block` | required | Block id to place. Ignored when `palette` is set | | `palette` | unset | Noise palette, wins over `block` | | `chance` | `0.5` | Per eligible position. Use low values for sparse fruit, 1 for full coverage such as snow on the crown | | `length` | `1` | Maximum downward strand length for `CANOPY_HANG`; each column picks 1 to `length` | | `axisAware` | `false` | Orients the block's facing away from the trunk, for fences, gates and banners mounted on wood | Targets: `BRANCH_TIP`, `TRUNK_SURFACE`, `CANOPY_TOP`, `CANOPY_BOTTOM`, `TRUNK_BASE`, `LEAF_SURFACE`, `CANOPY_HANG`, `BRANCH_SURFACE`, `TRUNK_TOP`, `GROUND_SCATTER`. Branch endpoints are only collected when at least one decorator exists, so an empty `decorators` list costs nothing. ### A complete tree ```json { "name": "oak-plains", "chance": 0.35, "density": 2, "variants": 8, "seed": 9001, "trunk": "minecraft:oak_log", "leaves": "minecraft:oak_leaves", "profile": "OAK", "heightMin": 7, "heightMax": 11, "plausible": true, "roots": true, "rootStyle": "BUTTRESS", "canopy": { "mode": "TRIMMED", "startAngle": 88, "squish": 0.9 } } ``` ## Fungi (`IrisFungus`) Snippet key: `fungus`. A stem column with a cap grown on top, or a sideways shelf bracket. | Field | Default | What it does | |-------|---------|--------------| | `stem` / `stemPalette` | `minecraft:mushroom_stem` | Stem material; palette wins | | `cap` / `capPalette` | `minecraft:red_mushroom_block` | Cap material; palette wins | | `stemHeightMin` / `stemHeightMax` | `5` / `9` | Stem height range spread over the variant pool | | `stemWidth` | `1` (1–3) | 1 is a single column, 3 a chunky trunk | | `stemCurve` | `0` | Degrees of lean off vertical | | `stemLeanAzimuth` | `0` | Direction of that lean | | `stemWaveAmplitude` | `0.4` | Blocks of sideways wobble up the stem, so it is not a ruler | | `stemWavePeriods` | `1` | Full sine wobbles over the stem height | | `capShape` | `DOME` | `DOME`, `FLAT`, `FUNNEL`, `CONICAL`, `FLAT_WIDE` | | `capRadiusMin` / `capRadiusMax` | `3` / `5` | Cap radius from centre to rim | | `capThickness` | `1` (1–3) | Shell thickness. 1 is a thin skin, 3 a fleshy slab | | `capSquish` | `0.4` | Vertical flatten, 0 full height and 1 a flat disc | | `capDroop` | `20` | Degrees the rim curls toward the ground | | `capOverhang` | `2` | Blocks the cap extends past the stem before the rim begins | | `gillBlock` / `gillPalette` | unset | Underside layer — gills, or shroomlight for a glowing cap | | `gillChance` | `0.85` | Share of underside blocks replaced when a gill block is set | | `spotBlock` / `spotPalette` | unset | Speckles across the cap top | | `spotChance` | `0.18` | Share of top blocks replaced, selected by value noise so spots cluster | | `shelf` | `false` | Switches to a bracket polypore: a flat sideways fan off a very short or absent stem | | `shelfRadius` | `3` | Fan radius in shelf mode | ## Coral (`IrisCoral`) Snippet key: `coral`. Defaults to `underwater: true` and `waterlogged: true`, so it grows from the seafloor and stays alive. | Field | Default | What it does | |-------|---------|--------------| | `waterlogged` | `true` | Forces every waterloggable block in the structure waterlogged. Set false for dead, dry coral on a beach | | `form` | `BRANCHING` | `BRANCHING`, `FAN`, `BRAIN`, `PILLAR`, `TENDRIL`. Each runs a different generator | | `block` / `blockPalette` | `minecraft:tube_coral_block` | Structural body; a palette mixes tube/brain/bubble/fire/horn tones across one reef | | `tipBlock` / `tipPalette` | unset | Placed at branch tips and the top — fans, sea pickles | | `tipChance` | `0.6` | Per eligible tip position | | `heightMin` / `heightMax` | `4` / `8` | Overall height | | `spread` | `3` | Horizontal reach. Arm length for `BRANCHING`, base footprint for the others | | `sway` | `0.5` | Lateral wobble, 0 ruler-straight and 1 heavily wandering | | `branchCount` | `4` (1–12) | `BRANCHING`: arms off the central stalk | | `branchLength` | `3` | `BRANCHING`: arm length before the tip | | `branchElevation` | `55` | `BRANCHING`: degrees up from horizontal. 90 is straight up | | `branchAzimuth` | `GOLDEN_ANGLE` | `BRANCHING`: `GOLDEN_ANGLE`, `EVEN`, or `RANDOM` distribution around the stalk | | `subBranches` | `true` | `BRANCHING`: split each arm once for a bushier reef | | `subBranchCount` | `2` (1–5) | Sub-arms per arm | | `subBranchScale` | `0.5` | Sub-arm length as a fraction of the parent | | `tipClusterRadius` | `1` (0–4) | `BRANCHING` and `PILLAR`: tip cluster size | | `brainRadius` | `3` (1–8) | `BRAIN`: blob radius | | `brainRoughness` | `0.35` | `BRAIN`: surface wrinkling. 0 is a smooth dome | | `pillarRadius` | `1` (1–12) | `PILLAR`: column radius | | `fanWidth` | `3` (1–8) | `FAN`: half-width of the upright plane | | `tendrilCount` | `4` (1–12) | `TENDRIL`: number of thin wavy stalks | ## Crystals (`IrisCrystal`) Snippet key: `crystal`. A budding base blob with tapered shards radiating from it. Defaults to `carvingSupport: CARVING_ONLY` and `chance: 0.2`, so it needs carved cave space to place at all — see `15 - Caves & Carving.md` for the anchor settings that govern it. | Field | Default | What it does | |-------|---------|--------------| | `growthSurface` | `FLOOR` | `FLOOR` points shards up, `CEILING` down, `WALL` outward. This orients the baked geometry only; use the cave anchor mode to actually land it on a ceiling | | `block` / `blockPalette` | `minecraft:amethyst_block` | Shard body. A palette mixes amethyst, calcite and tinted glass into one prismatic cluster | | `tipBlock` / `tipPalette` | unset | Different block at the very point of each shard | | `tipChance` | `0.6` | Per shard | | `glow` | `false` | With no tip block set, sprinkles `glowBlock` among the tips instead | | `glowBlock` | `minecraft:glowstone` | The light source used by `glow` | | `baseBlock` / `basePalette` | `minecraft:budding_amethyst` | The blob the shards grow from | | `baseRadius` | `1.6` | Blob radius. 0 makes shards spring from a single point | | `baseNoise` | `0.35` | Surface lumpiness of the blob, so it is not a clean sphere | | `shardCountMin` / `shardCountMax` | `5` / `11` | Shards per cluster | | `shardLengthMin` / `shardLengthMax` | `3` / `8` | Shard length from base to tip | | `shardBaseRadius` | `1.4` | Thickness at the shard's base end | | `shardTaper` | `0.85` | How aggressively it narrows. 0 is a near-constant column, 1 a sharp spike. Every shard ends in a single block regardless | | `spreadAngle` | `45` | Half-angle of the cone the shards fan within. 0 makes them all parallel; large values give a starburst | | `distribution` | `GOLDEN_ANGLE` | `GOLDEN_ANGLE` for an evenly spaced rosette, `RANDOM` for a chaotic clump | | `jitter` | `0.25` | Angular randomness on top of the distribution, so the cluster never looks mechanical | ## Formations (`IrisFormation`) Snippet key: `formation`. Natural rock landmarks. Default `chance: 0.02` — these are meant to be rare, and they are the family most likely to widen the pack's mantle radius. | Field | Default | What it does | |-------|---------|--------------| | `form` | `SPIRE` | `SPIRE`, `HOODOO`, `ARCH`, `SEA_STACK`, `BOULDER`, `BASALT_COLUMN` | | `block` / `blockPalette` | `minecraft:stone` | Main rock body | | `capBlock` / `capPalette` | unset | Caprock on the crown, and the overhanging cap for `HOODOO`. Unset means the main rock everywhere | | `strataPalette` | unset | Horizontal colour bands. Every `strataThickness` blocks the palette advances, which is what produces the badlands look | | `strataThickness` | `3` (1–32) | Band thickness | | `heightMin` / `heightMax` | `14` / `26` | Total height | | `baseWidthMin` / `baseWidthMax` | `3` / `6` | Base radius | | `topWidth` | `0` | Radius at the very top before the profile applies. 0 tapers to a point | | `profile` | `TAPER` | `CONSTANT`, `LINEAR`, `TAPER`, `PARABOLIC`, `BULGE` — how the radius changes with height | | `profileWaist` | `0.55` | Normalized height of the pinch for `PARABOLIC`, used by hoodoos | | `profileWaistFloor` | `0.35` | Minimum radius fraction at that waist. Lower pinches tighter | | `lean` | `0` | Degrees off vertical; the whole body is sheared | | `leanAzimuth` | `0` | Lean direction | | `roughness` | `0.3` | 3D noise perturbation of the radius, 0 clean and 1 heavily eroded. This is the main "does it look like rock" control | | `jitter` | `0.15` | Per-block surface noise that adds and removes isolated edge blocks | | `surfaceSupportBuffer` | `3` (0–16) | Solid ground required beyond the lowest footprint blocks. Iris takes the larger of this and the dimension `objectSurfaceSupportBuffer` | | `hoodooCapRadius` | `3` | `HOODOO`: extra cap radius for the mushroom overhang. 0 disables it | | `hoodooCapHeight` | `3` (1–6) | `HOODOO`: cap slab thickness | | `archSpan` | `10` | `ARCH`: gap width between the legs | | `archThickness` | `3` | `ARCH`: leg and span thickness | | `basaltColumns` | `5` (2–12) | `BASALT_COLUMN`: columns per cluster | | `basaltColumnRadius` | `1` | `BASALT_COLUMN`: radius of each column | | `basaltHeightVariance` | `0.45` | `BASALT_COLUMN`: how much column heights differ, 0 all equal and 1 highly varied | ## Ruins (`IrisRuin`) Snippet key: `ruin`. Man-made shapes that are weathered, eroded, and partly buried. Default `chance: 0.05` and `mode: MIN_HEIGHT`. | Field | Default | What it does | |-------|---------|--------------| | `form` | `PILLAR` | `PILLAR` broken column, `WALL` gapped segment, `ARCH` two legs and a span, `FLOOR_SLAB` foundation patch, `RUBBLE` low scattered pile | | `block` / `blockPalette` | `minecraft:cobblestone` | Bulk material before weathering | | `heightMin` / `heightMax` | `4` / `9` | Structure height, or slab thickness and mound height for the flat forms | | `widthMin` / `widthMax` | `1` / `3` | Footprint along X | | `lengthMin` / `lengthMax` | `3` / `7` | Footprint along Z | | `weatheredBlock` | `minecraft:mossy_cobblestone` | The weathered swap, used when no palette is set | | `weatheringPalette` | unset | Palette of weathered variants, overriding the single block | | `mossiness` | `0.45` | Share of the structure that weathers. The mask is noise-driven and biased toward lower rows, so moss climbs from the ground | | `weatheringScale` | `1.0` (0–8) | Weathering noise scale. Higher gives busy speckles, lower gives broad mossy zones | | `erosion` | `0.25` | How crumbled it is. Blocks below this noise threshold are deleted. The bottom row and core legs are never eroded, so the shape does not collapse into confetti | | `erosionScale` | `1.5` (0–8) | Erosion noise scale. Higher knocks out small holes, lower carves large missing chunks | | `buriedFraction` | `0.2` | Fraction of the height that sits below the surface, so the ruin reads as settled | | `accents` | `[]` | `IrisRuinDecorator` entries applied after erosion | ### Ruin decorator (`IrisRuinDecorator`) Snippet key: `ruin-decorator`. | Field | Default | What it does | |-------|---------|--------------| | `target` | `TOP` | `TOP` sits on the highest block of each column, `SURFACE` clings to air-facing vertical faces, `BASE_SCATTER` rings the ground around the base | | `block` | required | Block id. Ignored when `palette` is set | | `palette` | unset | Noise palette, wins over `block` | | `chance` | `0.4` | Per candidate position | | `scatterRadius` | `2` | For `BASE_SCATTER`, how far beyond the footprint the ring extends | ## Sapling overrides (`IrisTree`) — a different system `IrisTree` lives on **object placements** (`IrisObjectPlacement.trees`), not under `proceduralObjects`. It maps a grown sapling to that placement's objects. This is a gameplay growth replacement, not worldgen scatter, and it never touches procedural trees. | Field | What it does | |-------|--------------| | `treeTypes` | Bukkit `TreeType` names this placement replaces, matched case-insensitively | | `sizes` | `IrisTreeSize` entries (`width` by `depth`) describing the sapling footprints it applies to. Width and depth are matched either way round, so a 1x2 entry also matches 2x1 | | `anyTree` | Declared but never read at runtime. Matching is by `treeTypes` only | | `anySize` | Declared but never read at runtime. Matching is by `sizes` only | Dimension `treeSettings` gates the whole feature: | Field | Default | What it does | |-------|---------|--------------| | `enabled` | `false` | Off by default. Nothing replaces grown trees until this is true | | `mode` | `FIRST` | `FIRST` uses biome matches and only falls back to region matches when the biome has none. `ALL` pools biome and region matches and picks randomly from the combined list. Dimension-level object placements are not consulted in either mode | ## Extending to the other families 1. Pick the family that matches the shape: trees for forests, fungi for mushroom biomes, coral for warm oceans, crystals for cave biomes, formations for deserts and coastlines, ruins for sparse land. 2. Add one entry with a single material, `chance: 1`, `density: 1`, `variants: 4`, and a fixed `seed`. Focus the biome and generate. 3. Get the silhouette right before touching frequency. Dimensions, profile, and roughness all change what the thing *is*; chance and density only change how often you meet it. 4. Add palettes, accents, and decorators once the shape holds up from several angles. 5. Match `carvingSupport` to the environment. Cave props also want a stilt place mode, either on the entry or through the cave profile's `defaultObjectPlaceMode`. 6. Drop `chance` to production values, remove the dimension `focus`, and verify the family stays inside the biomes and regions that declare it. The pass condition: the same seed reproduces the same shapes across a Studio restart, the placement leaves believable negative space, and no variant is skipped in the log. When exact hand-authored geometry matters more than variation, use an `.iob` object instead (`19 - Objects.md`). ## Practical notes - Procedural objects are independent of `objects` placements, decorators, and jigsaw structures. Nothing is shared except the placement machinery. - `plausible` exists only on trees. The other five families always place with decay prevention active. - `IrisTree` (sapling to object) and `IrisProceduralTree` (worldgen bake) are unrelated types with similar names. - An entry whose bake produces no blocks is skipped silently at placement time. If a family never appears and `chance: 1` did not help, look for bake warnings in the console. - Every baked variant enlarges the mantle object radius for the entire engine. A single very large formation costs generation time everywhere, not just where it spawns.