DND Engine Registry

dungeon

Registers under Dungeons · source: modules/dungeon/v1

`nodes` is a list of already-shaped DungeonNode dicts ({"id", "type", "data"}) — this first-pass module doesn't add guided per-node-type defaults beyond registering the whole graph as one record; a later version could offer node-builder helpers the way weapon() offers tier resolution. The FIRST entry in `nodes` is where a run starts (see bot/engine_bridge.py's `dungeon["nodes"][0]["id"]`) — no separate `isStart` flag needed here the way the one-node-per- file `.resource` convention (modules/dungeon_node/v1) needs one, since this module's own node order is never ambiguous the way file discovery order across several separate files would be. `name`/`difficulty`/`image_id` are pure display/selection data, same "never read by the engine" spirit as an item's `icon_id` — added 2026-10-14 alongside this module finally getting a COMMUNITY_SCHEMA, for a `.resource`-authored dungeon's own outer block to carry something real beyond just `id`/`description`. `image_id` picks this dungeon's own cover art from assets/dungeons/<image_id>.png — no UI currently reads it (no "pick a dungeon" art surface exists yet, the same "accepted now, rendered later" situation several dungeon NODE fields were already in) — accepted anyway so the content model doesn't need a second migration once that surface exists.

Example Usage

This real example file builds more than one record — dungeon is one of several blocks (or a nested sub-block) in it, shown together because they reference each other.

resource "goblin_watch" {
  source: modules/dungeon/v1
  id: "goblin_watch"
  name: "Goblin Watch"
  description: "A sentry outpost goblins built out of salvaged watchtower stone, strike fast before their numbers call for backup."
  difficulty: 1.5
  image_id: "goblin_watch"

  combat_node {
    id: "watch_entry"
    enemy_group_id: catalog.goblin_pack
    next_node_id: "watch_fork"
    description: "Goblins posted at the gate scramble to meet you."
  }

  choice_node {
    id: "watch_fork"
    description: "Two paths lead deeper - a straight charge, or a careful search for weak stonework."
    fork{
      label: "Push straight through the courtyard."
      next_node_id: "watch_ambush"
    }
    fork{
    label: "Search for a weak point in the wall."
    stat_check {
      stat_check_stat_id: "luck"
      stat_check_difficulty: 3
    }
    on_pass {
      next_node_id: "watch_treasure"
    }
    on_fail {
      next_node_id: "watch_ambush"
    }
    }
  }

  combat_node {
    id: "watch_ambush"
    enemy_group_id: catalog.goblin_ambush
    next_node_id: "watch_treasure"
    description: "The goblins you passed catch up from behind."
  }

  reward_node {
    id: "watch_treasure"
    next_node_id: "watch_end"
    gold_given: 15
    essence_given_type_id: catalog.mana
    essence_given_amount: 5
    stat_exp_stat_id: "luck"
    stat_exp_amount: 2
  }

  terminal_node {
    id: "watch_end"
  }
}

Arguments Reference

The following arguments are supported:

  • id - (Required) The unique id this record is registered and referenced under — what a `.resource` file's own filename-independent identity is, and what any other content's foreign-key-style field (a dungeon node's `next`, a skill node's `parent_id`, and so on) points at. community
  • nodes - (Required) `nodes` is a list of already-shaped DungeonNode dicts ({"id", "type", "data"}) — this first-pass module doesn't add guided per-node-type defaults beyond registering the whole graph as one record; a later version could offer node-builder helpers the way weapon() offers tier resolution. developer
  • name - (Optional) `name`/`difficulty`/`image_id` are pure display/selection data, same "never read by the engine" spirit as an item's `icon_id` — added 2026-10-14 alongside this module finally getting a COMMUNITY_SCHEMA, for a `.resource`-authored dungeon's own outer block to carry something real beyond just `id`/`description`. community
  • description - (Optional) Flavor text shown to players — pure display data with no effect on game mechanics. community
  • difficulty - (Optional) Defaults to 0.0. community
  • image_id - (Optional) `image_id` picks this dungeon's own cover art from assets/dungeons/<image_id>.png — no UI currently reads it (no "pick a dungeon" art surface exists yet, the same "accepted now, rendered later" situation several dungeon NODE fields were already in) — accepted anyway so the content model doesn't need a second migration once that surface exists. community

Nested Blocks

These can't be set as flat key: value fields — write them as their own block_type { ... } inside this module's block instead (see the Example Usage above).

combat_node

