Skip to content

osrlib.core.items

Equipment, inventories, magic items, identification, and curses.

The equipment catalog — weapons, armour, gear, and ammunition — compiles from the OSE SRD's equipment pages and loads as frozen templates via load_equipment. The magic item catalog — enchanted arms, potions, scrolls, rings, rods, staves, wands, and sentient swords — compiles the same way and loads via load_magic_items. Play never mutates either catalog: it spawns owned instances from the frozen templates — ItemInstance for mundane equipment, MagicItemInstance for magic items — and carries them in an Inventory.

A magic item instance starts unidentified, and even once identified may still hide a curse: a revealed cursed item pins to its bearer until remove curse. equip and unequip, and their validate_* counterparts, enforce what a class may wear or wield: armour and weapon policies, a two-ring cap, and the conflict between a two-handed weapon and a shield.

Torch, holy water, and burning oil appear on both the SRD's weapon table and its gear list. osrlib adopts the reading that each is one physical item, not two: they compile as gear carrying an embedded combat facet, the weapons list holds the 19 pure weapons, and no item has two ids. Class weapon policies govern the weapons list only — gear combat facets are exempt, so a cleric may use holy water and a magic-user may throw oil or swing a torch, as a documented adaptation (see the adaptations register).

All weights are in coins (ten coins to the pound); coins themselves weigh 1 each. The maximum load rule always applies, not only under detailed encumbrance: tracked weight above 1,600 coins means the character cannot move, under both tracking modes. Inventory itself is never capped.

AnyInstance module-attribute

AnyInstance = Annotated[ItemInstance | MagicItemInstance, Field(discriminator='instance_type')]

Any owned instance — mundane or magic — discriminated by instance_type.

BASE_MOVEMENT_FEET module-attribute

BASE_MOVEMENT_FEET = 120

The default movement rate, feet per exploration turn: 120' (40').

COIN_VALUES_CP module-attribute

COIN_VALUES_CP = {'pp': 500, 'gp': 100, 'ep': 50, 'sp': 10, 'cp': 1}

Coin values in copper pieces, from the SRD's Wealth conversion table.

ItemTemplate module-attribute

ItemTemplate = Annotated[
    WeaponTemplate | ArmourTemplate | GearTemplate | AmmunitionTemplate, Field(discriminator="item_type")
]

Any equipment template, discriminated by item_type.

MAX_LOAD_COINS module-attribute

MAX_LOAD_COINS = 1600

The maximum load any character can carry; above it, movement is 0.

MAX_RINGS_WORN module-attribute

MAX_RINGS_WORN = 2

RAW's ring cap: one on each hand — a third is rejected (more than two = none function).

MISC_GEAR_WEIGHT_COINS module-attribute

MISC_GEAR_WEIGHT_COINS = 80

Detailed encumbrance's flat weight for carrying any miscellaneous gear.

AmmunitionTemplate

Bases: BaseModel

An ammunition row.

Ammunition weight is always 0: the SRD's missile weapon weights already include the ammunition and its container, and the ammunition table has no weight column. Sling stones' printed cost of Free compiles to cost 0 with a purchase lot size of 1.

item_type class-attribute instance-attribute

item_type: Literal['ammunition'] = 'ammunition'

id instance-attribute

id: str

name instance-attribute

name: str

cost_gp class-attribute instance-attribute

cost_gp: int = Field(ge=0)

lot_size class-attribute instance-attribute

lot_size: int = Field(default=1, ge=1)

weight_coins class-attribute instance-attribute

weight_coins: int = Field(default=0, ge=0)

material class-attribute instance-attribute

overrides_applied class-attribute instance-attribute

overrides_applied: tuple[str, ...] = ()

ArmourCategory

Bases: StrEnum

Basic-encumbrance armour categories; unarmoured is the absence of worn armour.

LIGHT class-attribute instance-attribute

LIGHT = 'light'

HEAVY class-attribute instance-attribute

HEAVY = 'heavy'

ArmourTemplate

Bases: BaseModel

An armour row: body armour with dual-format AC, or the shield with its bonus.

item_type class-attribute instance-attribute

item_type: Literal['armour'] = 'armour'

id instance-attribute

id: str

name instance-attribute

name: str

cost_gp class-attribute instance-attribute

cost_gp: int = Field(ge=0)

weight_coins class-attribute instance-attribute

weight_coins: int = Field(ge=0)

ac class-attribute instance-attribute

ac: int | None = None

ac_ascending class-attribute instance-attribute

ac_ascending: int | None = None

ac_bonus class-attribute instance-attribute

ac_bonus: int | None = None

category class-attribute instance-attribute

category: ArmourCategory | None = None

overrides_applied class-attribute instance-attribute

overrides_applied: tuple[str, ...] = ()

is_shield property

is_shield: bool

Whether this row is the shield (an AC bonus rather than a base AC).

ArmourTypeRow

Bases: BaseModel

One d8 band of the Magic Armour Type table.

roll_min class-attribute instance-attribute

roll_min: int = Field(ge=1, le=8)

roll_max class-attribute instance-attribute

roll_max: int = Field(ge=1, le=8)

base_item_id instance-attribute

base_item_id: str

CoinPurse

Bases: BaseModel

Coins by denomination. Each coin weighs 1, whatever its metal.

