DND Engine Registry

weapon

Registers under Weapons · source: modules/weapon/v1

Builds and registers a weapon record under the "weapon" category. `icon_id` is the filename stem of this item's art in assets/items/ (`icon_id="rusty_sword"` -> assets/items/rusty_sword.png) — pure display data like `description`, never read by the engine; empty (or no such file) renders the category's placeholder. See bot/item_icons.py. `name` is the player-facing display label (e.g. "Rusty Sword" for `id="rusty_sword"`) — every place a player sees this weapon (Shop, inventory, character sheet) shows `name`, never the raw `id`; `id` stays the thing typed into a command's arguments. `damage_types` is at most two ids (base + optional hybrid — see combat-engine-readme.md → Typed Resistance's weapon cap; adding the second one post-acquisition goes through the Artificer, not this module). `hitdie` is a Content-authored {"diceCount": n, "dieSize": n} pair. `tier` is resolved into a concrete `baseDamage` multiplier here, at build time — the engine never sees `tier`/`Tier.LEGENDARY`, only the flattened number. `can_be_in_shop`/`shop_price` feed the Shop's `CanBeInShop: true` pool (see combat-engine-readme.md → Gear → Locations & Economy) — rotation cadence/pool size are explicitly TBD, so this module just tags eligibility and a price, nothing more. `base_damage` now defaults to a flat placeholder (6.0) rather than being required — a real per-weapon-category baseline is just as undecided as everything else `Tier`/`TIER_MODIFIER` stand in for (see content-open-questions.md → Gear), and this specific default also has to exist for `COMMUNITY_SCHEMA` below to actually be callable: `base_damage` isn't a community-settable field (see toolchain-architecture.md → Community Content Authoring — it's a raw balance number, exactly what that section says a community author should never touch directly), so every required-with-no-default parameter of this factory MUST either be in COMMUNITY_SCHEMA or have a default — otherwise a `.resource` file could never successfully call this module at all. See gamemaster/builder/resource_parser.py's own build-time check for this exact invariant. `damage_multiplier`, when given, replaces the flat `TIER_MODIFIER[tier]` lookup entirely — this is what gives two same-tier weapons different real damage instead of an identical fixed number. It's deliberately a *separate* field from `base_damage`: `base_damage` stays closed (still the raw balance input), while `damage_multiplier` is the one new field a `.resource` submission is actually allowed to carry, and in practice it's the registry_site Pipeline that's expected to set it — rolled once from content/tuning.py's weaponTierDamageMultiplierRange for the weapon's own `tier`, at Plan time, with the rolled literal baked straight into the saved file (see registry_site/pipeline.py). Nothing stops a human from hand-typing this field too (COMMUNITY_SCHEMA can't tell who wrote a line), so `VALID_DAMAGE_MULTIPLIER_RANGE` clamps it to a generous sanity rail regardless of source — real per-tier balance stays the Pipeline's table's job, not this module's. `shop_price` is the exact same shape as `damage_multiplier` above: schema-allowed so a Pipeline-written value survives a rebuild, clamped to `VALID_SHOP_PRICE_RANGE` as a backstop, and in practice rolled by the Pipeline (content/tuning.py's shopPriceRangeByTier, keyed by `tier`, only once `can_be_in_shop` is set) rather than hand-typed. Left at its 0.0 default (can_be_in_shop off, or a Pipeline submission the Pipeline didn't roll a price for), the live shopPriceByTier fallback in bot/item_pricing.py still applies — that fallback is for trusted-Python content that never goes through the Pipeline at all, unaffected by any of this. `tags`: free-text labels (e.g. "pixel art") for the registry-site Catalog's filter (DND-30, 2026-10-20) — pure display/browse metadata, same "never read by the engine" status as `description`. No fixed vocabulary; the Catalog just surfaces whatever's actually in use.

Example Usage

resource "apprentice_wand" {
  source: modules/weapon/v1
  id: "apprentice_wand"
  name: "Apprentice Wand"
  weapon_category: "magic"
  tier: "common"
  damage_types: ["fire"]
  can_be_in_shop: true
  description: "A knobby length of ash, warm to the touch. Every Wizard starts with one."
  icon_id: "apprentice_wand"
  tags: ["pixel art"]
}

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
  • name - (Required) `name` is the player-facing display label (e.g. "Rusty Sword" for `id="rusty_sword"`) — every place a player sees this weapon (Shop, inventory, character sheet) shows `name`, never the raw `id`; `id` stays the thing typed into a command's arguments. community
  • weapon_category - (Required) Currently registered: magic, melee and ranged. community
  • base_damage - (Optional) `base_damage` now defaults to a flat placeholder (6.0) rather than being required — a real per-weapon-category baseline is just as undecided as everything else `Tier`/`TIER_MODIFIER` stand in for (see content-open-questions.md → Gear), and this specific default also has to exist for `COMMUNITY_SCHEMA` below to actually be callable: `base_damage` isn't a community-settable field (see toolchain-architecture.md → Community Content Authoring — it's a raw balance number, exactly what that section says a community author should never touch directly), so every required-with-no-default parameter of this factory MUST either be in COMMUNITY_SCHEMA or have a default — otherwise a `.resource` file could never successfully call this module at all. Defaults to 6.0. developer
  • tier - (Optional) `tier` is resolved into a concrete `baseDamage` multiplier here, at build time — the engine never sees `tier`/`Tier.LEGENDARY`, only the flattened number. Valid options are common, rare and legendary. Defaults to 'common'. community
  • damage_multiplier - (Optional) `damage_multiplier`, when given, replaces the flat `TIER_MODIFIER[tier]` lookup entirely — this is what gives two same-tier weapons different real damage instead of an identical fixed number. Defaults to None. community
  • slot - (Optional) Defaults to 'mainhand'. developer
  • damage_types - (Optional) `damage_types` is at most two ids (base + optional hybrid — see combat-engine-readme.md → Typed Resistance's weapon cap; adding the second one post-acquisition goes through the Artificer, not this module). Defaults to None. community
  • hitdie - (Optional) `hitdie` is a Content-authored {"diceCount": n, "dieSize": n} pair. Defaults to None. developer
  • essence_cost - (Optional) Defaults to 'light'. developer
  • can_be_in_shop - (Optional) `can_be_in_shop`/`shop_price` feed the Shop's `CanBeInShop: true` pool (see combat-engine-readme.md → Gear → Locations & Economy) — rotation cadence/pool size are explicitly TBD, so this module just tags eligibility and a price, nothing more. Defaults to False. community
  • shop_price - (Optional) `shop_price` is the exact same shape as `damage_multiplier` above: schema-allowed so a Pipeline-written value survives a rebuild, clamped to `VALID_SHOP_PRICE_RANGE` as a backstop, and in practice rolled by the Pipeline (content/tuning.py's shopPriceRangeByTier, keyed by `tier`, only once `can_be_in_shop` is set) rather than hand-typed. Defaults to 0.0. community
  • description - (Optional) Flavor text shown to players — pure display data with no effect on game mechanics. community
  • icon_id - (Optional) `icon_id` is the filename stem of this item's art in assets/items/ (`icon_id="rusty_sword"` -> assets/items/rusty_sword.png) — pure display data like `description`, never read by the engine; empty (or no such file) renders the category's placeholder. community
  • tags - (Optional) `tags`: free-text labels (e.g. "pixel art") for the registry-site Catalog's filter (DND-30, 2026-10-20) — pure display/browse metadata, same "never read by the engine" status as `description`. Defaults to None. community