A Combat dungeon node — see modules/dungeon_node/v1's own docstring for why nodes are authored one-per-file. `enemy_group_id` references an existing EnemyEncounter id; a community author picks among existing encounters, same "categorical, not a new balance number" spirit as a weapon's damage_types referencing existing DamageType ids. `dungeon_id` is the shared id every node of the same dungeon sets — dungeon_assembly.py groups by it after all node files are discovered, regardless of which one authored it. `next_node_id` is the id of the node the run continues to once this fight resolves — named with the `_node_id` suffix (not just `next`), matching reward_node's own next-node field and choice_node's fork `next_node_id`/`next_node_on_pass`/`next_node_on_fail` (2026-10-14 rename, for a consistent "every node-to-node link ends in `_node_id`" convention across all four node types). The record's own internal `data["next"]` key is unchanged — engine.dungeon.py and bot/engine_bridge.py read that key regardless of which authoring path produced the node, so this rename is purely the community-facing argument name. `image_id` picks this node's background art from assets/dungeon_nodes/<image_id>.png (see bot/dungeon_node_image.py) — accepted and stored here for forward-compatibility, but not yet actually rendered for Combat nodes specifically (only Choice and Terminal nodes draw their own imageId today, see bot/dungeon_run_view.py).

  • id - (Required) The unique id this record is registered and referenced under — what a `.resource` file's own filename-independent identity is, and what any other content's foreign-key-style field (a dungeon node's `next`, a skill node's `parent_id`, and so on) points at. community
  • enemy_group_id - (Required) `enemy_group_id` references an existing EnemyEncounter id; a community author picks among existing encounters, same "categorical, not a new balance number" spirit as a weapon's damage_types referencing existing DamageType ids. Currently registered: goblin_ambush, goblin_chieftain, goblin_den_pack, goblin_pack and voidmaw_wyrm. community
  • next_node_id - (Required) `next_node_id` is the id of the node the run continues to once this fight resolves — named with the `_node_id` suffix (not just `next`), matching reward_node's own next-node field and choice_node's fork `next_node_id`/`next_node_on_pass`/`next_node_on_fail` (2026-10-14 rename, for a consistent "every node-to-node link ends in `_node_id`" convention across all four node types). community
  • is_boss - (Optional) Defaults to False. community
  • description - (Optional) Flavor text shown to players — pure display data with no effect on game mechanics. community
  • image_id - (Optional) `image_id` picks this node's background art from assets/dungeon_nodes/<image_id>.png (see bot/dungeon_node_image.py) — accepted and stored here for forward-compatibility, but not yet actually rendered for Combat nodes specifically (only Choice and Terminal nodes draw their own imageId today, see bot/dungeon_run_view.py). community

choice_node

A Choice dungeon node — see modules/dungeon_node/v1's own docstring. Standalone (one-node-per-file) authoring gets exactly TWO options, each in its own flat "slot" (option_a_*/option_b_*) rather than a generic options list — a flat file has no list-of-maps syntax, so this is the widest shape that still fits it directly. A choice needing 3+ options, written this way, stays a trusted-Python-authored dungeon (modules/dungeon/v1's own dungeon(), calling choice_node(..., options=[...]) directly — see `options` below). 2026-10-14: gamemaster/builder/resource_parser.py's nested-sub-block support means a choice_node NESTED inside a dungeon block isn't limited to two anymore either — each option is its own `fork { }` sub-block (repeat as many as needed), optionally containing one nested `stat_check { }` sub-block — resource_parser.py collects those into an `options` list and passes it here directly, bypassing the option_a_*/ option_b_* flat slots entirely. Both conventions call this exact same factory; `options`, when given, always wins over the flat slots (not merged with them). Each option is either a plain path or a stat-check fork — exactly engine.dungeon.resolve_choice's own two option shapes, see build_option below (the single source of truth for one option's shape, used by BOTH authoring conventions so neither can drift out of sync with the other). `next_node_id` is the id of the node this option leads to directly, for a plain-path option (left empty for a stat-check option). `stat_check_stat_id` is the stat this option's check rolls against, for a stat-check option (`next_node_id` left empty instead). `stat_check_difficulty` is that check's difficulty number. `label` is the player-facing button text for that option (e.g. "Take the open path"). `next_node_on_pass` is the id of the node to continue to if the stat check succeeds. `next_node_on_fail` is the id of the node to continue to if it fails. `dungeon_id` is the shared id every node of the same dungeon sets — dungeon_assembly.py groups by it after all node files are discovered, regardless of which one authored it. `image_id` picks this node's background art from assets/dungeon_nodes/<image_id>.png (see bot/dungeon_node_image.py).

  • id - (Required) The unique id this record is registered and referenced under — what a `.resource` file's own filename-independent identity is, and what any other content's foreign-key-style field (a dungeon node's `next`, a skill node's `parent_id`, and so on) points at. community
  • description - (Optional) Flavor text shown to players — pure display data with no effect on game mechanics. community
  • image_id - (Optional) `image_id` picks this node's background art from assets/dungeon_nodes/<image_id>.png (see bot/dungeon_node_image.py). community