Payment consumes denominations smallest-first (cp, sp, ep, gp, pp) until the cost is covered; change for any overpayment returns in the fewest coins, largest denominations first. Purse contents after a purchase are always deterministic and value-preserving.

pp class-attribute instance-attribute

pp: int = Field(default=0, ge=0)

gp class-attribute instance-attribute

gp: int = Field(default=0, ge=0)

ep class-attribute instance-attribute

ep: int = Field(default=0, ge=0)

sp class-attribute instance-attribute

sp: int = Field(default=0, ge=0)

cp class-attribute instance-attribute

cp: int = Field(default=0, ge=0)

value_cp property

value_cp: int

The purse's total value in copper pieces.

total_coins property

total_coins: int

How many coins the purse holds — its weight in coins.

can_afford

can_afford(cost_gp: int) -> bool

Whether the purse's total value covers a cost in gold pieces.

Parameters:

Name Type Description Default
cost_gp int

The cost in whole gold pieces. Non-negative.

required

Returns:

Type Description
bool

True if the purse's value in cp is at least the cost's.

Raises:

Type Description
ValueError

If cost_gp is negative.

spend

spend(cost_gp: int) -> None

Pay a cost in gold pieces, spending smallest denominations first and making change largest-first.

Parameters:

Name Type Description Default
cost_gp int

The cost in whole gold pieces. Non-negative.

required

Raises:

Type Description
ValueError

If cost_gp is negative or the purse cannot cover it — validate with can_afford first; overspending is programmer misuse.

Coins

Bases: BaseModel

A frozen coin bundle: generated treasure, cache contents, dropped piles.

pp class-attribute instance-attribute

pp: int = Field(default=0, ge=0)

gp class-attribute instance-attribute

gp: int = Field(default=0, ge=0)

ep class-attribute instance-attribute

ep: int = Field(default=0, ge=0)

sp class-attribute instance-attribute

sp: int = Field(default=0, ge=0)

cp class-attribute instance-attribute

cp: int = Field(default=0, ge=0)

total_coins property

total_coins: int

How many coins the bundle holds.

value_cp property

value_cp: int

The bundle's total value in copper pieces — the award math's exact unit.

value_gp property

value_gp: int

The bundle's value in whole gold pieces, floored — the 1-gp-=-1-XP input.

CombatFacet

Bases: BaseModel

The combat statistics embedded in a gear item (torch, holy water, burning oil).

damage instance-attribute

damage: str

qualities instance-attribute

qualities: tuple[WeaponQuality, ...]

missile_ranges class-attribute instance-attribute

missile_ranges: MissileRanges | None = None

EquipmentCatalog

Bases: BaseModel

The loaded equipment lists, with id lookup across all four.

weapons instance-attribute

weapons: tuple[WeaponTemplate, ...]

armour instance-attribute

armour: tuple[ArmourTemplate, ...]

gear instance-attribute

gear: tuple[GearTemplate, ...]

ammunition instance-attribute

ammunition: tuple[AmmunitionTemplate, ...]

treasure_weights instance-attribute

treasure_weights: tuple[TreasureWeight, ...]

get

Return the template with item_id from any of the four lists.

Parameters:

Name Type Description Default
item_id str

An equipment id from load_equipment — see the equipment id index, e.g. "sword" or "torch".

required

Returns:

Type Description
WeaponTemplate | ArmourTemplate | GearTemplate | AmmunitionTemplate

The template.

Raises:

Type Description
ValueError

If no item has that id.

GearTemplate

Bases: BaseModel

An adventuring gear item.

lot_size sizes the purchase lot bought at cost_gp — see purchase for exactly what a lot buys. capacity_coins is container capacity where the SRD gives one (backpack, sacks); combat is the embedded combat facet for the three dual-listed items. params carries structured exploration mechanics from the SRD's gear table (a torch's burn_turns and light_radius_feet, the tinder box's light_chance_in_six), consumed by the crawl procedures.

item_type class-attribute instance-attribute

item_type: Literal['gear'] = 'gear'

id instance-attribute

id: str

name instance-attribute

name: str

cost_gp class-attribute instance-attribute

cost_gp: int = Field(ge=0)

lot_size class-attribute instance-attribute

lot_size: int = Field(default=1, ge=1)

capacity_coins class-attribute instance-attribute

capacity_coins: int | None = None

combat class-attribute instance-attribute

combat: CombatFacet | None = None

params class-attribute instance-attribute

params: dict[str, int | str | bool] = {}

overrides_applied class-attribute instance-attribute

overrides_applied: tuple[str, ...] = ()

GeneratedTreasure

Bases: BaseModel

One generation's output: coins, valuables, and magic item instances.

coins class-attribute instance-attribute

coins: Coins = Coins()

valuables class-attribute instance-attribute

valuables: tuple[ValuableInstance, ...] = ()

magic_items class-attribute instance-attribute

magic_items: tuple[MagicItemInstance, ...] = ()

Inventory

Bases: BaseModel

A character's carried items, coins, valuables, and equipped state.

The item list (items) is ordered (a defined order everywhere, per the determinism contract). Equipping moves an instance out of items into its slot, so each instance lives in exactly one place. Carried coins are the purse (CoinPurse); magic items join the item list and the equipped slots as union members; rings are the two worn-ring slots (RAW: one on each hand — the cap is enforced at equip validation); valuables are carried gems and jewellery.

items class-attribute instance-attribute

