BELOW THE IRON Modding guide

Creatures#

This page shows how to change what a creature is and how it defends itself, using two files: the profile in creatures.json and the rules in creature-rules.json.

Worked example: the golem#

A profile is the entry of one creature family in creatures.json. A definition is a named bundle of modifiers and listeners in creature-rules.json. A profile points to definitions by name. The shipped golem shows the whole chain.

Goal: make the golem a heavy, armoured creature that never dodges.

First the profile, from creatures.json. Only the fields that matter here are shown, so this snippet is partial.

{
  "golem": {
    "trait": "armored",
    "armorWeight": "heavy",
    "dodge": 0,
    "ruleIds": ["armored-hide"]
  }
}

Then the definition, in creature-rules.json. Again this is an extract, not a whole file.

{
  "definitions": {
    "armored-hide": {
      "modifiers": [{ "hook": "defense.armorClass", "op": "add", "value": 0 }]
    }
  },
  "traits": { "armored": ["armored-hide"] }
}

The modifier on its own is a normal modifier, so you can check it like any other:

[
  { "hook": "defense.armorClass", "op": "add", "value": 0 }
]
In game

Every attack against the golem is checked against its armour class and its dodge. The value 0 changes nothing, so armored-hide is a template: replace the 0 with 2 and golems become harder to hit.

Which file holds what#

FileSectionWhat it holds
creatures.jsonone entry per familyGlyph, health and attack numbers, trait, ability, dodge, armorWeight, ruleIds.
creature-rules.jsondefinitionsNamed bundles of modifiers and listeners, and nothing else.
creature-rules.jsontraits, abilities, statesLists of definition names, looked up by trait, ability kind or state.
creature-rules.jsonelites, variants, variantMagicResistancesElite affixes, variant names and their magic resistance.
creature-rules.jsonhazards, monsterGrowthWhat creatures leave on the floor, and how health and damage grow with level.

Any other top-level key in creature-rules.json is refused when the game loads.

Traits#

A trait is a label on a profile ("trait": "armored"). The traits section maps each trait to a list of definitions, so every creature with that trait gets them. Use a trait when many families share a behaviour.

Abilities#

An ability is the special power of a family, such as fury or shockwave. The abilities section maps an ability kind to definitions. The shipped fury entry points to fury-recovery: a creature that just used its power is easier to hit.

[{ "hook": "defense.armorClass", "op": "add", "value": -2, "when": { "selfRecovering": true } }]
In game

While the creature is recovering from its power, its armour class drops by 2. The recovery lasts one turn.

States#

A state is a temporary condition of the creature. The states section accepts four keys: lit, slowed, bound and poisoned. A definition under a state is active only while the creature is in it. Being lit or poisoned gives no defence bonus by itself: only the definitions you write do.

{
  "definitions": {
    "lit-glare": {
      "modifiers": [{ "hook": "defense.armorClass", "op": "add", "value": 1 }]
    }
  },
  "states": { "lit": ["lit-glare"] }
}

This extract is for reading: the game checks the states key when it loads. The key bound covers both frozen and entangled creatures.

Elites#

An elite is a creature that rolled extra affixes when its floor was generated. The elites section sets how many it gets (count, 0 to 10), the odds that a creature is elite (chance) and the pool of affixes.

Goal: elites get two affixes from this pool, with a 3% chance per floor of depth, capped at 25%.

{ "count": 2, "chance": { "base": 0, "perDepth": 0.03, "max": 0.25 }, "affixes": { "elite-evasive": { "name": "CREATURE_AFFIX_EVASIVE", "value": { "min": 2, "max": 4 } }, "elite-hardened": { "name": "CREATURE_AFFIX_HARDENED", "stackable": true } } }
In game

A creature is elite with a chance of base plus perDepth for each floor below the first, never above max. elite-evasive rolls a value from 2 to 4 once and keeps it in the save. elite-hardened can be rolled twice, and each copy counts.

The definition behind elite-evasive reads the rolled number with $value:

[{ "hook": "defense.dodge", "op": "add", "value": "$value" }]

Variants and magic resistance#

A variant is a named version of a family, such as frost or runic. variants lists the names. variantMagicResistances gives a variant a fraction from 0 to 1 of spell damage it absorbs. A variant not listed there has no resistance.

Goal: make cursed creatures resist a quarter of spell damage, on top of the shipped values.

{
  "creatureRules": {
    "variantMagicResistances": { "ash": 0.5, "frost": 0.5, "runic": 0.5, "cursed": 0.25 }
  }
}
In game

A cursed creature takes 25% less damage from spells. The key must be a name from variants, or the game refuses to start.

The snippet shows only the part of creature-rules.json to change; the other sections stay as they are.

Another example: skeletons that dodge arrows#

Goal: skeletons dodge arrows better, and nothing else.

{ "hook": "defense.dodge", "op": "add", "value": 20, "when": { "kind": "ranged", "resource": "arrows" } }

Put this modifier in a new definition, skeleton-arrow-dodge, then add its name to the ruleIds of the skeleton profile. A creature is its own subject, so you do not need to filter by family.

In game

Arrows the player shoots at a skeleton can be dodged. Bolts, melee and spells are not affected. The 20 points then go through the armour weight and the dodge cap, so a light skeleton dodges 20% of arrows and a medium one 10%.

Hearing and terrain#

Each profile has a hearingRadius. How far a noise carries is that radius times the noise intensity, capped at 12 in the shipped settings. The distance in a straight line must be within that reach.

Terrain then reduces the reach. Each cell between the noise and the listener multiplies it by its dampensSound factor divided by 100. A wall gives 0.05, a closed door 0.40, so two closed doors give 0.16. A cell without that component lets everything through, and opening a door restores it. See Terrain.

Advanced: activation order

A creature's rules are collected in this order: the profile's ruleIds, then its trait, then its ability kind, then its states in the order of the states keys. Elite affixes come last, in the order they were rolled. A definition reached by several paths is granted only once, at its first occurrence.

Advanced: cooldowns and memory per creature

A listener's cooldowns and stored values belong to one creature. Two creatures with the same rule never share them. Losing a trait does not reset the counters of a definition the creature still has, and entering a floor resets only that creature's per-floor cooldowns.

Advanced: opening doors

The optional opensDoors field, true or false, lets a family open ordinary doors. Opening uses the creature's whole action and makes the normal door noise.

What the validator refuses#

The game stops at the first error and names the file and the key. The usual causes:

The exact messages and fixes are in Troubleshooting.