fork

One option in a choice_node's own list — write one `fork { }` per option (2 or more; unlike choice_node's own flat option_a_*/option_b_* arguments, not capped at exactly two). A plain-path fork sets `next_node_id` directly; a stat-check fork instead nests `stat_check { }` plus `on_pass { }`/`on_fail { }`, and leaves `next_node_id` unset.

  • label - (Required) The player-facing button text for this option. community
  • next_node_id - (Optional) The id of the node this option leads to directly — for a plain-path fork. Left unset for a stat-check fork, which uses on_pass/on_fail instead. community

stat_check

Makes this fork a stat check instead of a plain path — present together with on_pass { } and on_fail { }, never alongside next_node_id.

  • stat_check_stat_id - (Required) The stat this fork's check rolls against. Valid options are strength, agility, evasiveness, luck, vitality, wisdom and charisma. community
  • stat_check_difficulty - (Required) That check's difficulty number. community

on_pass

Where this fork continues to if its stat_check succeeds.

  • next_node_id - (Required) The id of the node to continue to. community

on_fail

Where this fork continues to if its stat_check fails.

  • next_node_id - (Required) The id of the node to continue to. community

reward_node

A Reward dungeon node — see modules/dungeon_node/v1's own docstring. Grants at most ONE stat-EXP amount and ONE essence amount per node (plus gold) rather than the arbitrary-sized dicts modules/dungeon/v1's Python path allows — a flat file has no map syntax, so multiple grants on one node stays a trusted-Python-only shape; author several reward nodes in a row for more than one grant. `dungeon_id` is the shared id every node of the same dungeon sets — dungeon_assembly.py groups by it after all node files are discovered, regardless of which one authored it. `next_node_id` is the id of the node the run continues to once this reward resolves — renamed from `next` 2026-10-14 for consistency with combat_node's own `next_node_id` and choice_node's fork `next_node_id`/`next_node_on_pass`/ `next_node_on_fail`; the record's own internal `data["next"]` key is unchanged, only the community-facing argument name moved. `image_id` picks this node's background art from assets/dungeon_nodes/<image_id>.png (see bot/dungeon_node_image.py) — accepted and stored here for forward- compatibility, but not yet actually rendered for Reward nodes specifically (only Choice and Terminal nodes draw their own imageId today, see bot/dungeon_run_view.py; a Reward node currently resolves silently mid-walk with no pause screen at all).

  • id - (Required) The unique id this record is registered and referenced under — what a `.resource` file's own filename-independent identity is, and what any other content's foreign-key-style field (a dungeon node's `next`, a skill node's `parent_id`, and so on) points at. community
  • next_node_id - (Required) `next_node_id` is the id of the node the run continues to once this reward resolves — renamed from `next` 2026-10-14 for consistency with combat_node's own `next_node_id` and choice_node's fork `next_node_id`/`next_node_on_pass`/ `next_node_on_fail`; the record's own internal `data["next"]` key is unchanged, only the community-facing argument name moved. community
  • stat_exp_stat_id - (Optional) Currently registered: strength, agility, evasiveness, luck, vitality, wisdom and charisma. community
  • stat_exp_amount - (Optional) Defaults to 0.0. community
  • essence_given_type_id - (Optional) Currently registered: ammo, mana and stamina. community
  • essence_given_amount - (Optional) Defaults to 0.0. community
  • gold_given - (Optional) Defaults to 0.0. community
  • image_id - (Optional) `image_id` picks this node's background art from assets/dungeon_nodes/<image_id>.png (see bot/dungeon_node_image.py) — accepted and stored here for forward- compatibility, but not yet actually rendered for Reward nodes specifically (only Choice and Terminal nodes draw their own imageId today, see bot/dungeon_run_view.py; a Reward node currently resolves silently mid-walk with no pause screen at all). community

terminal_node

A Terminal dungeon node — ends the run. See modules/dungeon_node/v1's own docstring. `dungeon_id` is the shared id every node of the same dungeon sets — dungeon_assembly.py groups by it after all node files are discovered, regardless of which one authored it.

  • id - (Required) The unique id this record is registered and referenced under — what a `.resource` file's own filename-independent identity is, and what any other content's foreign-key-style field (a dungeon node's `next`, a skill node's `parent_id`, and so on) points at. community