items: list[AnyInstance] = []

purse class-attribute instance-attribute

purse: CoinPurse = CoinPurse()

valuables class-attribute instance-attribute

valuables: list[ValuableInstance] = []

worn_armour class-attribute instance-attribute

worn_armour: AnyInstance | None = None

shield class-attribute instance-attribute

shield: AnyInstance | None = None

wielded class-attribute instance-attribute

wielded: list[AnyInstance] = []

rings class-attribute instance-attribute

rings: list[MagicItemInstance] = []

all_instances

all_instances() -> list[ItemInstance | MagicItemInstance]

Return every carried instance — the item list plus the equipped slots.

equipped_instances

equipped_instances() -> list[ItemInstance | MagicItemInstance]

Return every equipped instance, slots first then wielded then rings.

magic_item

magic_item(instance_id: str) -> MagicItemInstance | None

Return the carried magic item with instance_id, or None.

Parameters:

Name Type Description Default
instance_id str

The instance id, e.g. "magic-item-0003".

required

Returns:

Type Description
MagicItemInstance | None

The instance, wherever it is carried or equipped.

ItemInstance

Bases: BaseModel

A mutable owned item spawned from a frozen template.

quantity counts individual units: buying one lot of torches yields one instance with quantity 6.

instance_type class-attribute instance-attribute

instance_type: Literal['item'] = 'item'

template instance-attribute

template: ItemTemplate

quantity class-attribute instance-attribute

quantity: int = Field(default=1, ge=1)

MagicArmourTypeTable

Bases: BaseModel

The Magic Armour Type d8 table: what a generated Armour +N is made of.

rows class-attribute instance-attribute

rows: tuple[ArmourTypeRow, ...] = Field(min_length=1)

base_for_roll

base_for_roll(roll: int) -> str

Return the base armour id a d8 roll selects.

Parameters:

Name Type Description Default
roll int

The d8 result, 1–8.

required

Returns:

Type Description
str

The mundane armour template id.

Raises:

Type Description
ValueError

If the roll is outside 1–8.

MagicItemCatalog

Bases: BaseModel

The loaded magic item catalog: templates, sub-tables, and the sword tables.

items instance-attribute

items: tuple[MagicItemTemplate, ...]

sub_tables instance-attribute

sub_tables: tuple[MagicSubTable, ...]

armour_type instance-attribute

armour_type: MagicArmourTypeTable

scroll_spell_levels instance-attribute

scroll_spell_levels: ScrollSpellLevelTable

sentient_swords instance-attribute

sentient_swords: SentientSwordTables

get

get(item_id: str) -> MagicItemTemplate

Return the magic item template with item_id.

Parameters:

Name Type Description Default
item_id str

A magic item id from load_magic_items — see the magic item id index, e.g. "potion_of_healing".

required

Returns:

Type Description
MagicItemTemplate

The template.

Raises:

Type Description
ValueError

If no item has that id.

sub_table

sub_table(category: MagicItemType) -> MagicSubTable

Return the generation sub-table for a master-table type.

Parameters:

Name Type Description Default
category MagicItemType

The master-table type.

required

Returns:

Type Description
MagicSubTable

The sub-table.

Raises:

Type Description
ValueError

If no sub-table covers that type.

MagicItemCategory

Bases: StrEnum

The magic item catalog's categories — the master table's types, devices split.

The master Magic Item Type table's rod_staff_wand type covers three catalog categories; every other type maps to one.

ARMOUR class-attribute instance-attribute

ARMOUR = 'armour'

MISC class-attribute instance-attribute

MISC = 'misc'

POTION class-attribute instance-attribute

POTION = 'potion'

RING class-attribute instance-attribute

RING = 'ring'

ROD class-attribute instance-attribute

ROD = 'rod'

STAFF class-attribute instance-attribute

STAFF = 'staff'

WAND class-attribute instance-attribute

WAND = 'wand'

SCROLL class-attribute instance-attribute

SCROLL = 'scroll'

SWORD class-attribute instance-attribute

SWORD = 'sword'

WEAPON class-attribute instance-attribute

WEAPON = 'weapon'

MagicItemEffect

Bases: BaseModel

The structured mechanics of a magic item the kernel automates.

kind names the kernel behavior that executes it (worn_modifiers, potion, damage_area, condition_area, healing, save_or_die, on_hit_drain, striking, ward, regeneration, light); every other item in the catalog carries manual-tagged prose instead. Fields are the union the behaviors read — dice, element, save, shape and dimensions, duration — with params carrying per-item scalars.

kind class-attribute instance-attribute

kind: str = Field(min_length=1)

modifiers class-attribute instance-attribute

modifiers: tuple[ModifierSpec, ...] = ()

condition class-attribute instance-attribute

condition: str | None = None

damage_dice class-attribute instance-attribute

damage_dice: str | None = None

heal_dice class-attribute instance-attribute

heal_dice: str | None = None

element class-attribute instance-attribute

element: str | None = None

save_category class-attribute instance-attribute

save_category: str | None = None

save_on class-attribute instance-attribute

save_on: Literal['negates', 'half'] | None = None

shape class-attribute instance-attribute

shape: str | None = None

dimensions class-attribute instance-attribute

dimensions: dict[str, int] = {}

range_feet class-attribute instance-attribute

range_feet: int | None = None

duration_unit class-attribute instance-attribute

duration_unit: str | None = None

duration_amount class-attribute instance-attribute

duration_amount: int | None = None

duration_dice class-attribute instance-attribute

duration_dice: str | None = None

params class-attribute instance-attribute

params: dict[str, int | str | bool | tuple[int | str, ...]] = {}

MagicItemInstance

Bases: BaseModel

A mutable owned magic item spawned from a frozen template.

template_id is the item id from load_magic_items — see the magic item id index. charges_remaining is referee-only (RAW: undiscoverable) and None for uncharged items; quantity counts ammunition; identified gates the player view's masking; base_item_id is the generated base for generic armour outcomes (the Magic Armour Type d8) and the template's own base otherwise; state is per-item memory (the energy-drain sword's remaining drains, the staff of healing's per-target days, a multi-spell scroll's remaining spells).

instance_type class-attribute instance-attribute

instance_type: Literal['magic_item'] = 'magic_item'

instance_id instance-attribute

instance_id: str

template_id instance-attribute

template_id: str

charges_remaining class-attribute instance-attribute

charges_remaining: int | None = None

quantity class-attribute instance-attribute

quantity: int = Field(default=1, ge=0)

identified class-attribute instance-attribute

identified: bool = False

cursed_revealed class-attribute instance-attribute

cursed_revealed: bool = False

base_item_id class-attribute instance-attribute

base_item_id: str | None = None

sentience class-attribute instance-attribute

sentience: SwordSentience | None = None

state class-attribute instance-attribute

state: dict[str, int | str | bool | tuple[int | str, ...]] = {}

MagicItemTemplate

Bases: BaseModel

A magic item, compiled from the generation tables and per-item pages.

Frozen SRD data: play spawns mutable MagicItemInstances. id is this template's own id, listed in load_magic_items's catalog — see the magic item id index. base_item_id is the mundane equipment id (from load_equipment; see the equipment id index) an enchanted arm overlays (sword, dagger, chainmail, shield, arrows); generic armour outcomes leave it None — the Magic Armour Type d8 sets the instance's base at generation. Bonuses are negative for cursed items; the cursed AC 9 [10] forms carry ac_set / ac_set_ascending instead. charges_dice rolls at generation (referee-only forever after); quantity_dice sizes ammunition (sub-table rows may override per printed band). weight_coins is the base item's weight — enchanted armour at half per RAW, potions/scrolls/devices from the TreasureWeight rows. hoard_recipe is a treasure map's compiled hoard; curses is the cursed scroll's example-curse table.

id instance-attribute

id: str

name instance-attribute

name: str

category instance-attribute

base_item_id class-attribute instance-attribute

base_item_id: str | None = None

attack_bonus class-attribute instance-attribute

attack_bonus: int = 0

damage_bonus class-attribute instance-attribute

damage_bonus: int = 0

ac_bonus class-attribute instance-attribute

ac_bonus: int = 0

ac_set class-attribute instance-attribute

ac_set: int | None = None

ac_set_ascending class-attribute instance-attribute

ac_set_ascending: int | None = None

versus class-attribute instance-attribute

versus: tuple[VersusBonus, ...] = ()

cursed class-attribute instance-attribute

cursed: bool = False

charges_dice class-attribute instance-attribute

charges_dice: str | None = None

quantity_dice class-attribute instance-attribute

quantity_dice: str | None = None

usable_by class-attribute instance-attribute

usable_by: UsableBy = UsableBy()

always_active class-attribute instance-attribute

always_active: bool = False

effect class-attribute instance-attribute

effect: MagicItemEffect | None = None

params class-attribute instance-attribute

params: dict[str, int | str | bool | tuple[int | str, ...]] = {}

manual class-attribute instance-attribute

manual: tuple[str, ...] = ()

weight_coins class-attribute instance-attribute

weight_coins: int = Field(default=0, ge=0)

hoard_recipe class-attribute instance-attribute

hoard_recipe: tuple[TreasureEntry, ...] = ()

curses class-attribute instance-attribute

curses: tuple[ScrollCurse, ...] = ()

overrides_applied class-attribute instance-attribute

overrides_applied: tuple[str, ...] = ()

MagicSubTable

Bases: BaseModel

One category's generation sub-table: the sparse B column and the full X column.

category instance-attribute

category: MagicItemType

basic_die class-attribute instance-attribute

basic_die: int = Field(ge=2)

rows class-attribute instance-attribute

rows: tuple[MagicSubTableRow, ...] = Field(min_length=1)

row_for_basic

row_for_basic(roll: int) -> MagicSubTableRow

Return the row a Basic-tier small-die roll selects.

Parameters:

Name Type Description Default
roll int

The small-die result, 1 through basic_die.

required

Returns:

Type Description
MagicSubTableRow

The selected row.

Raises:

Type Description
ValueError

If no row carries that face.

row_for_expert

row_for_expert(roll: int) -> MagicSubTableRow

Return the row an Expert-tier d% roll selects.

Parameters:

Name Type Description Default
roll int

The d% result, 1–100.

required

Returns:

Type Description
MagicSubTableRow

The selected row.

Raises:

Type Description
ValueError

If the roll is outside 1–100.

MagicSubTableRow

Bases: BaseModel

One outcome row of a category generation sub-table.

basic_value is the sparse small-die B column face (None when the printed cell is blank — B and X are independent index spaces over the same outcome list); expert_min/expert_max are the full d% X band (00 reads as 100). item_ids is usually one id; the armour-with-shield rows are two-item bundles. params carries per-band generation data (the ring wish-count dice, the arrow and bolt quantity dice) that overrides the template's own fields.

item_ids class-attribute instance-attribute

item_ids: tuple[str, ...] = Field(min_length=1)

basic_value class-attribute instance-attribute

basic_value: int | None = None

expert_min class-attribute instance-attribute

expert_min: int = Field(ge=1, le=100)

expert_max class-attribute instance-attribute

expert_max: int = Field(ge=1, le=100)

params class-attribute instance-attribute

params: dict[str, int | str | bool | tuple[int | str, ...]] = {}

Material

Bases: StrEnum

Weapon material — silver matters to the damage pipeline's immunity gate. Extensible.

STANDARD class-attribute instance-attribute

STANDARD = 'standard'

SILVER class-attribute instance-attribute

SILVER = 'silver'

MissileRanges

Bases: BaseModel

A missile weapon's short (+1 to hit), medium, and long (−1 to hit) range bands.

short instance-attribute

short: RangeBand

medium instance-attribute

medium: RangeBand

long instance-attribute

long: RangeBand

RangeBand

Bases: BaseModel

One missile range band in feet, as the SRD prints it (5'–80').

min_feet class-attribute instance-attribute

min_feet: int = Field(ge=0)

max_feet class-attribute instance-attribute

max_feet: int = Field(ge=0)

ScrollCurse

Bases: BaseModel

One of the cursed scroll's six example curses, compiled as a data row.

wired=True marks the two the kernel resolves (energy drain through drain_levels with the curse's own halfway XP policy, slow healing through the slowed-healing hooks); the rest are manual prose carried on the event.

id instance-attribute

id: str

name instance-attribute

name: str

prose instance-attribute

prose: str

wired class-attribute instance-attribute

wired: bool = False

ScrollSpellLevelRow

Bases: BaseModel

One row of the Random Scroll Spell Level table.

The B column here is d6 bands (1–3), not sparse faces — None bounds mark the Expert-only rows.

basic_min class-attribute instance-attribute

basic_min: int | None = None

basic_max class-attribute instance-attribute

basic_max: int | None = None

expert_min class-attribute instance-attribute

expert_min: int = Field(ge=1, le=100)

expert_max class-attribute instance-attribute

expert_max: int = Field(ge=1, le=100)

arcane_level class-attribute instance-attribute

arcane_level: int = Field(ge=1, le=6)

divine_level class-attribute instance-attribute

divine_level: int = Field(ge=1, le=5)

ScrollSpellLevelTable

Bases: BaseModel

The Random Scroll Spell Level table, with its arcane and divine columns.

rows class-attribute instance-attribute

rows: tuple[ScrollSpellLevelRow, ...] = Field(min_length=1)

level_for_basic

level_for_basic(roll: int, *, divine: bool) -> int

Return the spell level a Basic-tier d6 roll selects.

Parameters:

Name Type Description Default
roll int

The d6 result, 1–6.

required
divine bool

True for the divine column.

required

Returns:

Type Description
int

The spell level.

Raises:

Type Description
ValueError

If no band covers the roll.

level_for_expert

level_for_expert(roll: int, *, divine: bool) -> int

Return the spell level an Expert-tier d% roll selects.

Parameters:

Name Type Description Default
roll int

The d% result, 1–100.

required
divine bool

True for the divine column.

required

Returns:

Type Description
int

The spell level.

Raises:

Type Description
ValueError

If the roll is outside 1–100.

SentientSwordTables

Bases: BaseModel

The sentient-sword generation tables, compiled as data.

Generation follows the SRD's own procedure: the special-purpose 1-in-20 roll first (a special sword is always sentient at INT 12/Ego 12), otherwise the 30% sentience roll, then INT 1d6+6, communication, languages, alignment, powers, and Ego 1d12, in the printed order.

communication instance-attribute

communication: tuple[SwordCommunicationRow, ...]

languages instance-attribute

languages: tuple[SwordTableBand, ...]

alignment instance-attribute

alignment: tuple[SwordTableBand, ...]

powers instance-attribute

powers: tuple[SwordPowersRow, ...]

sensory_bands instance-attribute

sensory_bands: tuple[SwordTableBand, ...]

extraordinary_bands instance-attribute

extraordinary_bands: tuple[SwordTableBand, ...]

powers_catalog instance-attribute

powers_catalog: tuple[SwordPower, ...]

special_purposes instance-attribute

special_purposes: tuple[SwordTableBand, ...]

special_purpose_prose class-attribute instance-attribute

special_purpose_prose: str = ''

alignment_touch_prose class-attribute instance-attribute

alignment_touch_prose: str = ''

power

power(power_id: str) -> SwordPower

Return the power with power_id.

Parameters:

Name Type Description Default
power_id str

The power id, e.g. "detect_magic".

required

Returns:

Type Description
SwordPower

The power.

Raises:

Type Description
ValueError

If no power has that id.

SwordCommunicationRow

Bases: BaseModel

One INT row of the sentient sword Communication table.

int_score class-attribute instance-attribute

int_score: int = Field(ge=7, le=12)

reading instance-attribute

reading: bool

communication instance-attribute

communication: str

SwordControlResult

Bases: BaseModel

A sentient sword control check's outcome — the RAW arithmetic, no events.

sword_will instance-attribute

sword_will: int

wielder_will instance-attribute

wielder_will: int

sword_controls instance-attribute

sword_controls: bool

SwordPower

Bases: BaseModel

One sentient-sword power: manual-tagged prose data (automation is out of 1.0).

id instance-attribute

id: str

name instance-attribute

name: str

prose instance-attribute

prose: str

extraordinary class-attribute instance-attribute

extraordinary: bool = False

duplicates_allowed class-attribute instance-attribute

duplicates_allowed: bool = False

SwordPowersRow

Bases: BaseModel

One INT row of the sentient sword Powers table.

int_score class-attribute instance-attribute

int_score: int = Field(ge=7, le=12)

sensory class-attribute instance-attribute

sensory: int = Field(ge=0)

extraordinary class-attribute instance-attribute

extraordinary: int = Field(ge=0)

SwordSentience

Bases: BaseModel

A sentient sword's rolled qualities — generation data fixed at creation.

intelligence class-attribute instance-attribute

intelligence: int = Field(ge=7, le=12)

ego class-attribute instance-attribute

ego: int = Field(ge=1, le=12)

communication instance-attribute

communication: str

reading instance-attribute

reading: bool

alignment instance-attribute

alignment: str

languages class-attribute instance-attribute

languages: int = Field(default=0, ge=0)

sensory_powers class-attribute instance-attribute

sensory_powers: tuple[str, ...] = ()

extraordinary_powers class-attribute instance-attribute

extraordinary_powers: tuple[str, ...] = ()

special_purpose class-attribute instance-attribute

special_purpose: str | None = None

SwordTableBand

Bases: BaseModel

One die band of a sentient-sword roll table; result is a slug or directive.

Directives: roll_twice (languages and both power tables), roll_thrice (extraordinary 00), roll_extraordinary (sensory 96–99).

roll_min class-attribute instance-attribute

roll_min: int = Field(ge=1)

roll_max class-attribute instance-attribute

roll_max: int = Field(ge=1)

result class-attribute instance-attribute

result: str = Field(min_length=1)

TreasureWeight

Bases: BaseModel

A treasure encumbrance row (coin, gem, jewellery, ...) from the encumbrance table.

id instance-attribute

id: str

weight_coins class-attribute instance-attribute

weight_coins: int = Field(ge=0)

UsableBy

Bases: BaseModel

Who may use a magic item.

all is the default ("All characters (unless noted)"); caster restricts to spell casters of caster kind (arcane for wands, per staff page otherwise); classes restricts to the named class ids. Swords, weapons, and armour stay all — "per normal class restrictions" resolves through the base item's equip policies, not here.

kind class-attribute instance-attribute

kind: Literal['all', 'classes', 'caster'] = 'all'

class_ids class-attribute instance-attribute

class_ids: tuple[str, ...] = ()

caster class-attribute instance-attribute

caster: Literal['arcane', 'divine', 'any'] | None = None

ValuableInstance

Bases: BaseModel

A gem or piece of jewellery, its value rolled at generation and fixed.

Appraisal is always instantaneous and exact: B/X prices treasure for the XP economy, and a haggling or appraisal minigame is a game's own feature to add, not part of osrlib. weight_coins comes from the TreasureWeight rows at generation.

instance_type class-attribute instance-attribute

instance_type: Literal['valuable'] = 'valuable'

instance_id instance-attribute

instance_id: str

kind instance-attribute

kind: Literal['gem', 'jewellery']

name class-attribute instance-attribute

name: str = ''

value_gp class-attribute instance-attribute

value_gp: int = Field(ge=0)

weight_coins class-attribute instance-attribute

weight_coins: int = Field(default=0, ge=0)

VersusBonus

Bases: BaseModel

A +2 vs Lycanthropes clause: the printed label and its resolved targets.

bonus is the alternate attack-and-damage bonus that replaces the item's base bonus against a matching target. Targets resolve structurally, never by string-matching prose: categories name monster category tags (undead, enchanted) and template_ids name compiled monster ids (the lycanthrope set, the ability-derived spell-user and regenerating sets, the dagger's orcs/goblins/kobolds). A clause matches a target whose template carries any listed category or id; characters carry no monster template, so a clause never matches one.

label class-attribute instance-attribute

label: str = Field(min_length=1)

bonus instance-attribute

bonus: int

categories class-attribute instance-attribute

categories: tuple[str, ...] = ()

template_ids class-attribute instance-attribute

template_ids: tuple[str, ...] = ()

WeaponQuality

Bases: StrEnum

The SRD's weapon qualities, consumed by combat resolution.

BLUNT class-attribute instance-attribute

BLUNT = 'blunt'

BRACE class-attribute instance-attribute

BRACE = 'brace'

CHARGE class-attribute instance-attribute

CHARGE = 'charge'

MELEE class-attribute instance-attribute

MELEE = 'melee'

MISSILE class-attribute instance-attribute

MISSILE = 'missile'

RELOAD class-attribute instance-attribute

RELOAD = 'reload'

SLOW class-attribute instance-attribute

SLOW = 'slow'

SPLASH class-attribute instance-attribute

SPLASH = 'splash'

TWO_HANDED class-attribute instance-attribute

TWO_HANDED = 'two_handed'

WeaponTemplate

Bases: BaseModel

A mundane weapon from the SRD's Weapon Combat Stats table.

item_type class-attribute instance-attribute

item_type: Literal['weapon'] = 'weapon'

id instance-attribute

id: str

name instance-attribute

name: str

cost_gp class-attribute instance-attribute

cost_gp: int = Field(ge=0)

weight_coins class-attribute instance-attribute

weight_coins: int = Field(ge=0)

damage instance-attribute

damage: str

qualities instance-attribute

qualities: tuple[WeaponQuality, ...]

missile_ranges class-attribute instance-attribute

missile_ranges: MissileRanges | None = None

material class-attribute instance-attribute

overrides_applied class-attribute instance-attribute

overrides_applied: tuple[str, ...] = ()

encounter_movement_rate

encounter_movement_rate(base_rate_feet: int) -> int

Return the encounter movement rate: base ÷ 3, computed, never stored.

Parameters:

Name Type Description Default
base_rate_feet int

The base movement rate in feet per turn.

required

Returns:

Type Description
int

The per-round encounter rate in feet.

equip

equip(inventory: Inventory, definition: ClassDefinition, instance: ItemInstance | MagicItemInstance) -> None

Equip an item from the inventory's item list.

Body armour goes to the worn-armour slot and the shield to the shield slot — a previous occupant returns to the item list. Weapons, combat-facet gear, devices, and miscellaneous magic items join the wielded list; rings join the ring slots. Equipping a two-handed weapon with a shield equipped (or the shield while a two-handed weapon is wielded) rejects — the conflict is enforced at equip time, not silently ignored at resolution.

Parameters:

Name Type Description Default
inventory Inventory

The inventory; mutated.

required
definition ClassDefinition

The character's class definition.

required
instance ItemInstance | MagicItemInstance

The instance to equip; must be in the item list.

required

Raises:

Type Description
ValueError

If the instance is not in the item list, or equipping it fails validate_equip.

equipment_weight_coins

equipment_weight_coins(inventory: Inventory) -> int

Return detailed-encumbrance equipment weight: weapons, armour, and the gear flat.

Weapons and armour weigh their listed weights; ammunition always weighs 0 — its weight is already folded into the missile weapon's listed weight, and the ammunition table has no weight column of its own. Miscellaneous gear counts as a flat 80 coins when any is carried, since the SRD gives gear no per-item weights. Enchanted weapons and armour weigh as equipment beside their mundane bases — base weight, armour halved per RAW.

Parameters:

Name Type Description Default
inventory Inventory

The inventory to weigh.

required

Returns:

Type Description
int

The equipment weight in coins.

equipped_item_modifiers

equipped_item_modifiers(inventory: Inventory) -> list[ModifierSpec]

Return the stat modifiers granted by equipped always-active magic items.

Item bonuses are computed from equipped inventory at query time rather than stored as ActiveEffect stat modifiers: they combine freely with spell modifiers and are always exempt from the cumulative largest-bonus-plus-largest- penalty cap that modifier_total applies to spell-sourced modifiers.

Parameters:

Name Type Description Default
inventory Inventory

The inventory to scan.

required

Returns:

Type Description
list[ModifierSpec]

The modifiers, in equipped order (slots, wielded, rings).

magic_item_template

magic_item_template(instance: MagicItemInstance) -> MagicItemTemplate

Return a magic item instance's template from load_magic_items's catalog.

Parameters:

Name Type Description Default
instance MagicItemInstance

The instance whose template to look up.

required

Returns:

Type Description
MagicItemTemplate

The frozen template.

movement_rate_feet

movement_rate_feet(inventory: Inventory, ruleset: Ruleset, carrying_treasure: bool = False) -> int

Return the movement rate in feet per exploration turn.

Per the Ruleset encumbrance flag:

  • none: always 120; nothing is tracked and no load cap applies.
  • basic: rate by worn-armour category (unarmoured/light/heavy) and the carrying_treasure judgment — significant treasure is a referee call in RAW, so it stays one: the game sets the flag, no invented threshold. Treasure weight (coins included) is still tracked against the 1,600-coin maximum load.
  • detailed: rate by total tracked weight; the SRD's thresholds are inclusive ("up to").

In both tracking modes, tracked weight above the 1,600-coin maximum load means the character cannot move (movement 0).

Parameters:

Name Type Description Default
inventory Inventory

The inventory to weigh.

required
ruleset Ruleset

The ruleset whose encumbrance flag governs.

required
carrying_treasure bool

Basic mode's referee judgment: is the character carrying a significant amount of treasure?

False

Returns:

Type Description
int

The movement rate in feet per turn: 120, 90, 60, 30, or 0.

purchase

purchase(inventory: Inventory, template: ItemTemplate, lots: int = 1) -> ItemInstance

Buy lots purchase lots of an item, paying from the purse.

A purchase lot is the quantity one purchase at the template's listed cost_gp delivers: gear and ammunition arrive in lot_size-sized lots (buying one lot of torches costs 1 gp and yields quantity 6), while weapons and armour carry no lot_size field, so one lot is a single unit. lots scales both the total cost and the delivered quantity: buying 2 lots of torches costs 2 gp and yields quantity 12. The new instance is appended to the item list.

Parameters:

Name Type Description Default
inventory Inventory

The buyer's inventory; mutated.

required
template ItemTemplate

The item to buy.

required
lots int

How many purchase lots. Positive.

1

Returns:

Type Description
ItemInstance

The purchased instance.

Raises:

Type Description
ValueError

If the purchase fails validate_purchase — buying what you cannot afford is programmer misuse.

sword_control_check

sword_control_check(character: Any, sword: MagicItemInstance, *, stream: RngStream) -> SwordControlResult

Resolve a sentient sword control check, à la carte.

RAW arithmetic: the sword's Will is INT + Ego, +1 per extraordinary power, +1d10 when the sword's and wielder's alignments differ; the wielder's Will is STR + WIS, −1d4 below full hit points, −2d4 below half. The sword controls when its Will is strictly higher. The crawl framework never auto-invokes this check; games narrate control through referee commands.

Parameters:

Name Type Description Default
character Any

The wielder: a Character with ability scores and hit points.

required
sword MagicItemInstance

The sentient sword instance.

required
stream RngStream

The RNG stream for the situational dice; à la carte callers choose.

required

Returns:

Type Description
SwordControlResult

The plain result — no events, crawl-neutral.

Raises:

Type Description
ValueError

If the sword is not sentient.

tracked_weight_coins

tracked_weight_coins(inventory: Inventory, mode: EncumbranceMode) -> int

Return the weight the given encumbrance mode tracks.

Parameters:

Name Type Description Default
inventory Inventory

The inventory to weigh.

required
mode EncumbranceMode

The encumbrance mode in play.

required

Returns:

Type Description
int

0 under none (nothing is tracked), treasure weight under basic, and

int

treasure plus equipment weight under detailed.

treasure_weight_coins

treasure_weight_coins(inventory: Inventory) -> int

Return the weight of carried treasure in coins.

Purse coins weigh 1 each; valuables weigh their TreasureWeight figures; magic items in the categories those rows price (potion, scroll, rod, staff, wand) weigh as treasure. Rings and miscellaneous items weigh zero absent a page figure (the Bag of Holding's printed loaded weight rides its params, counted while it holds anything); enchanted weapons and armour always weigh as equipment beside their mundane bases, not as treasure, so basic encumbrance's treasure tracking stays honest.

Parameters:

Name Type Description Default
inventory Inventory

The inventory to weigh.

required

Returns:

Type Description
int

The treasure weight in coins.

unequip

unequip(inventory: Inventory, instance: ItemInstance | MagicItemInstance) -> None

Return an equipped instance to the item list.

Parameters:

Name Type Description Default
inventory Inventory

The inventory; mutated.

required
instance ItemInstance | MagicItemInstance

The equipped instance.

required

Raises:

Type Description
ValueError

If the instance is not equipped, or a revealed curse pins it (validate with validate_unequip first).

usable_by_class

usable_by_class(template: MagicItemTemplate, definition: ClassDefinition) -> bool

Return whether a class may use a magic item per its usable_by.

Parameters:

Name Type Description Default
template MagicItemTemplate

The magic item template.

required
definition ClassDefinition

The class definition.

required

Returns:

Type Description
bool

True when the item's usability admits the class.

validate_equip

validate_equip(
    definition: ClassDefinition, instance: ItemInstance | MagicItemInstance, inventory: Inventory | None = None
) -> list[Rejection]

Validate equipping an item against the class's armour and weapon policies.

Weapon policies govern the weapons list only: gear carrying a combat facet (torch, holy water, burning oil) is exempt and always equippable, as a documented adaptation (see the adaptations register) that also lets a magic-user throw oil or swing a torch. Gear without a facet, and ammunition, is not equippable at all. Magic items resolve through their base item's policies (enchanted arms), the ring cap, or their own usable_by (devices, miscellaneous items).

Wielding a two-handed weapon with a shield equipped — or equipping the second of the pair — always rejects with items.equip.two_handed_with_shield: the conflict is enforced at equip time rather than silently ignored at resolution. The check needs the current equipped state, so pass inventory when one exists.

Parameters:

Name Type Description Default
definition ClassDefinition

The character's class definition.

required
instance ItemInstance | MagicItemInstance

The instance to equip.

required
inventory Inventory | None

The inventory whose equipped state the two-handed-versus-shield conflict is checked against.

None

Returns:

Type Description
list[Rejection]

Structured rejections; empty when equipping is legal.

validate_purchase

validate_purchase(purse: CoinPurse, template: ItemTemplate, lots: int = 1) -> list[Rejection]

Validate buying lots purchase lots of an item.

See purchase for exactly what a purchase lot buys.

Parameters:

Name Type Description Default
purse CoinPurse

The buyer's purse.

required
template ItemTemplate

The item to buy.

required
lots int

How many purchase lots. Positive.

1

Returns:

Type Description
list[Rejection]

Structured rejections; empty when the purchase is legal.

Raises:

Type Description
ValueError

If lots is not positive.

validate_unequip

validate_unequip(inventory: Inventory, instance: ItemInstance | MagicItemInstance) -> list[Rejection]

Validate returning an equipped instance to the item list.

A revealed cursed item pins to its bearer: it rejects with items.curse.stuck until remove curse (each cursed category's page carries the same cannot-discard clause).

Parameters:

Name Type Description Default
inventory Inventory

The inventory holding the instance.

required
instance ItemInstance | MagicItemInstance

The equipped instance.

required

Returns:

Type Description
list[Rejection]

Structured rejections; empty when unequipping is legal.